use svd_parser::svd::{Access, ModifiedWriteValues, ReadAction};
use crate::svd::GeneratedFiles;
use crate::svd::model::{Field, Instance, Model, Register};
const HEX_DIGITS_PER_GROUP: usize = 4;
const BITS_PER_HEX_DIGIT: usize = 4;
const ADDRESS_HEX_DIGITS: usize = 8;
const KEYWORDS: [&str; 52] = [
"abstract", "as", "async", "await", "become", "box", "break", "const", "continue", "crate",
"do", "dyn", "else", "enum", "extern", "false", "final", "fn", "for", "gen", "if", "impl",
"in", "let", "loop", "macro", "match", "mod", "move", "mut", "override", "priv", "pub", "ref",
"return", "self", "static", "struct", "super", "trait", "true", "try", "type", "typeof",
"unsafe", "unsized", "use", "virtual", "where", "while", "yield", "Self",
];
pub(crate) fn emit(
model: &Model,
source_name: &str,
digest: &str,
notice: &[String],
) -> GeneratedFiles {
let provenance = std::iter::once(format!(
"// rigless svd from {source_name}, sha256 {digest}. Do not edit."
))
.chain(notice.iter().map(|line| format!("// {line}")))
.collect::<Vec<_>>()
.join("\n");
let mut files = GeneratedFiles::new();
files.insert(
"generated.rs".to_owned(),
instances_file(model, source_name, &provenance),
);
for (layout, registers) in &model.layouts {
files.insert(
format!("generated/{}.rs", module_name(layout)),
layout_file(layout, registers, &provenance),
);
}
files
}
pub(crate) fn module_name(name: &str) -> String {
keyword_safe(identifier(name).to_lowercase())
}
pub(crate) fn static_name(name: &str) -> String {
identifier(name).to_uppercase()
}
pub(crate) fn variant_name(name: &str) -> String {
let identifier = identifier(name);
let mut variant = String::new();
for word in identifier.split('_').filter(|word| !word.is_empty()) {
let mut letters = word.chars();
if let Some(first) = letters.next() {
variant.extend(first.to_uppercase());
variant.extend(letters.flat_map(char::to_lowercase));
}
}
if variant.starts_with(|first: char| first.is_ascii_digit()) {
variant.insert(0, 'R');
}
if identifier.ends_with('_') {
variant.push('_');
}
keyword_safe(variant)
}
fn identifier(name: &str) -> String {
let mut identifier: String = name
.chars()
.map(|letter| {
if letter.is_ascii_alphanumeric() {
letter
} else {
'_'
}
})
.collect();
if identifier.starts_with(|first: char| first.is_ascii_digit()) {
identifier.insert(0, '_');
}
identifier
}
fn keyword_safe(mut identifier: String) -> String {
if KEYWORDS.contains(&identifier.as_str()) {
identifier.push('_');
}
identifier
}
fn instances_file(model: &Model, source_name: &str, provenance: &str) -> String {
let mut lines = vec![
provenance.to_owned(),
format!(
"//! Every peripheral instance of `{}` from `{source_name}`.",
model.device_name
),
String::new(),
"use rigless::soc::{Cpu, Endian, Peripheral};".to_owned(),
String::new(),
];
lines.extend(
model
.layouts
.keys()
.map(|layout| format!("pub mod {};", module_name(layout))),
);
lines.push(String::new());
lines.push(if model.cpu.interrupt_lines_stated {
"/// The CPU as the SVD describes it.".to_owned()
} else {
"/// The CPU as the SVD describes it. The SVD names no interrupt count, so this is the highest line it names plus one.".to_owned()
});
lines.push(format!(
"pub static CPU: Cpu = Cpu::new(\"{}\", {}, {}, Endian::{:?});",
model.cpu.name, model.cpu.fpu, model.cpu.interrupt_lines, model.cpu.endian
));
lines.push(String::new());
for instance in &model.instances {
lines.extend(instance_lines(instance));
}
let every_instance: Vec<String> = model
.instances
.iter()
.map(|instance| static_name(&instance.name))
.collect();
lines.push(String::new());
lines.push("/// Every instance, by ascending base address.".to_owned());
lines.push(format!(
"pub static PERIPHERALS: [Peripheral; {}] = [{}];",
model.instances.len(),
every_instance.join(", ")
));
lines.push(String::new());
lines.join("\n")
}
fn instance_lines(instance: &Instance) -> [String; 2] {
let lines: Vec<String> = instance.interrupts.iter().map(u16::to_string).collect();
let interrupts = if lines.is_empty() {
String::new()
} else {
format!(", interrupts {}", lines.join(", "))
};
[
format!(
"/// `{}` at `{}`, layout `{}`{interrupts}.",
instance.name,
hex_literal(instance.base, ADDRESS_HEX_DIGITS),
module_name(&instance.layout)
),
format!(
"pub static {}: Peripheral = Peripheral {{ name: \"{}\", base: {}, size: {}, interrupts: &[{}], registers: &{}::REGISTERS }};",
static_name(&instance.name),
instance.name,
hex_literal(instance.base, ADDRESS_HEX_DIGITS),
hex_literal(instance.size, 0),
lines.join(", "),
module_name(&instance.layout)
),
]
}
fn layout_file(layout: &str, registers: &[Register], provenance: &str) -> String {
let has_fields = registers.iter().any(|register| !register.fields.is_empty());
let field_import = if has_fields { "FieldSpec, " } else { "" };
let mut lines = vec![
provenance.to_owned(),
format!("//! Registers of `{layout}` and the instances derived from it."),
String::new(),
format!(
"use rigless::soc::{{Access, {field_import}Layout, ReadAction, RegisterSpec, Width, WriteModify}};"
),
String::new(),
];
lines.extend(register_enum_lines(registers));
for (row, register) in registers
.iter()
.enumerate()
.filter(|(_, register)| !register.fields.is_empty())
{
lines.push(String::new());
lines.extend(field_module_lines(row, register));
}
lines.push(String::new());
lines.extend(table_lines(registers));
lines.push(String::new());
lines.join("\n")
}
fn register_enum_lines(registers: &[Register]) -> Vec<String> {
let variants: Vec<String> = registers
.iter()
.map(|register| format!("Self::{}", variant_name(®ister.name)))
.collect();
let mut lines = vec![
"/// The registers by name, where a variant's index is its row in [`REGISTERS`]."
.to_owned(),
"#[derive(Debug, Clone, Copy, PartialEq, Eq)]".to_owned(),
"pub enum Register {".to_owned(),
];
for (row, register) in registers.iter().enumerate() {
lines.push(format!(" /// {}", register_summary(register)));
lines.push(format!(" {} = {row},", variant_name(®ister.name)));
}
lines.push("}".to_owned());
lines.push(String::new());
lines.push("impl Register {".to_owned());
lines.push(" /// Every register, in table order.".to_owned());
lines.push(format!(
" pub const ALL: [Self; {}] = [{}];",
registers.len(),
variants.join(", ")
));
lines.push("}".to_owned());
lines.push(String::new());
lines.push("impl Layout for Register {".to_owned());
lines.push(" const REGISTERS: &'static [RegisterSpec] = ®ISTERS;".to_owned());
lines.push(" fn from_index(index: usize) -> Option<Self> {".to_owned());
lines.push(" Self::ALL.get(index).copied()".to_owned());
lines.push(" }".to_owned());
lines.push(" fn index(self) -> usize {".to_owned());
lines.push(" self as usize".to_owned());
lines.push(" }".to_owned());
lines.push("}".to_owned());
lines
}
fn register_summary(register: &Register) -> String {
let mut summary = format!(
"`{}` at offset `{}`, resets to `{}`, {}",
register.name,
hex_literal(register.offset.into(), 0),
hex_literal(register.reset, register.bits as usize / BITS_PER_HEX_DIGIT),
access_words(register.access)
);
if let Some(read) = register.read {
summary.push_str(", ");
summary.push_str(read_words(read));
}
if let Some(write) = register.write {
summary.push_str(", ");
summary.push_str(write_words(write));
}
summary.push('.');
summary
}
fn field_module_lines(row: usize, register: &Register) -> Vec<String> {
let mut lines = vec![
format!("/// The fields of `{}`.", register.name),
format!("pub mod {} {{", module_name(®ister.name)),
" use super::FieldSpec;".to_owned(),
];
for (position, field) in register.fields.iter().enumerate() {
lines.push(format!(" /// {}", field_summary(field, register)));
lines.push(format!(
" pub const {}: &FieldSpec = &super::REGISTERS[{row}].fields[{position}];",
static_name(&field.name)
));
}
lines.push("}".to_owned());
lines
}
fn field_summary(field: &Field, register: &Register) -> String {
let last_bit = field.bit + field.width - 1;
let mut summary = if field.width == 1 {
format!("`{}`, bit {}", field.name, field.bit)
} else {
format!("`{}`, bits {}..={last_bit}", field.name, field.bit)
};
if field.access != register.access {
summary.push_str(", ");
summary.push_str(access_words(field.access));
}
if field.read != register.read {
summary.push_str(", ");
summary.push_str(field.read.map_or("no read action", read_words));
}
if field.write != register.write {
summary.push_str(", ");
summary.push_str(field.write.map_or("plain write", write_words));
}
summary.push('.');
summary
}
fn table_lines(registers: &[Register]) -> Vec<String> {
let mut lines = vec![
"/// The register table.".to_owned(),
format!(
"pub static REGISTERS: [RegisterSpec; {}] = [",
registers.len()
),
];
for register in registers {
let row = format!(
" RegisterSpec {{ name: \"{}\", offset: {}, width: Width::W{}, reset: {}, access: {}, read: {}, write: {}, fields: &[",
register.name,
hex_literal(register.offset.into(), 0),
register.bits,
hex_literal(register.reset, register.bits as usize / BITS_PER_HEX_DIGIT),
access_variant(register.access),
read_variant(register.read),
write_variant(register.write)
);
if register.fields.is_empty() {
lines.push(format!("{row}] }},"));
continue;
}
lines.push(row);
lines.extend(register.fields.iter().map(field_row));
lines.push(" ] },".to_owned());
}
lines.push("];".to_owned());
lines
}
fn field_row(field: &Field) -> String {
format!(
" FieldSpec {{ name: \"{}\", bit: {}, width: {}, access: {}, read: {}, write: {} }},",
field.name,
field.bit,
field.width,
access_variant(field.access),
read_variant(field.read),
write_variant(field.write)
)
}
fn access_variant(access: Access) -> &'static str {
match access {
Access::ReadOnly => "Access::ReadOnly",
Access::WriteOnly => "Access::WriteOnly",
Access::ReadWrite => "Access::ReadWrite",
Access::WriteOnce => "Access::WriteOnce",
Access::ReadWriteOnce => "Access::ReadWriteOnce",
}
}
fn access_words(access: Access) -> &'static str {
match access {
Access::ReadOnly => "read-only",
Access::WriteOnly => "write-only",
Access::ReadWrite => "read-write",
Access::WriteOnce => "write-once",
Access::ReadWriteOnce => "read-write-once",
}
}
fn read_variant(read: Option<ReadAction>) -> &'static str {
match read {
None => "ReadAction::None",
Some(ReadAction::Clear) => "ReadAction::Clear",
Some(ReadAction::Set) => "ReadAction::Set",
Some(ReadAction::Modify) => "ReadAction::Modify",
Some(ReadAction::ModifyExternal) => "ReadAction::ModifyExternal",
}
}
fn read_words(read: ReadAction) -> &'static str {
match read {
ReadAction::Clear => "reading clears it",
ReadAction::Set => "reading sets it",
ReadAction::Modify => "reading modifies it",
ReadAction::ModifyExternal => "reading modifies another register",
}
}
fn write_variant(write: Option<ModifiedWriteValues>) -> &'static str {
match write {
None => "WriteModify::None",
Some(ModifiedWriteValues::OneToClear) => "WriteModify::OneToClear",
Some(ModifiedWriteValues::OneToSet) => "WriteModify::OneToSet",
Some(ModifiedWriteValues::OneToToggle) => "WriteModify::OneToToggle",
Some(ModifiedWriteValues::ZeroToClear) => "WriteModify::ZeroToClear",
Some(ModifiedWriteValues::ZeroToSet) => "WriteModify::ZeroToSet",
Some(ModifiedWriteValues::ZeroToToggle) => "WriteModify::ZeroToToggle",
Some(ModifiedWriteValues::Clear) => "WriteModify::Clear",
Some(ModifiedWriteValues::Set) => "WriteModify::Set",
Some(ModifiedWriteValues::Modify) => "WriteModify::Modify",
}
}
fn write_words(write: ModifiedWriteValues) -> &'static str {
match write {
ModifiedWriteValues::OneToClear => "writing one clears",
ModifiedWriteValues::OneToSet => "writing one sets",
ModifiedWriteValues::OneToToggle => "writing one toggles",
ModifiedWriteValues::ZeroToClear => "writing zero clears",
ModifiedWriteValues::ZeroToSet => "writing zero sets",
ModifiedWriteValues::ZeroToToggle => "writing zero toggles",
ModifiedWriteValues::Clear => "any write clears",
ModifiedWriteValues::Set => "any write sets",
ModifiedWriteValues::Modify => "any write modifies",
}
}
fn hex_literal(value: u64, min_digits: usize) -> String {
let digits: Vec<char> = format!("{value:0>min_digits$X}").chars().collect();
let mut literal = String::from("0x");
for (position, digit) in digits.iter().enumerate() {
let remaining = digits.len() - position;
if position > 0 && remaining.is_multiple_of(HEX_DIGITS_PER_GROUP) {
literal.push('_');
}
literal.push(*digit);
}
literal
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hex_literals_group_in_fours_from_the_right() {
assert_eq!(hex_literal(0x0, 0), "0x0");
assert_eq!(hex_literal(0x400, 0), "0x400");
assert_eq!(hex_literal(0x3F200, 0), "0x3_F200");
assert_eq!(hex_literal(0x4002_5000, 8), "0x4002_5000");
assert_eq!(hex_literal(0, 8), "0x0000_0000");
assert_eq!(hex_literal(0xA5, 2), "0xA5");
assert_eq!(hex_literal(0xA5, 4), "0x00A5");
assert_eq!(hex_literal(0x1234_5678_9ABC, 0), "0x1234_5678_9ABC");
}
#[test]
fn names_fold_into_the_three_identifier_shapes() {
assert_eq!(module_name("Link_CTRL"), "link_ctrl");
assert_eq!(module_name("2ND"), "_2nd");
assert_eq!(module_name("LOOP"), "loop_");
assert_eq!(static_name("Port2ext"), "PORT2EXT");
assert_eq!(static_name("RX-FIFO"), "RX_FIFO");
assert_eq!(variant_name("CH0_CFG"), "Ch0Cfg");
assert_eq!(variant_name("STATUS"), "Status");
assert_eq!(variant_name("2ND"), "R2nd");
assert_eq!(variant_name("SELF"), "Self_");
assert_eq!(variant_name("MATCH"), "Match");
assert_eq!(variant_name("CTRL_"), "Ctrl_");
assert_ne!(variant_name("CTRL_"), variant_name("CTRL"));
}
}