From cfbc71f303c67f1fd5a3b3282c2272426e3e286b Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Wed, 4 Jun 2025 22:02:26 +0800 Subject: [PATCH] add hspice block analyze --- loadsig/loadsig/src/hspice.rs | 167 ++++++++++++++++++++++++++++++++++ loadsig/loadsig/src/lib.rs | 49 ++++++++++ loadsig/loadsig/src/main.rs | 7 ++ loadsig/loadsig/src/shared.rs | 78 ++++++++++++++++ 4 files changed, 301 insertions(+) create mode 100644 loadsig/loadsig/src/main.rs diff --git a/loadsig/loadsig/src/hspice.rs b/loadsig/loadsig/src/hspice.rs index e69de29..1c9ab91 100644 --- a/loadsig/loadsig/src/hspice.rs +++ b/loadsig/loadsig/src/hspice.rs @@ -0,0 +1,167 @@ +use super::shared::{self, Endian}; +use byteorder::{BigEndian, LittleEndian, NativeEndian, ReadBytesExt}; +use std::io::{Read, Seek, SeekFrom}; +use thiserror::Error as TeError; + +// Reference: +// +// If these URLs are unreachable, use Internet Archive instead. +// +// https://github.com/HMC-ACE/hspiceParser/blob/main/hSpice_output.md +// https://www.rvq.fr/linux/gawfmt.php + +#[derive(Debug, TeError)] +pub enum Error { + #[error("IO error {0}")] + Io(#[from] std::io::Error), + #[error("fail to cast value into integral value")] + CastToInt(#[from] std::num::TryFromIntError), + #[error("wrong endian integer {0:#X} in block head. it must be one of 0x4 or 0x4000000")] + WrongEndianInt(u32), + #[error("wrong block head value {0:#X}. it must be 0x4")] + WrongBlockHead(u32), + #[error("wrong block tail value {0:#X}. it must be equal to head given value {1:#X}")] + WrongBlockTail(u32, u32), +} +type Result = std::result::Result; + +const ENDIAN_INT: u32 = 0x4; +const LITTLE_ENDIAN_INT: u32 = 0x4; +const BIG_ENDIAN_INT: u32 = 0x4000000; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SimSource { + /// For HSPICE, read as big endian. + Hspice, + /// For HSPICE on Windows, read as little endian. + HspiceWin, +} +impl SimSource { + fn to_endian(&self) -> Endian { + match *self { + SimSource::Hspice => Endian::BigEndian, + SimSource::HspiceWin => Endian::LittleEndian, + } + } +} + +#[derive(Debug)] +pub struct HspiceBlock { + pub block_offset: u64, + pub block_len: usize, +} +#[derive(Debug)] +pub struct HspiceBlocks { + pub sim_source: SimSource, + pub blocks: Vec, + pub header_blk_cnt: usize, + pub data_blk_cnt: usize, +} + +/// Guess whether given reader is a legal HSPICE signal file. +/// +/// Please note that this is a vauge check. +/// There is no guarantee that the file with `true` return value of this function, +/// can be loaded successfully. +pub fn guess_signal(reader: &mut T) -> bool +where + T: ReadBytesExt + Seek, +{ + // Back to the head of file. + if let Err(_) = reader.seek(SeekFrom::Start(0)) { + return false; + } + + // Check whether first u32 is 4 (in little or big endian) + match reader.read_u32::() { + Ok(val) => match val { + LITTLE_ENDIAN_INT | BIG_ENDIAN_INT => true, + _ => false, + }, + Err(_) => false, + } +} + +/// Load given reader as HSPICE signal file. +pub fn load_signal(reader: &mut T) -> Result<()> +where + T: ReadBytesExt + Seek, +{ + // Fetch blocks list first + let blocks = analyze_hspice_block(reader)?; + println!("{:#?}", blocks); + + Ok(()) +} + +fn analyze_hspice_block(reader: &mut T) -> Result +where + T: ReadBytesExt + Seek, +{ + // Back to the file head + reader.seek(SeekFrom::Start(0))?; + + // Fetch the first u32 as little endian to decide the sim source + let endian_u32 = reader.read_u32::()?; + let sim_source = match endian_u32 { + LITTLE_ENDIAN_INT => SimSource::HspiceWin, + BIG_ENDIAN_INT => SimSource::Hspice, + other => return Err(Error::WrongEndianInt(other)), + }; + let endian = sim_source.to_endian(); + + // Iterate each block + let mut blocks: Vec = Vec::new(); + reader.seek(SeekFrom::Start(0))?; + loop { + // Read block header. + // If there is no more data, break this loop. + let mut block_header = [0u32; 4]; + if let Err(e) = shared::read_int_values(endian, &mut block_header, reader) { + match e.kind() { + std::io::ErrorKind::UnexpectedEof => break, + _ => return Err(e.into()), + } + } + + // Check endian u32 + if block_header[0] != ENDIAN_INT { + return Err(Error::WrongBlockHead(block_header[0])); + } + if block_header[2] != ENDIAN_INT { + return Err(Error::WrongBlockHead(block_header[2])); + } + + // Get the offset and length of block body + let raw_block_len = block_header[3]; + let block_offset = reader.seek(SeekFrom::Current(0))?; + let block_len = usize::try_from(raw_block_len).unwrap(); + + // Skip block body to check block tail + reader.seek(SeekFrom::Current(i64::try_from(raw_block_len).unwrap()))?; + let block_tail = shared::read_int_value(endian, reader)?; + if block_tail != raw_block_len { + return Err(Error::WrongBlockTail(block_tail, raw_block_len)); + } + + // Push result + blocks.push(HspiceBlock { + block_offset, + block_len, + }); + + // Cursor now at the beginning of next block. + // We can continue analyze next blocks safely. + } + + // TODO: + // Analyze header block count + + // Build result and return. + Ok(HspiceBlocks { + sim_source, + blocks, + header_blk_cnt: 0, + data_blk_cnt: 0, + }) +} diff --git a/loadsig/loadsig/src/lib.rs b/loadsig/loadsig/src/lib.rs index 47254a8..60e0713 100644 --- a/loadsig/loadsig/src/lib.rs +++ b/loadsig/loadsig/src/lib.rs @@ -1,3 +1,52 @@ +/* + * loadsig.c: retrieval of HSPICE data into MATLAB + * written by Michael Perrott while at Silicon + * Laboratories. This code leveraged the work of + * Stephen G. Tell (i.e., his Gwave viewer) to + * develop the Hspice loading functions. At this + * point, the code here has little similarity to the + * routines he provided, but many thanks to Stephen + * for providing enough info to get started. + * For those wishing to modify this code - good luck! + * Unfortunatley, it is quite "hacked" due + * to the fact that the actual binary format of Hspice output + * was never provided (i.e., I simply updated the + * code each time a new issue was found), and due to the + * fact that I had little time to develop it. However, + * after years of use, it's pretty solid now. + * + * To compile this code into a mex file for Matlab, simply + * run Matlab in the directory that loadsig.c is contained + * and then type: + * mex loadsig.c + * You'll then have a loadsig mex function for whatever + * computer (i.e., Sun, Linux, Windows) that you're running + * on at the time. + * + * I do ask for one thing for those that use this code - please + * keep my name and Silicon Labs attached to it when the user + * first executes it (as currently done). I am not particularly + * interested in getting anything for this package other then + * recognition, but I do want that since this was a nontrivial + * amount of work. Of course, if you want to throw money at me, + * I'm happy to accomodate you. :) - Michael Perrott 6/3/03 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the MxFree + * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + pub mod shared; pub mod hspice; pub mod ngspice; diff --git a/loadsig/loadsig/src/main.rs b/loadsig/loadsig/src/main.rs new file mode 100644 index 0000000..78645f5 --- /dev/null +++ b/loadsig/loadsig/src/main.rs @@ -0,0 +1,7 @@ +fn main() { + let args: Vec<_> = std::env::args().collect(); + let mut reader = std::fs::File::open(&args[1]).unwrap(); + loadsig::hspice::load_signal(&mut reader).unwrap_or_else(|e| { + eprintln!("{}", e); + }); +} diff --git a/loadsig/loadsig/src/shared.rs b/loadsig/loadsig/src/shared.rs index e69de29..daf59f5 100644 --- a/loadsig/loadsig/src/shared.rs +++ b/loadsig/loadsig/src/shared.rs @@ -0,0 +1,78 @@ +use byteorder::{BigEndian, LittleEndian, NativeEndian, ReadBytesExt}; +use std::io::Result; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Endian { + NativeEndian, + BigEndian, + LittleEndian, +} + +pub fn read_int_values(endian: Endian, buf: &mut [u32], reader: &mut T) -> Result<()> +where + T: ReadBytesExt, +{ + match endian { + Endian::NativeEndian => reader.read_u32_into::(buf)?, + Endian::BigEndian => reader.read_u32_into::(buf)?, + Endian::LittleEndian => reader.read_u32_into::(buf)?, + } + Ok(()) +} + +pub fn read_int_value(endian: Endian, reader: &mut T) -> Result +where + T: ReadBytesExt, +{ + Ok(match endian { + Endian::NativeEndian => reader.read_u32::()?, + Endian::BigEndian => reader.read_u32::()?, + Endian::LittleEndian => reader.read_u32::()?, + }) +} + +pub fn read_float_values(endian: Endian, buf: &mut [f32], reader: &mut T) -> Result<()> +where + T: ReadBytesExt, +{ + match endian { + Endian::NativeEndian => reader.read_f32_into::(buf)?, + Endian::BigEndian => reader.read_f32_into::(buf)?, + Endian::LittleEndian => reader.read_f32_into::(buf)?, + } + Ok(()) +} + +pub fn read_float_value(endian: Endian, reader: &mut T) -> Result +where + T: ReadBytesExt, +{ + Ok(match endian { + Endian::NativeEndian => reader.read_f32::()?, + Endian::BigEndian => reader.read_f32::()?, + Endian::LittleEndian => reader.read_f32::()?, + }) +} + +pub fn read_double_values(endian: Endian, buf: &mut [f64], reader: &mut T) -> Result<()> +where + T: ReadBytesExt, +{ + match endian { + Endian::NativeEndian => reader.read_f64_into::(buf)?, + Endian::BigEndian => reader.read_f64_into::(buf)?, + Endian::LittleEndian => reader.read_f64_into::(buf)?, + } + Ok(()) +} + +pub fn read_double_value(endian: Endian, reader: &mut T) -> Result +where + T: ReadBytesExt, +{ + Ok(match endian { + Endian::NativeEndian => reader.read_f64::()?, + Endian::BigEndian => reader.read_f64::()?, + Endian::LittleEndian => reader.read_f64::()?, + }) +}