Skip to main content

vlfd_rs/
program.rs

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