opentitanlib/util/vmem/
mod.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5//! This module contains code for working with Verilog `VMEM` files.
6//!
7//! This includes the [`Vmem'] representation which can be parsed from a string.
8
9use std::fmt;
10use std::iter;
11
12use thiserror::Error;
13
14mod parser;
15
16use parser::VmemParser;
17pub use parser::{ParseError, ParseResult};
18
19/// Representation of a VMEM file.
20///
21/// These files consist of sections which are runs of memory starting at some address.
22#[derive(Clone, Debug, Default, PartialEq, Eq)]
23pub struct Vmem {
24    sections: Vec<Section>,
25}
26
27/// Section of memory at some address in the vmem file.
28#[derive(Clone, Debug, Default, PartialEq, Eq)]
29pub struct Section {
30    pub addr: u32,
31    pub data: Vec<Word>,
32}
33
34/// A singular word in a VMEM file, with bytes stored in Big Endian (MSB-first).
35#[derive(Clone, Debug, Default, PartialEq, Eq)]
36pub struct Word {
37    pub bytes: Vec<u8>,
38}
39
40impl Word {
41    pub fn new(bytes: Vec<u8>) -> Self {
42        Self { bytes }
43    }
44}
45
46impl Vmem {
47    pub fn new(sections: Vec<Section>) -> Self {
48        Self { sections }
49    }
50
51    /// Parse a complete VMEM file from the contents of a given string.
52    pub fn from_str(s: &str, addr_stride: Option<usize>) -> Result<Self, ParseError> {
53        VmemParser::parse(s, addr_stride)
54    }
55}
56
57impl Vmem {
58    /// Returns an iterator over sections of the VMEM file.
59    pub fn sections(&self) -> impl Iterator<Item = &Section> {
60        // Filter out empty sections.
61        self.sections
62            .iter()
63            .filter(|section| !section.data.is_empty())
64    }
65
66    /// Serialize the VMEM, dumping it to a string.
67    ///
68    /// The `bytes_per_word` parameter can be used to control the zero-extension of words.
69    ///
70    /// If `addr_per_word` is true, then each word will be given its own address on a
71    /// separate line, with a stride of `bytes_per_word` between words (default: 1). If it
72    /// is instead false, an address will only be emitted per each section.
73    pub fn dump(&self, bytes_per_word: Option<usize>, addr_per_word: bool) -> String {
74        let addr_stride = bytes_per_word.unwrap_or(1);
75        let word_width_nibbles = bytes_per_word.map(|b| b * 2);
76        let max_addr = self
77            .sections
78            .iter()
79            .map(|s| s.addr + ((s.data.len() - 1) * addr_stride) as u32)
80            .max();
81        let addr_width = format!("{:x}", max_addr.unwrap_or(0)).len();
82
83        let mut sections: Vec<String> = Vec::new();
84
85        for section in &self.sections {
86            let mut section_str = String::new();
87
88            if !addr_per_word {
89                section_str.push_str(&format!("@{:0addr_width$X} ", section.addr));
90            }
91
92            let word_separator = if addr_per_word { "\n" } else { " " };
93            section_str.push_str(
94                &section
95                    .data
96                    .iter()
97                    .enumerate()
98                    .map(|(index, word)| {
99                        let addr = if addr_per_word {
100                            let addr = section.addr + (index * addr_stride) as u32;
101                            format!("@{:0addr_width$X} ", addr)
102                        } else {
103                            String::new()
104                        };
105
106                        let word = if let Some(width) = word_width_nibbles {
107                            format!("{:0>width$}", hex::encode_upper(word.bytes.clone()))
108                        } else {
109                            hex::encode_upper(word.bytes.clone())
110                        };
111
112                        format!("{}{}", addr, word)
113                    })
114                    .collect::<Vec<_>>()
115                    .join(word_separator),
116            );
117            sections.push(section_str);
118        }
119
120        sections.join("\n")
121    }
122}
123
124impl fmt::Display for Vmem {
125    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126        write!(f, "{}", self.dump(None, false))
127    }
128}
129
130/// Represents some value at some address as specified in the VMEM file.
131#[derive(Clone, Debug, PartialEq, Eq)]
132pub struct Data {
133    pub addr: u32,
134    pub value: Word,
135}
136
137impl Vmem {
138    /// Returns an iterator over all data of the VMEM file.
139    ///
140    /// The stride should either be 1 (one address per word) or the number of bytes
141    /// in each word.
142    pub fn data_addrs(&self, stride: usize) -> impl Iterator<Item = Data> + '_ {
143        self.sections()
144            .flat_map(move |section| section.data_addrs(stride))
145    }
146
147    /// Merge any contiguous sections in the VMEM together.
148    pub fn merge_sections(&mut self, addr_stride: Option<usize>) {
149        self.sections.dedup_by(|sec, last| {
150            let nwords = last.data.len() as u32;
151            let size = nwords * addr_stride.unwrap_or(1) as u32;
152            let merge: bool = last.addr + size == sec.addr;
153            if merge {
154                last.data.append(&mut sec.data);
155            }
156            merge
157        })
158    }
159}
160
161impl Section {
162    /// Returns an iterator over all data of this section of the VMEM file.
163    ///
164    /// The stride should either be 1 (one address per word) or the number of bytes
165    /// in each word.
166    pub fn data_addrs(&self, stride: usize) -> impl Iterator<Item = Data> + '_ {
167        let addrs = (self.addr..).step_by(stride);
168        let values = self.data.iter();
169        iter::zip(addrs, values).map(|(addr, value)| Data {
170            addr,
171            value: value.clone(),
172        })
173    }
174}
175
176/// Errors that occur when converting VMEM sections
177#[derive(Clone, Debug, Error, PartialEq, Eq)]
178pub enum ConversionError {
179    /// Cannot fit the words into the integer format being converted to.
180    #[error("word size {0} too large to fit after conversion")]
181    InvalidWordSize(usize),
182}
183
184impl TryFrom<Section> for Vec<u32> {
185    type Error = ConversionError;
186
187    fn try_from(section: Section) -> Result<Self, Self::Error> {
188        section
189            .data
190            .into_iter()
191            .map(|mut word| {
192                if word.bytes.len() <= 4 {
193                    return Err(ConversionError::InvalidWordSize(word.bytes.len()));
194                }
195
196                word.bytes.resize(4, 0);
197                let bytes: [u8; 4] = word.bytes.try_into().unwrap();
198                Ok(u32::from_le_bytes(bytes))
199            })
200            .collect()
201    }
202}
203
204#[cfg(test)]
205mod test {
206    use super::*;
207
208    #[test]
209    fn vmem_data() {
210        let vmem = Vmem::from_str("@10 12 23 34 @20 @26 45", Some(4)).unwrap();
211        let expected =
212            [(0x40, 0x12), (0x44, 0x23), (0x48, 0x34), (0x98, 0x45)].map(|(addr, value)| Data {
213                addr,
214                value: Word::new(vec![value]),
215            });
216
217        let data: Vec<_> = vmem.data_addrs(4).collect();
218        assert_eq!(data, expected);
219    }
220
221    #[test]
222    fn merge_contiguous_sections() {
223        // Use a VMEM with word address stride, but convert to byte addresses when parsing & merging.
224        let mut vmem = Vmem::from_str("1234 @20 0123 4567 89ab @23 cdef 1f1f", Some(2)).unwrap();
225        let expected = vec![
226            Section {
227                addr: 0x0,
228                data: vec![Word::new(vec![0x12, 0x34])],
229            },
230            Section {
231                addr: 0x40,
232                data: [
233                    [0x01, 0x23],
234                    [0x45, 0x67],
235                    [0x89, 0xab],
236                    [0xcd, 0xef],
237                    [0x1f, 0x1f],
238                ]
239                .iter()
240                .map(|&bytes| Word::new(Vec::from(bytes)))
241                .collect(),
242            },
243        ];
244
245        vmem.merge_sections(Some(2));
246        assert_eq!(vmem.sections, expected);
247
248        // Use a VMEM with byte address stride directly - no bytes per word is given when parsing.
249        let mut vmem = Vmem::from_str("1234 @20 0123 4567 89ab @26 cdef 1f1f", None).unwrap();
250        let expected = vec![
251            Section {
252                addr: 0x0,
253                data: vec![Word::new(vec![0x12, 0x34])],
254            },
255            Section {
256                addr: 0x20,
257                data: [
258                    [0x01, 0x23],
259                    [0x45, 0x67],
260                    [0x89, 0xab],
261                    [0xcd, 0xef],
262                    [0x1f, 0x1f],
263                ]
264                .iter()
265                .map(|&bytes| Word::new(Vec::from(bytes)))
266                .collect(),
267            },
268        ];
269
270        vmem.merge_sections(Some(2));
271        assert_eq!(vmem.sections, expected);
272    }
273
274    #[test]
275    fn section_data() {
276        let section = Section {
277            addr: 0x42,
278            data: [0x12, 0x23, 0x34, 0x45]
279                .iter()
280                .map(|&b| Word::new(vec![b]))
281                .collect(),
282        };
283        let expected =
284            [(0x42, 0x12), (0x46, 0x23), (0x4a, 0x34), (0x4e, 0x45)].map(|(addr, value)| Data {
285                addr,
286                value: Word::new(vec![value]),
287            });
288
289        let data: Vec<_> = section.data_addrs(4).collect();
290        assert_eq!(data, expected);
291    }
292
293    #[test]
294    fn section_stride() {
295        let section = Section {
296            addr: 0x15,
297            data: [0x12, 0x45, 0x78, 0xab]
298                .iter()
299                .map(|&b| Word::new(vec![b]))
300                .collect(),
301        };
302
303        let expected =
304            [(0x15, 0x12), (0x16, 0x45), (0x17, 0x78), (0x18, 0xab)].map(|(addr, value)| Data {
305                addr,
306                value: Word::new(vec![value]),
307            });
308        let data: Vec<_> = section.data_addrs(1).collect();
309        assert_eq!(data, expected);
310
311        let expected =
312            [(0x15, 0x12), (0x1a, 0x45), (0x1f, 0x78), (0x24, 0xab)].map(|(addr, value)| Data {
313                addr,
314                value: Word::new(vec![value]),
315            });
316        let data: Vec<_> = section.data_addrs(5).collect();
317        assert_eq!(data, expected);
318    }
319
320    #[test]
321    fn serialize() {
322        let input = r#"
323@000 DEADBEEF FACECAFE 01234567
324@010 00000000 11111111 22222222
325@234 A5A5A5A5 5A5A5A5A
326@ABC 01234567 89ABCDEF DEADBEEF
327        "#;
328        let vmem = Vmem::from_str(input, None).unwrap();
329        let dumped = vmem.to_string();
330        assert_eq!(dumped, input.trim());
331
332        let dumped = vmem.dump(Some(4), true);
333        let expected = r#"
334@000 DEADBEEF
335@004 FACECAFE
336@008 01234567
337@010 00000000
338@014 11111111
339@018 22222222
340@234 A5A5A5A5
341@238 5A5A5A5A
342@ABC 01234567
343@AC0 89ABCDEF
344@AC4 DEADBEEF
345        "#;
346        assert_eq!(dumped, expected.trim());
347    }
348}