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}