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}