2023-10-01 13:28:31 -05:00

276 lines
9.3 KiB
Rust

use serde::{Deserialize, Serialize};
#[macro_export]
macro_rules! regex {
($re:literal) => {{
::ref_thread_local::ref_thread_local! {
static managed REGEX: ::regex::Regex = ::regex::Regex::new($re).unwrap();
}
<REGEX as ::ref_thread_local::RefThreadLocal<::regex::Regex>>::borrow(&REGEX)
}};
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Chip {
pub name: String,
pub family: String,
pub line: String,
pub die: String,
pub device_id: u16,
pub packages: Vec<chip::Package>,
pub memory: Vec<chip::Memory>,
pub docs: Vec<chip::Doc>,
pub cores: Vec<chip::Core>,
}
pub mod chip {
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Package {
pub name: String,
pub package: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Memory {
pub name: String,
pub kind: memory::Kind,
pub address: u32,
pub size: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub settings: Option<memory::Settings>,
}
pub mod memory {
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Kind {
Flash,
Ram,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Settings {
pub erase_size: u32,
pub write_size: u32,
pub erase_value: u8,
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Doc {
pub r#type: String,
pub title: String,
pub name: String,
pub url: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Core {
pub name: String,
pub peripherals: Vec<core::Peripheral>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nvic_priority_bits: Option<u8>,
pub interrupts: Vec<core::Interrupt>,
pub dma_channels: Vec<core::DmaChannels>,
}
pub mod core {
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Peripheral {
pub name: String,
#[serde(default)]
pub address: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub registers: Option<peripheral::Registers>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rcc: Option<peripheral::Rcc>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub pins: Vec<peripheral::Pin>,
#[serde(skip_serializing_if = "Option::is_none")]
pub interrupts: Option<Vec<peripheral::Interrupt>>, // TODO: This should just be a Vec
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub dma_channels: Vec<peripheral::DmaChannel>,
}
pub mod peripheral {
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Registers {
pub kind: String,
pub version: String,
pub block: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Rcc {
pub clock: String,
pub enable: rcc::Enable,
#[serde(skip_serializing_if = "Option::is_none")]
pub reset: Option<rcc::Reset>,
}
pub mod rcc {
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Enable {
pub register: String,
pub field: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Reset {
pub register: String,
pub field: String,
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct Pin {
pub pin: String,
pub signal: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub af: Option<u8>,
}
fn extract_port_and_pin(pin: &str) -> (char, u8) {
let captures = regex!(r"^P([A-Z])(\d+)(?:_C)?")
.captures(pin)
.expect("Could not match regex on pin");
let port = captures
.get(1)
.expect("Could not extract port")
.as_str()
.chars()
.next()
.expect("Empty port");
let pin_number = captures
.get(2)
.expect("Could not extract pin number")
.as_str()
.parse::<u8>()
.expect("Could not parse pin number to u8");
(port, pin_number)
}
impl Ord for Pin {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let (port_a, pin_number_a) = extract_port_and_pin(&self.pin);
let (port_b, pin_number_b) = extract_port_and_pin(&other.pin);
if port_a != port_b {
port_a.cmp(&port_b)
} else if pin_number_a != pin_number_b {
pin_number_a.cmp(&pin_number_b)
} else {
self.signal.cmp(&other.signal)
}
}
}
impl PartialOrd for Pin {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Interrupt {
pub signal: String,
pub interrupt: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct DmaChannel {
pub signal: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub dma: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub channel: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dmamux: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub request: Option<u8>,
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Interrupt {
pub name: String,
pub number: u8,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct DmaChannels {
pub name: String,
pub dma: String,
pub channel: u8,
#[serde(skip_serializing_if = "Option::is_none")]
pub dmamux: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dmamux_channel: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub supports_2d: Option<bool>,
}
}
}
#[cfg(test)]
mod tests {
use std::borrow::Cow;
use std::path::Path;
use std::{fs, str};
use super::*;
fn normalize_line(line: &str) -> Cow<'_, str> {
// The python script saves with 4 spaces instead of 2
let line = line.trim_start();
// The python script escapes unicode
let mut line = Cow::Borrowed(line);
for symbol in [("\\u00ae", "\u{00ae}"), ("\\u2122", "\u{2122}")] {
if line.contains(symbol.0) {
line = Cow::Owned(line.replace(symbol.0, symbol.1));
}
}
line
}
fn normalize(file: &[u8]) -> impl Iterator<Item = Cow<'_, str>> + '_ {
str::from_utf8(file).unwrap().lines().map(normalize_line)
}
fn check_file(path: impl AsRef<Path>) {
println!("Checking {:?}", path.as_ref());
let original = fs::read(path).unwrap();
let parsed: Chip = serde_json::from_slice(&original).unwrap();
let reencoded = serde_json::to_vec_pretty(&parsed).unwrap();
itertools::assert_equal(normalize(&original), normalize(&reencoded))
}
const CHIPS_DIR: &str = "../build/data/chips/";
#[test]
fn test_one() {
let path = Path::new(CHIPS_DIR).join("STM32F030C6.json");
check_file(path);
}
#[test]
fn test_all() {
use rayon::prelude::*;
Path::new(CHIPS_DIR).read_dir().unwrap().par_bridge().for_each(|chip| {
check_file(chip.unwrap().path());
});
}
}