x7dap/
lib.rs

1// Copyright 2025 Adam Greig
2// Licensed under the Apache-2.0 and MIT licenses.
3#![doc = include_str!("../README.md")]
4
5use std::{fmt, time::Duration, convert::{From, TryFrom}, path::Path, fs::File, io::Read};
6use num_enum::TryFromPrimitive;
7use indicatif::{ProgressBar, ProgressStyle};
8use jtagdap::jtag::{IDCODE, JTAGTAP, JTAGChain, Error as JTAGError};
9use jtagdap::bitvec::{self, bytes_to_bits, bits_to_bytes, Error as BitvecError};
10
11pub use jtagdap;
12
13#[derive(thiserror::Error, Debug)]
14pub enum Error {
15    #[error("Device status register in incorrect state.")]
16    BadStatus,
17    #[error("Cannot access flash memory unless the device is the only TAP in the JTAG chain.")]
18    NotOnlyTAP,
19    #[error(
20        "Bitstream file contains an IDCODE 0x{bitstream:08X} incompatible \
21         with the detected device IDCODE 0x{jtag:08X}."
22    )]
23    IncompatibleIdcode { bitstream: u32, jtag: u32 },
24    #[error("Could not remove VERIFY_IDCODE because parsing the bitstream failed")]
25    RemoveIdcodeNoMetadata,
26    #[error("SPI Flash error")]
27    SPIFlash(#[from] spi_flash::Error),
28    #[error("JTAG error")]
29    JTAG(#[from] JTAGError),
30    #[error("Bitvec error")]
31    Bitvec(#[from] BitvecError),
32    #[error("I/O error")]
33    IO(#[from] std::io::Error),
34    #[error(transparent)]
35    Other(#[from] anyhow::Error),
36}
37
38pub type Result<T> = std::result::Result<T, Error>;
39
40/// IDCODEs for all X7 device types.
41///
42/// IDCODEs are the same between C/A/Q part numbers (e.g. XC7Z030, XA7Z030, XQ7Z030).
43///
44/// Note first byte is the revision which may vary and so is 0 here.
45#[derive(Copy, Clone, Debug, Eq, PartialEq, TryFromPrimitive)]
46#[allow(non_camel_case_types)]
47#[repr(u32)]
48pub enum X7IDCODE {
49    X7S6        = 0x03622093,
50    X7S15       = 0x03620093,
51    X7S25       = 0x037C4093,
52    X7S50       = 0x0362F093,
53    X7S75       = 0x037C8093,
54    X7S100      = 0x037c7093,
55    X7A12T      = 0x037c3093,
56    X7A15T      = 0x0362E093,
57    X7A25T      = 0x037C2093,
58    X7A35T      = 0x0362D093,
59    X7A50T      = 0x0362C093,
60    X7A75T      = 0x03632093,
61    X7A100T     = 0x03631093,
62    X7A200T     = 0x03636093,
63    X7K70T      = 0x03647093,
64    X7K160T     = 0x0364C093,
65    X7K325T     = 0x03651093,
66    X7K355T     = 0x03747093,
67    X7K410T     = 0x03656093,
68    X7K420T     = 0x03752093,
69    X7K480T     = 0x03751093,
70    X7V575T     = 0x03671093,
71    X7VX330T    = 0x03667093,
72    X7VX415T    = 0x03682093,
73    X7VX485T    = 0x03687093,
74    X7VX550T    = 0x03692093,
75    X7VX690T    = 0x03691093,
76    X7VX980T    = 0x03696093,
77    X7VX1140T   = 0x036D5093,
78    X7VH580T    = 0x036D9093,
79    X7VH870T    = 0x036DB093,
80    X7Z007S     = 0x03723093,
81    X7Z012S     = 0x0373c093,
82    X7Z014S     = 0x03728093,
83    X7Z010      = 0x03722093,
84    X7Z015      = 0x0373b093,
85    X7Z020      = 0x03727093,
86    X7Z030      = 0x0372c093,
87    X7Z035      = 0x03732093,
88    X7Z045      = 0x03731093,
89    X7Z100      = 0x03736093,
90}
91
92impl From<X7IDCODE> for IDCODE {
93    fn from(id: X7IDCODE) -> IDCODE {
94        IDCODE(id as u32)
95    }
96}
97
98impl From<&X7IDCODE> for IDCODE {
99    fn from(id: &X7IDCODE) -> IDCODE {
100        IDCODE(*id as u32)
101    }
102}
103
104impl X7IDCODE {
105    pub fn try_from_idcode(idcode: IDCODE) -> Option<Self> {
106        Self::try_from(idcode.0 & 0x0FFF_FFFF).ok()
107    }
108
109    pub fn try_from_u32(idcode: u32) -> Option<Self> {
110        Self::try_from_idcode(IDCODE(idcode))
111    }
112
113    pub fn try_from_name(name: &str) -> Option<Self> {
114        match name.to_ascii_uppercase().as_str() {
115            "X7S6" => Some(X7IDCODE::X7S6),
116            "X7S15" => Some(X7IDCODE::X7S15),
117            "X7S25" => Some(X7IDCODE::X7S25),
118            "X7S50" => Some(X7IDCODE::X7S50),
119            "X7S75" => Some(X7IDCODE::X7S75),
120            "X7S100" => Some(X7IDCODE::X7S100),
121            "X7A12T" => Some(X7IDCODE::X7A12T),
122            "X7A15T" => Some(X7IDCODE::X7A15T),
123            "X7A25T" => Some(X7IDCODE::X7A25T),
124            "X7A35T" => Some(X7IDCODE::X7A35T),
125            "X7A50T" => Some(X7IDCODE::X7A50T),
126            "X7A75T" => Some(X7IDCODE::X7A75T),
127            "X7A100T" => Some(X7IDCODE::X7A100T),
128            "X7A200T" => Some(X7IDCODE::X7A200T),
129            "X7K70T" => Some(X7IDCODE::X7K70T),
130            "X7K160T" => Some(X7IDCODE::X7K160T),
131            "X7K325T" => Some(X7IDCODE::X7K325T),
132            "X7K355T" => Some(X7IDCODE::X7K355T),
133            "X7K410T" => Some(X7IDCODE::X7K410T),
134            "X7K420T" => Some(X7IDCODE::X7K420T),
135            "X7K480T" => Some(X7IDCODE::X7K480T),
136            "X7V575T" => Some(X7IDCODE::X7V575T),
137            "X7VX330T" => Some(X7IDCODE::X7VX330T),
138            "X7VX415T" => Some(X7IDCODE::X7VX415T),
139            "X7VX485T" => Some(X7IDCODE::X7VX485T),
140            "X7VX550T" => Some(X7IDCODE::X7VX550T),
141            "X7VX690T" => Some(X7IDCODE::X7VX690T),
142            "X7VX980T" => Some(X7IDCODE::X7VX980T),
143            "X7VX1140T" => Some(X7IDCODE::X7VX1140T),
144            "X7VH580T" => Some(X7IDCODE::X7VH580T),
145            "X7VH870T" => Some(X7IDCODE::X7VH870T),
146            "X7Z007S" => Some(X7IDCODE::X7Z007S),
147            "X7Z012S" => Some(X7IDCODE::X7Z012S),
148            "X7Z014S" => Some(X7IDCODE::X7Z014S),
149            "X7Z010" => Some(X7IDCODE::X7Z010),
150            "X7Z015" => Some(X7IDCODE::X7Z015),
151            "X7Z020" => Some(X7IDCODE::X7Z020),
152            "X7Z030" => Some(X7IDCODE::X7Z030),
153            "X7Z035" => Some(X7IDCODE::X7Z035),
154            "X7Z045" => Some(X7IDCODE::X7Z045),
155            "X7Z100" => Some(X7IDCODE::X7Z100),
156            _ => None,
157        }
158    }
159
160    pub fn name(&self) -> &'static str {
161        match self {
162            X7IDCODE::X7S6 => "X7S6",
163            X7IDCODE::X7S15 => "X7S15",
164            X7IDCODE::X7S25 => "X7S25",
165            X7IDCODE::X7S50 => "X7S50",
166            X7IDCODE::X7S75 => "X7S75",
167            X7IDCODE::X7S100 => "X7S100",
168            X7IDCODE::X7A12T => "X7A12T",
169            X7IDCODE::X7A15T => "X7A15T",
170            X7IDCODE::X7A25T => "X7A25T",
171            X7IDCODE::X7A35T => "X7A35T",
172            X7IDCODE::X7A50T => "X7A50T",
173            X7IDCODE::X7A75T => "X7A75T",
174            X7IDCODE::X7A100T => "X7A100T",
175            X7IDCODE::X7A200T => "X7A200T",
176            X7IDCODE::X7K70T => "X7K70T",
177            X7IDCODE::X7K160T => "X7K160T",
178            X7IDCODE::X7K325T => "X7K325T",
179            X7IDCODE::X7K355T => "X7K355T",
180            X7IDCODE::X7K410T => "X7K410T",
181            X7IDCODE::X7K420T => "X7K420T",
182            X7IDCODE::X7K480T => "X7K480T",
183            X7IDCODE::X7V575T => "X7V575T",
184            X7IDCODE::X7VX330T => "X7VX330T",
185            X7IDCODE::X7VX415T => "X7VX415T",
186            X7IDCODE::X7VX485T => "X7VX485T",
187            X7IDCODE::X7VX550T => "X7VX550T",
188            X7IDCODE::X7VX690T => "X7VX690T",
189            X7IDCODE::X7VX980T => "X7VX980T",
190            X7IDCODE::X7VX1140T => "X7VX1140T",
191            X7IDCODE::X7VH580T => "X7VH580T",
192            X7IDCODE::X7VH870T => "X7VH870T",
193            X7IDCODE::X7Z007S => "X7Z007S",
194            X7IDCODE::X7Z012S => "X7Z012S",
195            X7IDCODE::X7Z014S => "X7Z014S",
196            X7IDCODE::X7Z010 => "X7Z010",
197            X7IDCODE::X7Z015 => "X7Z015",
198            X7IDCODE::X7Z020 => "X7Z020",
199            X7IDCODE::X7Z030 => "X7Z030",
200            X7IDCODE::X7Z035 => "X7Z035",
201            X7IDCODE::X7Z045 => "X7Z045",
202            X7IDCODE::X7Z100 => "X7Z100",
203        }
204    }
205
206    /// Returns whether the provided IDCODE is considered compatible with
207    /// this IDCODE.
208    pub fn compatible(&self, other: X7IDCODE) -> bool {
209        *self == other
210    }
211
212    /// Number of configuration bits per frame.
213    ///
214    /// Returns (pad_bits_before_frame, bits_per_frame, pad_bits_after_frame).
215    pub fn config_bits_per_frame(&self) -> (usize, usize, usize) {
216        (0, 0, 0)
217    }
218}
219
220pub fn check_tap_idx(chain: &JTAGChain, index: usize) -> Option<X7IDCODE> {
221    match chain.idcodes().iter().nth(index) {
222        Some(Some(idcode)) => X7IDCODE::try_from_idcode(*idcode),
223        _ => None,
224    }
225}
226
227/// Attempt to discover a unique TAP index for a 7-series device in a JTAGChain.
228pub fn auto_tap_idx(chain: &JTAGChain) -> Option<(usize, X7IDCODE)> {
229    let x7_idxs: Vec<(usize, X7IDCODE)> = chain
230        .idcodes()
231        .iter()
232        .enumerate()
233        .filter_map(|(idx, id)| id.map(|id| (idx, id)))
234        .filter_map(|(idx, id)| X7IDCODE::try_from_idcode(id).map(|id| (idx, id)))
235        .collect();
236    let len = x7_idxs.len();
237    if len == 0 {
238        log::info!("No 7-series device found in JTAG chain");
239        None
240    } else if len > 1 {
241        log::info!("Multiple 7-series devices found in JTAG chain, specify one using --tap");
242        None
243    } else {
244        let (index, idcode) = x7_idxs.first().unwrap();
245        log::debug!("Automatically selecting device at TAP {}", index);
246        Some((*index, *idcode))
247    }
248}
249
250/// 7-series JTAG instructions.
251#[derive(Copy, Clone, Debug)]
252#[allow(unused, non_camel_case_types, clippy::upper_case_acronyms)]
253#[repr(u8)]
254enum Command {
255    EXTEST = 0b100110,
256    EXTEST_PULSE = 0b111100,
257    EXTEST_TRAIN = 0b1111101,
258    SAMPLE = 0b000001,
259    USER1 = 0b000010,
260    USER2 = 0b000011,
261    USER3 = 0b100010,
262    USER4 = 0b100011,
263    CFG_OUT = 0b000100,
264    CFG_IN = 0b000101,
265    USERCODE = 0b001000,
266    IDCODE = 0b001001,
267    HIGHZ_IO = 0b001010,
268    JPROGRAM = 0b001011,
269    JSTART = 0b001100,
270    JSHUTDOWN = 0b001101,
271    XADC_DRP = 0b110111,
272    ISC_ENABLE = 0b010000,
273    ISC_PROGRAM = 0b010001,
274    XSC_PROGRAM_KEY = 0b010010,
275    XSC_DNA = 0b010111,
276    FUSE_DNA = 0b110010,
277    ISC_NOOP = 0b010100,
278    ISC_DISABLE = 0b010110,
279    BYPASS = 0b111111,
280}
281
282impl Command {
283    pub fn bits(&self) -> Vec<bool> {
284        jtagdap::bitvec::bytes_to_bits(&[*self as u8], 6).unwrap()
285    }
286}
287
288/// Configuration status register.
289#[derive(Copy, Clone)]
290pub struct Status(u32);
291
292impl Status {
293    pub fn new(word: u32) -> Self {
294        Self(word)
295    }
296
297    pub fn startup_state(&self) -> u8       { ((self.0 >> 18) & 0b111) as u8 }
298    pub fn xadc_overtemp(&self) -> bool     { self.bit(17) }
299    pub fn dec_error(&self) -> bool         { self.bit(16) }
300    pub fn id_error(&self) -> bool          { self.bit(15) }
301    pub fn done(&self) -> bool              { self.bit(14) }
302    pub fn release_done(&self) -> bool      { self.bit(13) }
303    pub fn init_b(&self) -> bool            { self.bit(12) }
304    pub fn init_complete(&self) -> bool     { self.bit(11) }
305    pub fn mode(&self) -> u8                { ((self.0 >> 8) & 0b111) as u8 }
306    pub fn ghigh_b(&self) -> bool           { self.bit(7) }
307    pub fn gwe(&self) -> bool               { self.bit(6) }
308    pub fn gts_cfg_b(&self) -> bool         { self.bit(5) }
309    pub fn eos(&self) -> bool               { self.bit(4) }
310    pub fn dci_match(&self) -> bool         { self.bit(3) }
311    pub fn mmcm_lock(&self) -> bool         { self.bit(2) }
312    pub fn part_secured(&self) -> bool      { self.bit(1) }
313    pub fn crc_error(&self) -> bool         { self.bit(0) }
314
315    fn bit(&self, offset: usize) -> bool {
316        (self.0 >> offset) & 1 == 1
317    }
318}
319
320impl fmt::Debug for Status {
321    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322        f.write_fmt(format_args!(
323            "Status: {:08X}
324  Startup state: 0b{:03b}
325  XADC overtemp: {}
326  Decrypt error: {}
327  ID error: {}
328  DONE: {}
329  Release DONE: {}
330  INIT_B: {}
331  INIT complete: {}
332  Mode: 0b{:03b}
333  GHIGH_B: {}
334  Global write enable: {}
335  Global tri-state: {}
336  End of startup: {}
337  DCI match: {}
338  MMCM lock: {}
339  Secured: {}
340  CRC error: {}",
341            self.0, self.startup_state(), self.xadc_overtemp(), self.dec_error(), self.id_error(),
342            self.done(), self.release_done(), self.init_b(), self.init_complete(), self.mode(),
343            self.ghigh_b(), self.gwe(), self.gts_cfg_b(), self.eos(), self.dci_match(),
344            self.mmcm_lock(), self.part_secured(), self.crc_error()))
345    }
346}
347
348pub struct X7 {
349    tap: JTAGTAP,
350    idcode: X7IDCODE,
351}
352
353impl X7 {
354    pub fn new(tap: JTAGTAP, idcode: X7IDCODE) -> Self {
355        X7 { tap, idcode }
356    }
357
358    pub fn idcode(&self) -> X7IDCODE {
359        self.idcode
360    }
361
362    /// Read full 64-bit device DNA.
363    pub fn dna(&mut self) -> Result<Vec<u8>> {
364        self.command(Command::FUSE_DNA)?;
365        let data = self.tap.read_dr(64)?;
366        let dna = bits_to_bytes(&data);
367        log::info!("Read DNA: {:02X?}", dna);
368        Ok(dna)
369    }
370
371    /// Read STATUS register content.
372    pub fn status(&mut self) -> Result<Status> {
373        self.tap.test_logic_reset()?;
374        self.tap.run_test_idle(5)?;
375        self.command(Command::CFG_IN)?;
376        let mut bits = Vec::new();
377        bitvec::append_u32(&mut bits, 0xaa99_5566u32.reverse_bits());
378        bitvec::append_u32(&mut bits, 0x2000_0000u32.reverse_bits());
379        bitvec::append_u32(&mut bits, 0x2800_e001u32.reverse_bits());
380        bitvec::append_u32(&mut bits, 0x2000_0000u32.reverse_bits());
381        bitvec::append_u32(&mut bits, 0x2000_0000u32.reverse_bits());
382        self.tap.write_dr(&bits)?;
383        self.command(Command::CFG_OUT)?;
384        let status = bits_to_bytes(&self.tap.read_dr(32)?);
385        let status = u32::from_le_bytes([status[0], status[1], status[2], status[3]]);
386        let status = Status::new(status.reverse_bits());
387        log::debug!("{:?}", status);
388        self.tap.test_logic_reset()?;
389        Ok(status)
390    }
391
392    /// Program a bitstream to SRAM.
393    ///
394    /// The FPGA is reset and begins running the new bitstream after programming.
395    pub fn program(&mut self, data: &[u8]) -> Result<()> {
396        self.program_cb(data, |_| {})
397    }
398
399    /// Program a bitstream to SRAM, with a progress bar.
400    ///
401    /// The FPGA is reset and begins running the new bitstream after programming.
402    pub fn program_progress(&mut self, data: &[u8]) -> Result<()> {
403        const DATA_PROGRESS_TPL: &str =
404            " {msg} [{bar:40.cyan/black}] {bytes}/{total_bytes} ({bytes_per_sec}; {eta_precise})";
405        const DATA_FINISHED_TPL: &str =
406            " {msg} [{bar:40.green/black}] {bytes}/{total_bytes} ({bytes_per_sec}; {eta_precise})";
407        const DATA_PROGRESS_CHARS: &str = "━╸━";
408        let pb = ProgressBar::new(data.len() as u64).with_style(
409            ProgressStyle::with_template(DATA_PROGRESS_TPL)
410                .unwrap()
411                .progress_chars(DATA_PROGRESS_CHARS));
412        pb.set_message("Programming");
413        pb.set_position(0);
414
415        self.program_cb(data, |n| pb.set_position(n as u64))?;
416
417        pb.set_style(ProgressStyle::with_template(DATA_FINISHED_TPL)
418            .unwrap()
419            .progress_chars(DATA_PROGRESS_CHARS)
420        );
421
422        pb.finish();
423        Ok(())
424    }
425
426    /// Program a bitstream to SRAM, calling `cb` with the number of bytes programmed so far.
427    ///
428    /// The FPGA is reset and begins running the new bitstream after programming.
429    pub fn program_cb<F: Fn(usize)>(&mut self, data: &[u8], cb: F) -> Result<()> {
430        // Reset FPGA and wait 10ms.
431        self.check_ready_to_program()?;
432        self.tap.test_logic_reset()?;
433        self.command(Command::JPROGRAM)?;
434        self.tap.run_test_idle(1)?;
435        std::thread::sleep(Duration::from_millis(20));
436
437        // Enter configuration mode.
438        self.tap.test_logic_reset()?;
439        self.command(Command::CFG_IN)?;
440
441        // Load in entire bitstream.
442        // We need to send the MSb of the first byte first and finish on the LSb of the last byte,
443        // so since bytes_to_bits is LSb-first, we reverse the bit order of each byte.
444        let data: Vec<u8> = data.iter().map(|x| x.reverse_bits()).collect();
445        let bits = bytes_to_bits(&data, data.len() * 8)?;
446
447        // Write bitstream, passing the callback through.
448        self.tap.write_dr_cb(&bits, |n| cb(n / 8))?;
449
450        // Return to Run-Test/Idle to complete programming.
451        self.tap.run_test_idle(1)?;
452
453        // Begin startup sequence.
454        self.command(Command::JSTART)?;
455        self.tap.run_test_idle(2000)?;
456        self.tap.test_logic_reset()?;
457
458        // Check programming was OK.
459        self.check_programmed_ok()?;
460        self.tap.test_logic_reset()?;
461
462        Ok(())
463    }
464
465    pub fn jprogram(&mut self) -> Result<()> {
466        self.command(Command::JPROGRAM)?;
467        self.tap.run_test_idle(2000)?;
468        self.tap.test_logic_reset()?;
469        Ok(())
470    }
471
472    fn check_ready_to_program(&mut self) -> Result<()> {
473        log::debug!("Checking status before programming...");
474        let status = self.status()?;
475        if !status.init_complete() {
476            log::error!("FPGA init not complete");
477            return Err(Error::BadStatus);
478        }
479        if !status.init_b() {
480            log::error!("FPGA INIT_B still low");
481            return Err(Error::BadStatus);
482        }
483        Ok(())
484    }
485
486    fn check_programmed_ok(&mut self) -> Result<()> {
487        log::debug!("Checking status after programming...");
488        let status = self.status()?;
489        if !status.init_complete() {
490            log::error!("Init not complete");
491            return Err(Error::BadStatus);
492        }
493        if !status.init_b() {
494            log::error!("INIT_B still low");
495            return Err(Error::BadStatus);
496        }
497        if !status.done() {
498            log::error!("DONE still low");
499            return Err(Error::BadStatus);
500        }
501        if !status.release_done() {
502            log::error!("DONE not released");
503            return Err(Error::BadStatus);
504        }
505        if status.dec_error() {
506            log::error!("Decrypt error");
507            return Err(Error::BadStatus);
508        }
509        if status.id_error() {
510            log::error!("ID error");
511            return Err(Error::BadStatus);
512        }
513        if status.crc_error() {
514            log::error!("CRC error");
515            return Err(Error::BadStatus);
516        }
517        Ok(())
518    }
519
520    /// Load a command into the IR.
521    fn command(&mut self, command: Command) -> Result<()> {
522        log::trace!("Loading command {:?}", command);
523        Ok(self.tap.write_ir(&command.bits())?)
524    }
525}
526
527pub struct Bitstream {
528    data: Vec<u8>,
529}
530
531impl Bitstream {
532    /// Open a bitstream from the provided path.
533    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
534        let mut file = File::open(path)?;
535        Self::from_file(&mut file)
536    }
537
538    /// Open a bitstream from the provided open `File`.
539    pub fn from_file(file: &mut File) -> Result<Self> {
540        let mut data = if let Ok(metadata) = file.metadata() {
541            Vec::with_capacity(metadata.len() as usize)
542        } else {
543            Vec::new()
544        };
545        file.read_to_end(&mut data)?;
546        Ok(Self::new(data))
547    }
548
549    /// Load a bitstream from the provided raw bitstream data.
550    pub fn from_data(data: &[u8]) -> Self {
551        Self::new(data.to_owned())
552    }
553
554    /// Load a bitstream directly from a `Vec<u8>`.
555    pub fn new(data: Vec<u8>) -> Self {
556        Self { data }
557    }
558
559    /// Get the underlying bitstream data.
560    pub fn data(&self) -> &[u8] {
561        &self.data[..]
562    }
563}