#![forbid(unsafe_code)]
#![forbid(missing_docs)]
#[cfg(feature = "cli")]
pub mod cli;
#[cfg(feature = "cli")]
use serde::Deserialize;
use std::{
mem::size_of,
path::PathBuf,
io::{self, Read, Write},
result,
fmt::UpperHex,
str::FromStr,
};
use num_traits::{
sign::Unsigned, int::PrimInt, cast::FromPrimitive,
ops::{checked::CheckedShl, wrapping::WrappingSub},
};
use byteorder::{LE, BE, ReadBytesExt};
use thiserror::Error;
use First::{Lsb, Msb};
use Error::*;
type Result<T> = result::Result<T, Error>;
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum Error {
#[error("Valid values are `lsb` and `msb`")]
NeitherLsbNorMsbFirst,
#[error("Width {0} out of [1, {1}]")]
WidthOutOfRange(usize, usize),
#[error("Word at depth {0} out of width {1}")]
ValueOutOfWidth(usize, usize),
#[error("Missing {0} words")]
MissingWords(usize),
#[error(transparent)]
IoError(#[from] io::Error),
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct Mif<T: UpperHex + Unsigned + PrimInt + FromPrimitive> {
width: usize,
depth: usize,
words: Vec<(T, usize)>,
areas: Vec<(usize, PathBuf)>,
}
impl<T> Mif<T>
where
T: UpperHex + Unsigned + PrimInt + FromPrimitive + CheckedShl + WrappingSub,
{
pub fn new(width: usize) -> Result<Mif<T>> {
if (1..=Self::max_width()).contains(&width) {
Ok(Mif { words: Vec::new(), depth: 0, areas: Vec::new(), width })
} else {
Err(WidthOutOfRange(width, Self::max_width()))
}
}
pub fn max_width() -> usize {
Self::max_align() * 8
}
pub fn max_align() -> usize {
size_of::<T>()
}
pub fn max_value(&self) -> T {
T::one().checked_shl(self.width as u32)
.unwrap_or(T::zero()).wrapping_sub(&T::one())
}
pub fn width(&self) -> usize {
self.width
}
pub fn align(&self) -> usize {
(self.width as f64 / 8.0).ceil() as usize
}
pub fn depth(&self) -> usize {
self.depth
}
pub fn words(&self) -> &Vec<(T, usize)> {
&self.words
}
pub fn areas(&self) -> &Vec<(usize, PathBuf)> {
&self.areas
}
pub fn area(&mut self, area: PathBuf) {
self.areas.push((self.depth, area));
}
pub fn push(&mut self, word: T, bulk: usize) -> Result<()> {
match self.words.last_mut() {
Some((last_word, last_bulk)) if *last_word == word =>
*last_bulk += bulk,
_ => {
if word > self.max_value() {
Err(ValueOutOfWidth(self.depth, self.width()))?;
}
if bulk > 0 {
self.words.push((word, bulk))
}
},
}
self.depth += bulk;
Ok(())
}
pub fn join(&mut self, other: &Self) -> Result<()> {
other.words.iter().try_for_each(|&(word, bulk)| self.push(word, bulk))
}
pub fn read(&mut self, bytes: &mut dyn Read, depth: usize, first: First)
-> Result<()> {
let align = self.align();
let mut words = 0;
for _ in 0..depth {
let word = match first {
Lsb => bytes.read_uint128::<LE>(align),
Msb => bytes.read_uint128::<BE>(align),
}?;
self.push(T::from_u128(word)
.ok_or(ValueOutOfWidth(words, self.width))?, 1)?;
words += 1;
}
if depth != words {
Err(MissingWords(depth - words))?;
}
Ok(())
}
pub fn write(&self, lines: &mut dyn Write, areas: bool) -> Result<()> {
let addr_pads = (self.depth as f64).log(16.0).ceil() as usize;
let word_pads = (self.width as f64 / 4.0).ceil() as usize;
if areas && !self.areas.is_empty() {
for (addr, path) in &self.areas {
writeln!(lines, "-- {:02$X}: {}",
addr, path.display(), addr_pads)?;
}
writeln!(lines)?;
}
writeln!(lines, "\
WIDTH={};\n\
DEPTH={};\n\
\n\
ADDRESS_RADIX=HEX;\n\
DATA_RADIX=HEX;\n\
\n\
CONTENT BEGIN", self.width, self.depth)?;
let mut addr = 0;
for &(word, bulk) in &self.words {
if bulk == 1 {
writeln!(lines, "\t{:02$X} : {:03$X};",
addr, word, addr_pads, word_pads)?;
} else {
writeln!(lines, "\t[{:03$X}..{:03$X}] : {:04$X};",
addr, addr + bulk - 1, word, addr_pads, word_pads)?;
}
addr += bulk;
}
writeln!(lines, "END;")?;
Ok(())
}
}
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
#[cfg_attr(feature = "cli", derive(Deserialize))]
#[cfg_attr(feature = "cli", serde(rename_all = "kebab-case"))]
pub enum First {
Lsb,
Msb,
}
impl Default for First {
fn default() -> Self { Lsb }
}
impl FromStr for First {
type Err = Error;
fn from_str(from: &str) -> Result<Self> {
match from {
"lsb" => Ok(Lsb),
"msb" => Ok(Msb),
_ => Err(NeitherLsbNorMsbFirst),
}
}
}
pub const fn default_width() -> usize { 16 }