From 069edbc2559147f306e40ba5e4617de5a4ff1d64 Mon Sep 17 00:00:00 2001 From: bunnie Date: Mon, 17 Jul 2023 16:27:09 +0800 Subject: [PATCH] support multi-svd file designs This patch set goes across all the toolchains and brings in the support for split SVD descriptions of SoCs. This is helpful for dealing with designs that have multiple "top" files for the same core IP set. For exmaple, you may have a SoC that you are developing that targets silicon, but you have a version that wraps this in additional peripherals available only in an FPGA emulator that are used for debugging. In this case, you can have one SVD file be the description of the SoC itself, and another SVD file describe just the "delta" introduced by the FPGA wrapper that emulates/controls the SoC. --- svd2utra/src/generate.rs | 65 +++++++++++++++++++---------- svd2utra/src/lib.rs | 2 +- svd2utra/src/main.rs | 3 +- tools/src/bin/create-image.rs | 77 +++++++++++++++++++++++++++++++---- tools/src/tags/memory.rs | 8 ++-- utralib/Cargo.lock | 6 ++- utralib/build.rs | 31 ++++++++------ xtask/src/builder.rs | 27 ++++++++---- xtask/src/main.rs | 4 -- 9 files changed, 159 insertions(+), 64 deletions(-) diff --git a/svd2utra/src/generate.rs b/svd2utra/src/generate.rs index e87e4faf2..37f493341 100644 --- a/svd2utra/src/generate.rs +++ b/svd2utra/src/generate.rs @@ -5,8 +5,7 @@ use quick_xml::events::{attributes::Attribute, Event}; use quick_xml::reader::Reader; use quick_xml::name::QName; -use std::io::{BufRead, Write}; -use std::path::Path; +use std::io::{BufRead, BufReader, Read, Write}; #[derive(Debug)] pub enum ParseError { @@ -479,9 +478,20 @@ fn parse_memory_regions( loop { match reader.read_event_into(&mut buf) { Ok(Event::Start(ref e)) => match e.name() { - QName(b"memoryRegion") => description + QName(b"memoryRegion") => { + let mut mr = generate_memory_region(reader)?; + // keep adding _ to the end of the name until it's unique + loop { + if description.memory_regions.iter().find(|&m| m.name == mr.name).is_some() { + mr.name.push_str("_"); + continue; + } + break; + } + description .memory_regions - .push(generate_memory_region(reader)?), + .push(mr) + }, _ => panic!("unexpected tag in : {:?}", e), }, Ok(Event::End(ref e)) => match e.name() { @@ -525,6 +535,14 @@ fn generate_constants( _ => panic!("unexpected value in constant: {:?}", maybe_att), } } + // keep adding _ to the end of the name until it's unique + loop { + if description.constants.iter().find(|&c| c.name == constant_descriptor.name).is_some() { + constant_descriptor.name.push_str("_"); + continue; + } + break; + } description.constants.push(constant_descriptor) } _ => panic!("unexpected tag in : {:?}", e), @@ -1014,31 +1032,34 @@ mod tests { Ok(()) } -pub fn parse_svd>(src: P) -> Result { - let mut buf = Vec::new(); - let mut reader = Reader::from_file(src).expect("couldn't open SVD for reading"); +pub fn parse_svd(sources: Vec::) -> Result { let mut description = Description::default(); - loop { - match reader.read_event_into(&mut buf) { - Ok(Event::Start(ref e)) => match e.name() { - QName(b"peripherals") => { - description.peripherals = generate_peripherals(&mut reader)?; - } - QName(b"vendorExtensions") => { - parse_vendor_extensions(&mut reader, &mut description)?; - } + for src in sources { + let mut buf = Vec::new(); + let buf_reader = BufReader::new(src); + let mut reader = Reader::from_reader(buf_reader); + loop { + match reader.read_event_into(&mut buf) { + Ok(Event::Start(ref e)) => match e.name() { + QName(b"peripherals") => { + description.peripherals = generate_peripherals(&mut reader)?; + } + QName(b"vendorExtensions") => { + parse_vendor_extensions(&mut reader, &mut description)?; + } + _ => (), + }, + Ok(Event::Eof) => break, + Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e), _ => (), - }, - Ok(Event::Eof) => break, - Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e), - _ => (), + } + buf.clear(); } - buf.clear(); } Ok(description) } -pub fn generate, U: Write>(src: T, dest: &mut U) -> Result<(), ParseError> { +pub fn generate(src: Vec::, dest: &mut U) -> Result<(), ParseError> { let description = parse_svd(src)?; print_header(dest).or(Err(ParseError::WriteError))?; diff --git a/svd2utra/src/lib.rs b/svd2utra/src/lib.rs index 153db84a1..590be9840 100644 --- a/svd2utra/src/lib.rs +++ b/svd2utra/src/lib.rs @@ -13,6 +13,6 @@ mod tests { fn basic_generate() { DirBuilder::new().recursive(true).create("target").unwrap(); let mut dest = File::create("target/example.rs").unwrap(); - generate("examples/soc.svd", &mut dest).unwrap(); + generate(vec![std::fs::File::open("examples/soc.svd").unwrap()], &mut dest).unwrap(); } } diff --git a/svd2utra/src/main.rs b/svd2utra/src/main.rs index c32bcc0ec..5e40040bb 100644 --- a/svd2utra/src/main.rs +++ b/svd2utra/src/main.rs @@ -11,8 +11,9 @@ fn main() -> Result<(), Box> { let generated_filename = std::env::args().nth(2).ok_or("Must specify destination utralib filename")?; let mut dest_file = std::fs::File::create(generated_filename).expect("couldn't open dest file"); + let src_file = std::fs::File::open(svd_filename).expect("couldn't open source file"); - svd2utra::generate(svd_filename, &mut dest_file)?; + svd2utra::generate(vec![src_file], &mut dest_file)?; Ok(()) } diff --git a/tools/src/bin/create-image.rs b/tools/src/bin/create-image.rs index f127690bf..1c2bb2aa4 100644 --- a/tools/src/bin/create-image.rs +++ b/tools/src/bin/create-image.rs @@ -31,9 +31,12 @@ fn csr_to_config(hv: tools::utils::CsrConfig, ram_config: &mut RamConfig) { fn round_mem(src: u32) -> u32 { (src + 4095) & !4095 } - // Look for the largest "ram" block, which we'll treat as main memory + // Look for the largest memory block, which we'll treat as main memory for (k, v) in &hv.regions { - if k.find("ram").is_some() && v.length > ram_config.size { + if ( + k.find("sram").is_some() // uniquely finds region on Precursor and Cramium (excludes 'reram' correctly) + || k.find("ddr_ram").is_some() // finds region on atsama5d27 uniquely + ) && v.length > ram_config.size { ram_config.size = round_mem(v.length); ram_config.offset = v.start; found_ram_name = Some(k.clone()); @@ -41,12 +44,13 @@ fn csr_to_config(hv: tools::utils::CsrConfig, ram_config: &mut RamConfig) { } if found_ram_name.is_none() { - eprintln!("Error: Couldn't find a memory region named \"ram\" in config file"); + eprintln!("Error: Couldn't find a memory region named \"sram\" in config file"); return; } // Now that we know which block is ram, add the other regions. let found_ram_name = MemoryRegion::make_name(&found_ram_name.unwrap()); + let mut raw_regions = Vec::::new(); for (k, v) in &hv.regions { ram_config.memory_required += round_mem(v.length) / 4096; let region_name = MemoryRegion::make_name(k); @@ -59,11 +63,22 @@ fn csr_to_config(hv: tools::utils::CsrConfig, ram_config: &mut RamConfig) { if round_mem(v.length) == 0 { continue; } - ram_config - .regions - .add(MemoryRegion::new(v.start, round_mem(v.length), region_name)); + raw_regions.push(MemoryRegion::new(v.start, round_mem(v.length), region_name)); } + // condense adjacent regions & eliminate overlapping regions + raw_regions.sort_by(|a, b| a.start.partial_cmp(&b.start).unwrap() ); + let mut candidate_region = raw_regions[0]; + for r in raw_regions[1..].iter() { + if r.start > candidate_region.start + candidate_region.length { + ram_config.regions.add(candidate_region); + candidate_region = r.to_owned(); + } else { + candidate_region.length = (r.start + r.length) - candidate_region.start; + } + } + ram_config.regions.add(candidate_region); } + fn main() { env_logger::init(); let matches = App::new("Xous Image Creator") @@ -113,10 +128,19 @@ fn main() { .short("s") .long("svd") .value_name("SOC_SVD") - .help("soc.csv file from litex") + .help("soc.svd file from litex (system-level)") .takes_value(true) .required_unless_one(&["ram", "svd", "csv"]), ) + .arg( + Arg::with_name("extra-svd") + .long("extra-svd") + .value_name("EXTRA_SVD") + .help("extra SVD files") + .takes_value(true) + .multiple(true) + .number_of_values(1) + ) .arg( Arg::with_name("ram") .short("r") @@ -184,7 +208,8 @@ fn main() { if let Some(soc_svd) = matches.value_of("svd") { let soc_svd_file = std::path::Path::new(soc_svd); - let desc = svd2utra::parse_svd(soc_svd_file).unwrap(); + let svd_file = std::fs::File::open(soc_svd_file).unwrap(); + let desc = svd2utra::parse_svd(vec![svd_file]).unwrap(); let mut map = std::collections::BTreeMap::new(); let mut csr_top = 0; @@ -224,6 +249,42 @@ fn main() { ); } } + // insert additional regions from core.svd, if specified + if matches.is_present("extra-svd") { + let files: Vec<_> = matches.values_of("extra-svd").unwrap().collect(); + for soc_svd in files { + let soc_svd_file = std::path::Path::new(soc_svd); + let svd_file = std::fs::File::open(soc_svd_file).unwrap(); + let desc = svd2utra::parse_svd(vec![svd_file]).unwrap(); + + let mut csr_top = 0; + for peripheral in desc.peripherals { + if peripheral.base > csr_top { + csr_top = peripheral.base; + } + } + for mut region in desc.memory_regions { + loop { + if map.contains_key(®ion.name.to_lowercase()) { + let mut new_name = region.name.to_lowercase(); + new_name.push_str("_"); + region.name = new_name; + continue; + } + break; + } + println!("{}: {:x}", region.name, region.base); + map.insert( + region.name.to_lowercase(), + tools::utils::CsrMemoryRegion { + start: region.base.try_into().unwrap(), + length: region.size.try_into().unwrap(), + }, + ); + } + } + } + csr_to_config(tools::utils::CsrConfig { regions: map }, &mut ram_config); } diff --git a/tools/src/tags/memory.rs b/tools/src/tags/memory.rs index 2d8d531a1..18d0e96e0 100644 --- a/tools/src/tags/memory.rs +++ b/tools/src/tags/memory.rs @@ -11,16 +11,16 @@ macro_rules! make_type { }}; } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone, Copy)] pub struct MemoryRegion { /// Starting offset (in bytes) - start: u32, + pub start: u32, /// Length (in bytes) - length: u32, + pub length: u32, /// Region name (as a type) - name: XousArgumentCode, + pub name: XousArgumentCode, /// Unused _padding: u32, diff --git a/utralib/Cargo.lock b/utralib/Cargo.lock index eadecc703..3ce078c68 100644 --- a/utralib/Cargo.lock +++ b/utralib/Cargo.lock @@ -19,14 +19,16 @@ dependencies = [ [[package]] name = "svd2utra" -version = "0.1.17" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ba87b81de81a553ec5904993a676aa29d1583a4f6ab96e03f8b44e4d6c05a61" dependencies = [ "quick-xml", ] [[package]] name = "utralib" -version = "0.1.19" +version = "0.1.20" dependencies = [ "svd2utra", ] diff --git a/utralib/build.rs b/utralib/build.rs index a22661260..172e88407 100644 --- a/utralib/build.rs +++ b/utralib/build.rs @@ -96,27 +96,27 @@ fn main() { // ----- select an SVD file based on a specific revision ----- #[cfg(feature = "precursor-perflib")] - let svd_filename = "precursor/soc-perf.svd"; + let svd_filenames = vec!["precursor/soc-perf.svd"]; #[cfg(feature = "precursor-perflib")] let generated_filename = "src/generated/precursor_perf.rs"; #[cfg(feature = "renode")] - let svd_filename = "renode/renode.svd"; + let svd_filenames = vec!["renode/renode.svd"]; #[cfg(feature = "renode")] let generated_filename = "src/generated/renode.rs"; #[cfg(feature = "precursor-dvt")] - let svd_filename = "precursor/soc-dvt.svd"; + let svd_filenames = vec!["precursor/soc-dvt.svd"]; #[cfg(feature = "precursor-dvt")] let generated_filename = "src/generated/precursor_dvt.rs"; #[cfg(feature = "precursor-pvt")] - let svd_filename = "precursor/soc-pvt.svd"; + let svd_filenames = vec!["precursor/soc-pvt.svd"]; #[cfg(feature = "precursor")] let generated_filename = "src/generated/precursor_pvt.rs"; #[cfg(feature = "atsama5d27")] - let svd_filename = "atsama5d/ATSAMA5D27.svd"; + let svd_filenames = vec!["atsama5d/ATSAMA5D27.svd"]; #[cfg(feature = "atsama5d27")] let generated_filename = "src/generated/atsama5d27.rs"; @@ -128,15 +128,18 @@ fn main() { // $env:CARGO_LOG="cargo::core::compiler::fingerprint=info" #[cfg(not(feature = "hosted"))] { - let svd_file_path = std::path::Path::new(&svd_filename); - println!( - "cargo:rerun-if-changed={}", - svd_file_path.canonicalize().unwrap().display() - ); - + let mut svd_files = Vec::new(); + for svd in svd_filenames.iter() { + let svd_file_path = std::path::Path::new(&svd); + println!( + "cargo:rerun-if-changed={}", + svd_file_path.canonicalize().unwrap().display() + ); + svd_files.push(std::fs::File::open(svd_file_path).unwrap()); + } // Regenerate the utra file in RAM. let mut dest_vec = vec![]; - svd2utra::generate(svd_file_path, &mut dest_vec).unwrap(); + svd2utra::generate(svd_files, &mut dest_vec).unwrap(); // If the file exists, check to see if it is different from what we just generated. // If not, skip writing the new file. @@ -171,7 +174,9 @@ fn main() { .truncate(true) .open(svd_path) .unwrap(); - write!(svd_file, "utralib/{}", svd_filename).unwrap(); + for svd in svd_filenames.iter() { + writeln!(svd_file, "utralib/{}", svd).unwrap(); + } } #[cfg(feature = "hosted")] { diff --git a/xtask/src/builder.rs b/xtask/src/builder.rs index 7a98bd0d2..76c39a5f1 100644 --- a/xtask/src/builder.rs +++ b/xtask/src/builder.rs @@ -5,7 +5,7 @@ use std::{ path::{Path, PathBuf}, process::Command, }; -use crate::{DynError, app_manifest::generate_app_menus, MemorySpec}; +use crate::{DynError, app_manifest::generate_app_menus}; #[allow(dead_code)] #[derive(Copy, Clone, Debug)] @@ -674,8 +674,12 @@ impl Builder { let mut svd_spec_file = OpenOptions::new() .read(true) .open(&svd_spec_path)?; - let mut svd_path = String::new(); - svd_spec_file.read_to_string(&mut svd_path)?; + let mut svd_path_str = String::new(); + svd_spec_file.read_to_string(&mut svd_path_str)?; + let mut svd_paths = Vec::new(); + for line in svd_path_str.lines() { + svd_paths.push(line.to_owned()); + } // ---------- install any pre-built packages ---------- services_path.append(&mut self.fetch_prebuilds()?); @@ -687,7 +691,7 @@ impl Builder { &kernel_path[0], &inie, &inif, - MemorySpec::SvdFile(svd_path) + svd_paths, )?; println!(); println!( @@ -780,7 +784,7 @@ impl Builder { kernel: &String, init: &[String], inif: &[String], - memory_spec: MemorySpec, + memory_spec: Vec::, ) -> Result { let stream = self.stream.to_str(); let mut args = vec!["run", "--package", "tools", "--bin", "create-image", "--"]; @@ -805,10 +809,15 @@ impl Builder { args.push(i); } - match memory_spec { - MemorySpec::SvdFile(ref s) => { - args.push("--svd"); - args.push(s); + if memory_spec.len() == 1 { + args.push("--svd"); + args.push(&memory_spec[0]) + } else { + args.push("--svd"); + args.push(&memory_spec[0]); + for spec in memory_spec[1..].iter() { + args.push("--extra-svd"); + args.push(&spec); } } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index c5dece11c..2071304c0 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -486,10 +486,6 @@ Note: By default, the `ticktimer` will get rebuilt every time. You can skip this type DynError = Box; -enum MemorySpec { - SvdFile(String), -} - /// [cratespecs] are positional arguments, and is a list of 0 to N tokens that immediately /// follow [verb] fn get_cratespecs() -> Vec {