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.
This commit is contained in:
bunnie 2023-07-17 16:27:09 +08:00
parent cde56555d4
commit 069edbc255
9 changed files with 159 additions and 64 deletions

View file

@ -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<T: BufRead>(
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 <memoryRegions>: {:?}", e),
},
Ok(Event::End(ref e)) => match e.name() {
@ -525,6 +535,14 @@ fn generate_constants<T: BufRead>(
_ => 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 <constants>: {:?}", e),
@ -1014,31 +1032,34 @@ mod tests {
Ok(())
}
pub fn parse_svd<P: AsRef<Path>>(src: P) -> Result<Description, ParseError> {
let mut buf = Vec::new();
let mut reader = Reader::from_file(src).expect("couldn't open SVD for reading");
pub fn parse_svd<T: Read>(sources: Vec::<T>) -> Result<Description, ParseError> {
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<T: AsRef<Path>, U: Write>(src: T, dest: &mut U) -> Result<(), ParseError> {
pub fn generate<T: Read, U: Write>(src: Vec::<T>, dest: &mut U) -> Result<(), ParseError> {
let description = parse_svd(src)?;
print_header(dest).or(Err(ParseError::WriteError))?;

View file

@ -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();
}
}

View file

@ -11,8 +11,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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(())
}

View file

@ -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::<MemoryRegion>::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(&region.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);
}

View file

@ -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,

6
utralib/Cargo.lock generated
View file

@ -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",
]

View file

@ -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")]
{

View file

@ -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::<String>,
) -> Result<PathBuf, DynError> {
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);
}
}

View file

@ -486,10 +486,6 @@ Note: By default, the `ticktimer` will get rebuilt every time. You can skip this
type DynError = Box<dyn std::error::Error>;
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<String> {