mirror of
https://gitlab.cern.ch/wotsubo/endcap-sl-software-ri-generator.git
synced 2025-04-20 03:36:25 +09:00
Compare commits
24 commits
Author | SHA1 | Date | |
---|---|---|---|
b492d097e7 | |||
12332e8565 | |||
fafd262230 | |||
9444431a61 | |||
1bb239812e | |||
ccfd443e5d | |||
cba5b8594b | |||
b5d165a3f5 | |||
e6e7ada193 | |||
b9f6dc6c23 | |||
1104f826fc | |||
5c7aa91d56 | |||
fa777f43fb | |||
205b08400b | |||
8732afd297 | |||
d0d5299101 | |||
76595d0c92 | |||
06a5429583 | |||
9971774a01 | |||
f8c653a1cc | |||
605228e2b5 | |||
dd119ccdb8 | |||
|
e62d935829 | ||
|
34e1528745 |
19 changed files with 1105 additions and 126 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
/target
|
/target
|
||||||
|
result*
|
||||||
|
|
37
CHANGELOG.md
37
CHANGELOG.md
|
@ -5,6 +5,39 @@ 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).
|
||||||
|
|
||||||
|
## [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
|
## [0.2.0] - 2025-02-12
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -23,6 +56,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
- 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.2.0...main
|
[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.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
|
||||||
|
|
24
Cargo.lock
generated
24
Cargo.lock
generated
|
@ -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.2.0"
|
version = "0.3.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
"csv",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"heck",
|
"heck",
|
||||||
"hex",
|
"hex",
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
[package]
|
[package]
|
||||||
name = "endcap-sl-software-ri-generator"
|
name = "endcap-sl-software-ri-generator"
|
||||||
version = "0.2.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"
|
||||||
|
@ -20,6 +20,7 @@ path = "src/lib.rs"
|
||||||
anyhow = { version = "1.0", optional = true }
|
anyhow = { version = "1.0", optional = true }
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
clap = { version = "4.5", features = ["derive"] }
|
clap = { version = "4.5", features = ["derive"] }
|
||||||
|
csv = { version = "1.3.1", optional = true }
|
||||||
env_logger = { version = "0.11", optional = true }
|
env_logger = { version = "0.11", optional = true }
|
||||||
heck = "0.5"
|
heck = "0.5"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
@ -37,4 +38,5 @@ thiserror = "2.0"
|
||||||
vergen-gitcl = { version = "1.0", features = ["build", "cargo", "rustc", "si"] }
|
vergen-gitcl = { version = "1.0", features = ["build", "cargo", "rustc", "si"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
bin = ["anyhow", "env_logger"]
|
bin = ["anyhow", "env_logger", "flatmap"]
|
||||||
|
flatmap = ["csv"]
|
||||||
|
|
|
@ -33,7 +33,7 @@ cargo build --features=bin --bins --release
|
||||||
|
|
||||||
`target/release`に入ってるバイナリファイルを実行するか、`cargo run -- <XML> <OUT>`で実行できます。
|
`target/release`に入ってるバイナリファイルを実行するか、`cargo run -- <XML> <OUT>`で実行できます。
|
||||||
|
|
||||||
詳しくは`--help`を見てください。
|
詳しくは(`-h`ではなく)`--help`を見てください。
|
||||||
|
|
||||||
### ライブラリクレートの使い方
|
### ライブラリクレートの使い方
|
||||||
|
|
||||||
|
|
|
@ -36,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
82
flake.lock
generated
Normal 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
37
flake.nix
Normal 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 { };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
11
nix/endcap-sl-software-ri-generator.nix
Normal file
11
nix/endcap-sl-software-ri-generator.nix
Normal 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" ];
|
||||||
|
}
|
|
@ -396,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",
|
||||||
|
|
|
@ -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
|
||||||
|
@ -161,27 +171,29 @@ This code is auto generated using endcap_sl_software_ri_generator.
|
||||||
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")
|
{
|
||||||
{
|
quote! {
|
||||||
quote! {
|
#![doc = #build_metadata]
|
||||||
#![doc = #build_metadata]
|
|
||||||
|
|
||||||
#tokens
|
#tokens
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tokens
|
tokens
|
||||||
};
|
};
|
||||||
let file: syn::File = syn::parse2(tokens)?;
|
let file: syn::File =
|
||||||
Ok((path, file))
|
syn::parse2(tokens.clone()).map_err(|e| CodeGenError::SynError {
|
||||||
},
|
source: e,
|
||||||
)
|
code: tokens.to_string(),
|
||||||
.process_results(|kv| HashMap::from_iter(kv))?)
|
})?;
|
||||||
|
Ok((path, file))
|
||||||
|
})
|
||||||
|
.process_results(|kv| HashMap::from_iter(kv))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,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);
|
||||||
|
|
||||||
|
@ -287,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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -297,7 +311,7 @@ impl CodeGen for Block {
|
||||||
match ®ister.multiple {
|
match ®ister.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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -311,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),* ]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -324,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
|
||||||
|
@ -346,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) },
|
||||||
|
@ -373,7 +389,9 @@ impl CodeGen for Block {
|
||||||
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| {
|
||||||
|
@ -439,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) },
|
||||||
|
@ -465,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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,31 @@
|
||||||
use std::path;
|
use std::path;
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
converter, generator,
|
converter, generator,
|
||||||
io::{get_xml_gitinfo, XmlGitInfoError},
|
io::{XmlGitInfoError, get_xml_gitinfo},
|
||||||
types,
|
types, validator,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("error in getting xml git info: {0}")]
|
#[error("error in getting xml git info")]
|
||||||
XmlGitInfoError(#[from] XmlGitInfoError),
|
XmlGitInfoError(#[from] XmlGitInfoError),
|
||||||
#[error("io error: {0}")]
|
#[error("io error")]
|
||||||
IOError(#[from] std::io::Error),
|
IOError(#[from] std::io::Error),
|
||||||
#[error("xml parse error: {0}")]
|
#[error("xml parse error")]
|
||||||
XmlParseError(#[from] roxmltree::Error),
|
XmlParseError(#[from] roxmltree::Error),
|
||||||
#[error("dom conversion error: {0}")]
|
#[error("dom conversion error")]
|
||||||
DomConversionError(#[from] converter::DomConversionError),
|
DomConversionError(#[from] converter::DomConversionError),
|
||||||
#[error("code generation error: {0}")]
|
#[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),
|
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`.
|
/// Generate register interface code from xml in `xml` and write them under directory `out`.
|
||||||
|
@ -42,6 +48,11 @@ 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)?;
|
let register_map = types::Module::from_xml_dom(doc.root_element(), xml_git_info)?;
|
||||||
log::debug!("converted {register_map:?}");
|
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()?;
|
let files = register_map.generate_code()?;
|
||||||
if log::log_enabled!(log::Level::Debug) {
|
if log::log_enabled!(log::Level::Debug) {
|
||||||
for (path, code) in &files {
|
for (path, code) in &files {
|
||||||
|
@ -56,3 +67,80 @@ pub fn generate(xml: &path::Path, out: &path::Path) -> Result<(), Error> {
|
||||||
|
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
|
|
120
src/io.rs
120
src/io.rs
|
@ -1,11 +1,15 @@
|
||||||
//! File IO for generated codes.
|
//! File IO for generated codes.
|
||||||
|
|
||||||
use std::{collections::HashMap, fs, io, path, process};
|
use std::{collections::HashMap, env, ffi::OsString, fs, io, path, process};
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::types::XmlGitInfo;
|
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)]
|
#[derive(Debug, Error)]
|
||||||
pub enum XmlGitInfoError {
|
pub enum XmlGitInfoError {
|
||||||
#[error("io error while getting xml git info: {msg} {source}")]
|
#[error("io error while getting xml git info: {msg} {source}")]
|
||||||
|
@ -16,6 +20,8 @@ pub enum XmlGitInfoError {
|
||||||
},
|
},
|
||||||
#[error("git failed: {msg}: {stderr}")]
|
#[error("git failed: {msg}: {stderr}")]
|
||||||
CommandFailed { msg: &'static str, stderr: String },
|
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 {
|
impl XmlGitInfoError {
|
||||||
|
@ -25,44 +31,84 @@ impl XmlGitInfoError {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_xml_gitinfo(xml_path: &path::Path) -> Result<XmlGitInfo, XmlGitInfoError> {
|
pub(crate) fn get_xml_gitinfo(xml_path: &path::Path) -> Result<XmlGitInfo, XmlGitInfoError> {
|
||||||
let describe = process::Command::new("git")
|
let describe = match env::var(ENVVAR_XML_GIT_DESCRIBE) {
|
||||||
.args(["describe", "--always", "--dirty"])
|
Ok(describe) => describe,
|
||||||
.current_dir(xml_path.parent().unwrap())
|
Err(env::VarError::NotUnicode(s)) => {
|
||||||
.output()
|
return Err(XmlGitInfoError::NotUnicodeEnvVar {
|
||||||
.map_err(|e| XmlGitInfoError::io_error(e, "git describe"))?;
|
var: ENVVAR_XML_GIT_DESCRIBE,
|
||||||
let describe = if describe.status.success() {
|
s,
|
||||||
String::from_utf8_lossy(&describe.stdout).trim().to_owned()
|
});
|
||||||
} else {
|
}
|
||||||
return Err(XmlGitInfoError::CommandFailed {
|
Err(env::VarError::NotPresent) => {
|
||||||
msg: "git describe",
|
let out_describe = process::Command::new("git")
|
||||||
stderr: String::from_utf8_lossy(&describe.stderr).into_owned(),
|
.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 = process::Command::new("git")
|
let timestamp = match env::var(ENVVAR_XML_GIT_TIMESTAMP) {
|
||||||
.args(["log", "-1", "--pretty=format:'%cI'"])
|
Ok(timestamp) => timestamp,
|
||||||
.current_dir(xml_path.parent().unwrap())
|
Err(env::VarError::NotUnicode(s)) => {
|
||||||
.output()
|
return Err(XmlGitInfoError::NotUnicodeEnvVar {
|
||||||
.map_err(|e| XmlGitInfoError::io_error(e, "git log (timestamp)"))?;
|
var: ENVVAR_XML_GIT_TIMESTAMP,
|
||||||
let timestamp = if timestamp.status.success() {
|
s,
|
||||||
String::from_utf8_lossy(×tamp.stdout).trim().to_owned()
|
});
|
||||||
} else {
|
}
|
||||||
return Err(XmlGitInfoError::CommandFailed {
|
Err(env::VarError::NotPresent) => {
|
||||||
msg: "git log (timestamp)",
|
let timestamp_out = process::Command::new("git")
|
||||||
stderr: String::from_utf8_lossy(×tamp.stderr).into_owned(),
|
.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(×tamp_out.stdout)
|
||||||
|
.trim()
|
||||||
|
.to_owned()
|
||||||
|
} else {
|
||||||
|
return Err(XmlGitInfoError::CommandFailed {
|
||||||
|
msg: "git log (timestamp)",
|
||||||
|
stderr: String::from_utf8_lossy(×tamp_out.stderr).into_owned(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
timestamp
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let sha = process::Command::new("git")
|
let git_sha = match env::var(ENVVAR_XML_GIT_SHA) {
|
||||||
.args(["rev-parse", "HEAD"])
|
Ok(sha) => sha,
|
||||||
.current_dir(xml_path.parent().unwrap())
|
Err(env::VarError::NotUnicode(s)) => {
|
||||||
.output()
|
return Err(XmlGitInfoError::NotUnicodeEnvVar {
|
||||||
.map_err(|e| XmlGitInfoError::io_error(e, "git rev-parse (sha)"))?;
|
var: ENVVAR_XML_GIT_SHA,
|
||||||
let git_sha = if sha.status.success() {
|
s,
|
||||||
String::from_utf8_lossy(&sha.stdout).trim().to_owned()
|
});
|
||||||
} else {
|
}
|
||||||
return Err(XmlGitInfoError::CommandFailed {
|
Err(env::VarError::NotPresent) => {
|
||||||
msg: "git rev-parse (sha)",
|
let sha = process::Command::new("git")
|
||||||
stderr: String::from_utf8_lossy(&sha.stderr).into_owned(),
|
.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 {
|
Ok(XmlGitInfo {
|
||||||
describe,
|
describe,
|
||||||
|
|
|
@ -14,7 +14,8 @@
|
||||||
//!
|
//!
|
||||||
//! 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
|
||||||
|
@ -22,6 +23,7 @@
|
||||||
//! - [`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;
|
||||||
|
@ -31,6 +33,7 @@ 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 integrated::generate;
|
||||||
pub use io::write_to_files;
|
pub use io::write_to_files;
|
||||||
|
|
47
src/main.rs
47
src/main.rs
|
@ -1,16 +1,41 @@
|
||||||
use std::path;
|
use std::path;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::{Parser, Subcommand};
|
||||||
|
|
||||||
/// 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,
|
||||||
/// Output directory.
|
#[command(subcommand)]
|
||||||
out: path::PathBuf,
|
command: Commands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
enum Commands {
|
||||||
|
/// Generate register interface code.
|
||||||
|
///
|
||||||
|
/// Also run validation before generation.
|
||||||
|
Generate {
|
||||||
|
/// Output directory.
|
||||||
|
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<()> {
|
||||||
|
@ -19,7 +44,17 @@ fn main() -> Result<()> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
log::debug!("args: {:?}", args);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
591
src/validator.rs
Normal file
591
src/validator.rs
Normal 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
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue