vlfd_rs/
program.rs

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
8/// Helper that manages FPGA bitstream uploads using a [`Device`].
9pub 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}