Skip to main content

readmem/
lib.rs

1//! A library for reading in Verilog $readmemb/$readmemh files formatted per
2//! IEEE Std. 1364-2005, ยง17.2.9
3
4use std::str::FromStr;
5
6use pest::{iterators::Pair, Parser};
7use pest_derive::Parser;
8use thiserror::Error;
9
10#[derive(Error, Debug)]
11pub enum Error {
12    #[error("syntax error")]
13    ParseError(#[from] pest::error::Error<Rule>),
14    #[error("we didn't successfully parse a top level syntax object")]
15    ParseFailure,
16    #[error("numeric format error")]
17    NumericFormatError(#[from] std::num::ParseIntError),
18}
19
20/// The type of content contained in a file to be read.
21pub enum ContentType {
22    /// The file is compatible with $readmemh and contains hex values
23    Hex,
24    /// The file is compatible with $readmemb and contains binary values
25    Binary,
26}
27
28#[derive(Parser)]
29#[grammar = "grammar.pest"]
30struct ReadmemParser;
31
32#[derive(Debug)]
33enum Item<I> {
34    Address(usize),
35    Number(I),
36}
37
38/// A trait implemented on unsigned numeric types to allow us to be polymorphic
39/// over them, supporting any storage type.
40#[doc(hidden)]
41pub trait Integral: Sized {
42    fn from_str_radix(src: &str, radix: u32) -> Result<Self, std::num::ParseIntError>;
43    fn zero() -> Self;
44}
45
46macro_rules! integrate {
47    ($t:ty) => {
48        impl Integral for $t {
49            fn from_str_radix(src: &str, radix: u32) -> Result<Self, std::num::ParseIntError> {
50                <$t>::from_str_radix(src, radix)
51            }
52            fn zero() -> Self {
53                0
54            }
55        }
56    };
57}
58
59integrate!(u8);
60integrate!(u16);
61integrate!(u32);
62integrate!(u64);
63integrate!(u128);
64
65/// Parse a `Pair` into an AST `Item`.
66fn parse_value<I>(pair: Pair<Rule>) -> Result<Item<I>, Error>
67where
68    I: Integral,
69{
70    let is_zx = |c| c == 'x' || c == 'z' || c == 'X' || c == 'Z';
71    Ok(match pair.as_rule() {
72        Rule::addr => Item::Address(usize::from_str_radix(&pair.as_str()[1..], 16)?),
73        Rule::hexnum => {
74            let without_underscore = pair.as_str().replace("_", "");
75            let without_zx = without_underscore.replace(is_zx, "0");
76            Item::Number(Integral::from_str_radix(&without_zx, 16)?)
77        }
78        Rule::binnum => {
79            let without_underscore = pair.as_str().replace("_", "");
80            let without_zx = without_underscore.replace(is_zx, "0");
81            Item::Number(Integral::from_str_radix(&without_zx, 2)?)
82        }
83        r => unreachable!(
84            "should not hit this rule {:?}, all our other rules are silent",
85            r
86        ),
87    })
88}
89
90/// Read a Verilog memory file from a string containing its contents into a Vec.
91///
92/// A memory file is composed of items, which are either numeric literals or
93/// `@hh..` hex addresses to restart writing at. All instances of `z` and `x` are
94/// replaced with zeros. If an address directive `@hh..` points past the end of
95/// the memory, the space between where we last wrote and the restart address
96/// will be filled with zeros.
97///
98/// Hex numbers support both upper- and lowercase digits. Both Verilog comment
99/// types `/* ... */` and `// ...` are supported, and so are underscore
100/// separators in numeric literals.
101///
102/// Example:
103///
104/// ```
105/// use readmem::{readmem, ContentType};
106/// assert_eq!(vec![0, 1, 2], readmem::<u8>("0 1\n2", ContentType::Hex).unwrap());
107/// assert_eq!(vec![0, 1, 2], readmem::<u8>("z/* a */x1 1_0", ContentType::Binary).unwrap());
108/// ```
109pub fn readmem<I>(content: &str, content_type: ContentType) -> Result<Vec<I>, Error>
110where
111    I: Integral + FromStr + Clone,
112{
113    let rule = match content_type {
114        ContentType::Hex => Rule::readmemh,
115        ContentType::Binary => Rule::readmemb,
116    };
117    let content = ReadmemParser::parse(rule, content)?;
118
119    let mut result = Vec::new();
120    let mut pos = 0;
121    for val in content {
122        if let Rule::EOI = val.as_rule() {
123            continue;
124        }
125        let val = parse_value::<I>(val)?;
126        match val {
127            Item::Address(a) => pos = a,
128            Item::Number(n) => {
129                if pos + 1 >= result.len() {
130                    result.resize(pos + 1, I::zero());
131                }
132                result[pos] = n;
133                pos += 1;
134            }
135        }
136    }
137    Ok(result)
138}
139
140#[cfg(doctest)]
141mod doctest {
142    use doc_comment::doctest;
143    doctest!("../README.md");
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149    #[test]
150    fn test_hex() {
151        let hex = include_str!("testdata/hex.txt");
152        let arr = readmem::<u8>(hex, ContentType::Hex).unwrap();
153        let expected = 0..255u8;
154        assert_eq!(256, arr.len());
155        for (&item, exp) in arr.iter().zip(expected) {
156            assert_eq!(item, exp);
157        }
158    }
159
160    #[test]
161    fn test_bin() {
162        let bin = include_str!("testdata/bin.txt");
163        let arr = readmem::<u16>(bin, ContentType::Binary).unwrap();
164        assert_eq!(257, arr.len());
165        let expected = 0..256u16;
166        for (&item, exp) in arr.iter().zip(expected) {
167            assert_eq!(item, exp);
168        }
169    }
170
171    #[test]
172    fn test_addressing() {
173        // it's unclear if the Verilog spec allows overwriting previous data but
174        // we do
175        let testdata = r#"
176            f
177            @4
178            1 2 3
179            @4
180            4 5 6
181            @8
182            e
183        "#;
184        let expected = vec![0xf, 0, 0, 0, 4, 5, 6, 0, 0xe];
185        assert_eq!(
186            expected,
187            readmem::<u32>(testdata, ContentType::Hex).unwrap()
188        );
189    }
190}