Skip to main content

vlfd_rs/
program.rs

1use crate::error::{Error, Result};
2use crate::session::Board;
3use crate::usb::TransportConfig;
4use std::{
5    fs::File,
6    io::{BufRead, BufReader},
7    path::Path,
8};
9
10pub struct Programmer {
11    board: Board,
12}
13
14impl Programmer {
15    pub fn open() -> Result<Self> {
16        Self::open_with_transport(TransportConfig::default())
17    }
18
19    pub fn open_with_transport(transport: TransportConfig) -> Result<Self> {
20        Ok(Self {
21            board: Board::open_with_transport(transport)?,
22        })
23    }
24
25    pub fn board(&self) -> &Board {
26        &self.board
27    }
28
29    pub fn board_mut(&mut self) -> &mut Board {
30        &mut self.board
31    }
32
33    pub fn program(&mut self, bitfile: impl AsRef<Path>) -> Result<()> {
34        let words = load_bitfile(bitfile.as_ref())?;
35        let mut session = self.board.programmer()?;
36        session.write_bitstream_words(&words)?;
37        session.finish()
38    }
39
40    pub fn close(self) -> Result<()> {
41        self.board.close()
42    }
43}
44
45pub fn load_bitfile(path: &Path) -> Result<Vec<u16>> {
46    let file = File::open(path)?;
47    load_bitfile_from_reader(BufReader::new(file))
48}
49
50pub fn load_bitfile_from_reader<R: BufRead>(reader: R) -> Result<Vec<u16>> {
51    let mut program_data = Vec::new();
52
53    for (line_index, line) in reader.lines().enumerate() {
54        let line_number = line_index + 1;
55        let line = line?;
56        let payload = line.split_whitespace().next().unwrap_or_default();
57
58        if payload.is_empty() {
59            continue;
60        }
61
62        for segment in payload.split('_') {
63            if segment.is_empty() {
64                return Err(Error::InvalidBitfileLine {
65                    line: line_number,
66                    reason: "empty word segment",
67                });
68            }
69
70            let value =
71                u16::from_str_radix(segment, 16).map_err(|_| Error::InvalidBitfileLine {
72                    line: line_number,
73                    reason: "bitfile contains non-hexadecimal characters",
74                })?;
75            program_data.push(value);
76        }
77    }
78
79    if program_data.is_empty() {
80        return Err(Error::InvalidBitfile("bitfile produced no data"));
81    }
82
83    Ok(program_data)
84}
85
86#[cfg(test)]
87mod tests {
88    use super::load_bitfile_from_reader;
89    use crate::Error;
90    use std::io::Cursor;
91
92    #[test]
93    fn parses_cpp_style_bitfile_lines_into_words() {
94        let data = "1234_abcd\n5678_9abc trailing\n";
95        let words = load_bitfile_from_reader(Cursor::new(data)).expect("parse should succeed");
96        assert_eq!(words, vec![0x1234, 0xabcd, 0x5678, 0x9abc]);
97    }
98
99    #[test]
100    fn reports_invalid_bitfile_line_numbers() {
101        let err =
102            load_bitfile_from_reader(Cursor::new("1234_gggg\n")).expect_err("parse should fail");
103        match err {
104            Error::InvalidBitfileLine { line, reason } => {
105                assert_eq!(line, 1);
106                assert_eq!(reason, "bitfile contains non-hexadecimal characters");
107            }
108            other => panic!("unexpected error: {other}"),
109        }
110    }
111}