1use crate::device::Device;
2use crate::error::{Error, Result};
3use crate::usb::Endpoint;
4use std::fs::File;
5use std::io::{BufRead, BufReader};
6use std::path::Path;
7
8pub struct Programmer {
10 device: Device,
11}
12
13impl Programmer {
14 pub fn new(device: Device) -> Self {
15 Self { device }
16 }
17
18 pub fn connect() -> Result<Self> {
19 let device = Device::connect()?;
20 Ok(Self { device })
21 }
22
23 pub fn device(&self) -> &Device {
24 &self.device
25 }
26
27 pub fn device_mut(&mut self) -> &mut Device {
28 &mut self.device
29 }
30
31 pub fn close(mut self) -> Result<()> {
32 self.device.close()
33 }
34
35 pub fn program(&mut self, bitfile: impl AsRef<Path>) -> Result<()> {
36 let mut program_data = load_bitfile(bitfile.as_ref())?;
37
38 self.device.ensure_session()?;
39 self.device.encrypt(&mut program_data);
40 self.device.activate_fpga_programmer()?;
41
42 let fifo_words = usize::from(self.device.config().fifo_size()).saturating_mul(2);
43 let chunk_len = fifo_words.max(1);
44
45 for chunk in program_data.chunks(chunk_len) {
46 self.device.usb().write_words(Endpoint::FifoWrite, chunk)?;
47 }
48
49 self.device.command_active()?;
50 self.device.read_config()?;
51
52 if !self.device.config().is_programmed() {
53 return Err(Error::NotProgrammed);
54 }
55
56 Ok(())
57 }
58}
59
60fn load_bitfile(path: &Path) -> Result<Vec<u16>> {
61 let file = File::open(path)?;
62 let reader = BufReader::new(file);
63 let mut program_data = Vec::new();
64
65 for line in reader.lines() {
66 let line = line?;
67 let mut accumulator = 0u16;
68 let mut has_nibble = false;
69
70 for byte in line.bytes() {
71 match byte {
72 b'_' => {
73 program_data.push(accumulator);
74 accumulator = 0;
75 has_nibble = false;
76 }
77 b' ' | b'\t' => break,
78 _ => {
79 let Some(nibble) = remap_hex(byte) else {
80 return Err(Error::InvalidBitfile(
81 "bitfile contains non-hexadecimal character",
82 ));
83 };
84 accumulator = (accumulator << 4) | u16::from(nibble);
85 has_nibble = true;
86 }
87 }
88 }
89
90 if has_nibble {
91 program_data.push(accumulator);
92 }
93 }
94
95 if program_data.is_empty() {
96 return Err(Error::InvalidBitfile("bitfile produced no data"));
97 }
98
99 Ok(program_data)
100}
101
102fn remap_hex(byte: u8) -> Option<u8> {
103 match byte {
104 b'0'..=b'9' => Some(byte - b'0'),
105 b'A'..=b'F' => Some(byte - b'A' + 10),
106 b'a'..=b'f' => Some(byte - b'a' + 10),
107 _ => None,
108 }
109}