ecpdap 0.2.0

Program ECP5 FPGAs using CMSIS-DAP probes
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
// Copyright 2022 Adam Greig
// Licensed under the Apache-2.0 and MIT licenses.

use std::time::Instant;
use std::convert::{TryFrom, TryInto};
use crate::ECP5IDCODE;
use super::{BitstreamCommandId, BitstreamMeta, StoredCrc};

#[derive(Clone, Debug, PartialEq, thiserror::Error)]
pub(super) enum ParseError {
    #[error("Exhausted: Ran out of bitstream data earlier than expected")]
    Exhausted,
    #[error("Did not find an initial comment block")]
    NoComment,
    #[error("Did not find the preamble marker")]
    NoPreamble,
    #[error("Invalid CRC inside bitstream: computed {computed:04x}, read {read:04x}")]
    InvalidCrc { computed: u16, read: u16 },
    #[error("Unexpected data: expected {expected:?}, read {read:?}")]
    BadCheck { expected: Vec<u8>, read: Vec<u8> },
    #[error("Did not find a VERIFY_ID command, so cannot decode the bitstream")]
    NoVerifyId,
    #[error("Found an unknown IDCODE 0x{0:08X}")]
    UnknownIdcode(u32),
    #[error("Did not find a compression dictionary but the configuration data is compressed")]
    NoCompDict,
    #[error("Found unknown command {0:02X}")]
    UnknownCommand(u8),
}

/// Read from a slice, keeping track of current position.
///
/// Provides convenient methods like peek, skip, and fetching constant-sized arrays.
struct Reader<'a> {
    data: &'a [u8],
    offset: usize,
}

impl<'a> Reader<'a> {
    fn new(data: &'a [u8]) -> Reader<'a> {
        Self { data, offset: 0 }
    }

    /// Return original input.
    fn input(&self) -> &[u8] {
        self.data
    }

    /// Fetch current offset, which is the position of the next byte to be read.
    fn offset(&self) -> usize {
        self.offset
    }

    /// Fetch next byte to be read, incrementing internal offset.
    fn next(&mut self) -> Result<u8, ParseError> {
        let x = self.data.get(self.offset).copied().ok_or(ParseError::Exhausted);
        self.offset += 1;
        x
    }

    /// Look at next n bytes without consuming them.
    fn peek(&self, n: usize) -> Result<&[u8], ParseError> {
        self.data.get(self.offset..(self.offset + n)).ok_or(ParseError::Exhausted)
    }

    /// Skip `n` bytes.
    fn skip(&mut self, n: usize) -> Result<(), ParseError> {
        self.offset += n;
        if self.offset <= self.data.len() {
            Ok(())
        } else {
            Err(ParseError::Exhausted)
        }
    }

    /// Skip until specific byte will be returned next.
    ///
    /// Make sure to advance the offset somehow before calling again or you'll get stuck.
    fn skip_to(&mut self, x: u8) -> Result<(), ParseError> {
        while self.offset < self.data.len() && self.data[self.offset] != x {
            self.offset += 1;
        }

        if self.eof() {
            Err(ParseError::Exhausted)
        } else {
            Ok(())
        }
    }

    /// Verify the next bytes match the provided slice and skip them.
    fn check_skip(&mut self, x: &[u8]) -> Result<(), ParseError> {
        let data = self.peek(x.len())?;
        if data != x {
            Err(ParseError::BadCheck { expected: x.to_vec(), read: data.to_vec() })
        } else {
            self.skip(x.len())?;
            Ok(())
        }
    }

    /// Take constant `n` bytes, returning an array reference.
    fn take<const N: usize>(&mut self) -> Result<&[u8; N], ParseError> {
        self.offset += N;
        self.data.get(self.offset - N..self.offset)
                 .map(|d| d.try_into().unwrap())
                 .ok_or(ParseError::Exhausted)
    }

    /// Read compressed bytes using ECP5 bitstream compression.
    ///
    /// Returns exactly `n` decompressed bytes or an error.
    #[allow(unused)]
    fn read_compressed(&mut self, n: usize, dict: &[u8; 8]) -> Result<Vec<u8>, ParseError> {
        let mut out = Vec::with_capacity(n);
        let mut bits = BitReader::new();

        for _ in 0..n {
            if bits.bit(self)? == 0 {
                // Code 0 represents a whole 0 byte of input.
                out.push(0);
            } else if bits.bit(self)? == 0 {
                if bits.bit(self)? == 0 {
                    let pos = bits.bits(self, 3)? as u8;
                    // Code 100xxx is a single set-bit position.
                    out.push(1 << pos);
                } else {
                    // Code 101xxx is a stored byte.
                    let idx = bits.bits(self, 3)? as usize;
                    out.push(dict[idx]);
                }
            } else {
                // Code 11xxxxxxxx is a literal byte.
                let byte = bits.bits(self, 8)?;
                out.push(byte as u8);
            }
        }

        Ok(out)
    }

    /// Skip compressed bytes using ECP5 bitstream compression.
    ///
    /// Skips exactly `n` decompressed bytes.
    fn skip_compressed(&mut self, n: usize) -> Result<(), ParseError> {
        let mut bits = BitReader::new();
        for _ in 0..n {
            // 0 bits map to a whole byte, so only read extra bits on 1.
            if bits.bit(self)? == 1 {
                if bits.bit(self)? == 0 {
                    // 10yxxx always requires 4 additional bits.
                    bits.bits(self, 4)?;
                } else {
                    // 11xxxxxxxx always requires 8 additional bits.
                    bits.bits(self, 8)?;
                }
            }
        }
        Ok(())
    }

    /// Check if internal offset is now at the end of the input data.
    fn eof(&self) -> bool {
        self.offset >= self.data.len()
    }
}

struct BitReader {
    bits: u32,
    bits_left: usize,
}

impl BitReader {
    fn new() -> Self {
        BitReader { bits: 0, bits_left: 0 }
    }

    fn bit(&mut self, rd: &mut Reader) -> Result<u8, ParseError> {
        if self.bits_left == 0 {
            self.bits = rd.next()? as u32;
            self.bits_left += 8;
        }
        self.bits_left -= 1;
        Ok((self.bits >> (self.bits_left)) as u8 & 1)
    }

    fn bits(&mut self, rd: &mut Reader, n: usize) -> Result<u32, ParseError> {
        assert!(n <= 32);
        while self.bits_left < n {
            self.bits = (self.bits << 8) | (rd.next()? as u32);
            self.bits_left += 8;
        }
        self.bits_left -= n;
        Ok((self.bits >> (self.bits_left)) & ((1 << n) - 1))
    }
}

impl BitstreamMeta {
    /// Parse the provided bitstream, extracting the relevant metadata.
    pub fn parse(input: &[u8]) -> Result<Self, ParseError> {
        log::info!("Parsing bitstream for metadata");
        let t0 = Instant::now();
        let mut rd = Reader::new(input);

        // Check start of comment block.
        if rd.take()? != &[0xFF, 0x00] {
            return Err(ParseError::NoComment);
        }

        // Skip through to end of comment block.
        while rd.take()? != &[0x00, 0xFF] {
            rd.skip_to(0x00)?;
        }

        // Check preamble.
        if rd.take()? != &[0xFF, 0xFF, 0xBD, 0xB3] {
            return Err(ParseError::NoPreamble);
        }

        let mut meta = BitstreamMeta {
            verify_id: None,
            spi_mode: None,
            stored_crcs: Vec::new(),
            comp_dict: None,
        };
        let mut current_crc = StoredCrc::new();

        // Process commands.
        loop {
            match BitstreamCommandId::try_from(rd.next()?) {
                Ok(BitstreamCommandId::DUMMY) => {
                    // Don't include DUMMY bytes in CRC processing.
                    current_crc.exclude(rd.offset() - 1);
                },
                Ok(BitstreamCommandId::VERIFY_ID) => {
                    rd.check_skip(&[0, 0, 0])?;
                    let offset = rd.offset();
                    let idcode = u32::from_be_bytes(*rd.take()?);
                    let idcode = ECP5IDCODE::try_from_u32(idcode)
                                             .ok_or(ParseError::UnknownIdcode(idcode))?;
                    log::trace!("VERIFY_ID command for IDCODE 0x{:08X} ({})",
                                idcode as u32, idcode.name());
                    meta.verify_id = Some((idcode, offset));
                },
                Ok(BitstreamCommandId::SPI_MODE) => {
                    let offset = rd.offset();
                    let mode = rd.next()?;
                    log::trace!("SPI_MODE command for mode {mode:02X}");
                    rd.check_skip(&[0, 0])?;
                    meta.spi_mode = Some((mode, offset));
                },
                Ok(BitstreamCommandId::LSC_RESET_CRC) => {
                    log::trace!("LSC_RESET_CRC command");
                    rd.check_skip(&[0, 0, 0])?;
                    current_crc.start(rd.offset());
                },
                Ok(BitstreamCommandId::LSC_WRITE_COMP_DIC) => {
                    log::trace!("LSC_WRITE_COMP_DIC command");
                    let crc_check = rd.next()?;
                    rd.check_skip(&[0, 0])?;
                    meta.comp_dict = Some(u64::from_be_bytes(*rd.take()?).to_le_bytes());
                    meta.maybe_check_crc(crc_check, &mut rd, &mut current_crc)?;
                },
                Ok(BitstreamCommandId::ISC_PROGRAM_USERCODE) => {
                    log::trace!("ISC_PROGRAM_USERCODE command");
                    let crc_check = rd.next()?;
                    rd.check_skip(&[0, 0])?;
                    rd.skip(4)?;
                    meta.maybe_check_crc(crc_check, &mut rd, &mut current_crc)?;
                },
                Ok(BitstreamCommandId::JUMP)
                    | Ok(BitstreamCommandId::LSC_PROG_CNTRL0)
                    | Ok(BitstreamCommandId::LSC_WRITE_ADDRESS)
                    | Ok(BitstreamCommandId::ISC_PROGRAM_SECURITY)
                    | Ok(BitstreamCommandId::LSC_EBR_ADDRESS)
                => {
                    rd.check_skip(&[0, 0, 0])?;
                    rd.skip(4)?;
                },
                Ok(BitstreamCommandId::LSC_INIT_ADDRESS)
                    | Ok(BitstreamCommandId::ISC_PROGRAM_DONE)
                => {
                    rd.check_skip(&[0, 0, 0])?;
                },
                Ok(BitstreamCommandId::LSC_PROG_SED_CRC) => {
                    rd.check_skip(&[0, 0, 0])?;
                    rd.skip(8)?;
                },
                Ok(BitstreamCommandId::LSC_EBR_WRITE) => {
                    log::trace!("LSC_EBR_WRITE command");
                    let crc_check = rd.next()?;
                    let num_frames = u16::from_be_bytes(*rd.take()?);
                    for _ in 0..num_frames {
                        rd.skip(9)?;
                    }
                    meta.maybe_check_crc(crc_check, &mut rd, &mut current_crc)?;
                },
                Ok(BitstreamCommandId::LSC_PROG_INCR_RTI) => {
                    log::trace!("LSC_PROG_INCR_RTI command");
                    meta.read_frame(&mut rd, false, &mut current_crc)?;
                },
                Ok(BitstreamCommandId::LSC_PROG_INCR_CMP) => {
                    log::trace!("LSC_PROG_INCR_CMP command");
                    meta.read_frame(&mut rd, true, &mut current_crc)?;
                },
                Err(e) => {
                    return Err(ParseError::UnknownCommand(e.number));
                },
            }

            // Stop looking for commands if we reached the end of the bitstream.
            if rd.eof() {
                let elapsed = t0.elapsed().as_millis();
                log::debug!("Parsed to end of bitstream in {elapsed}ms");
                break;
            }
        }

        Ok(meta)
    }

    /// Verify a trailing CRC16 after a command/frame.
    ///
    /// Validates the CRC and updates `stored_crcs` as appropriate.
    fn check_crc(&mut self, rd: &mut Reader, current_crc: &mut StoredCrc)
        -> Result<(), ParseError>
    {
        let pos = rd.offset();
        let crc = u16::from_be_bytes(*rd.take()?);
        current_crc.finish(pos);
        let computed = current_crc.compute(rd.input());
        if computed != crc {
            return Err(ParseError::InvalidCrc { computed, read: crc });
        }
        self.stored_crcs.push(current_crc.clone());
        current_crc.start(rd.offset());
        Ok(())
    }

    /// Conditionally verify a trailing CRC16 if set.
    fn maybe_check_crc(&mut self, check: u8, rd: &mut Reader, current_crc: &mut StoredCrc)
        -> Result<(), ParseError>
    {
        if check & 0x80 != 0 {
            self.check_crc(rd, current_crc)
        } else {
            Ok(())
        }
    }

    /// Skip configuration frame data, which may be compressed.
    ///
    /// Requires `verify_id` to determine frame size, and if compressed, a `comp_dict`.
    fn read_frame(&mut self, rd: &mut Reader, compressed: bool, current_crc: &mut StoredCrc)
        -> Result<(), ParseError>
    {
        // Check we have an ID and (if required) a compression dictionary.
        let id = self.verify_id.ok_or(ParseError::NoVerifyId)?.0;
        let (mut pad_before, bits_per_frame, mut pad_after) = id.config_bits_per_frame();
        if compressed && self.comp_dict.is_none() {
            return Err(ParseError::NoCompDict);
        }

        // Work out size per frame.
        let params = rd.next()?;
        let check_crc = params & 0x80 != 0;
        let crc_after_each = check_crc && (params & 0x40 == 0);
        let use_dummy_bits = params & 0x20 == 0;
        let use_dummy_bytes = params & 0x10 != 0;
        let mut dummy_bytes = (params & 0x0F) as usize;
        if !use_dummy_bits {
            log::warn!("Frame doesn't use dummy bits, which is unusual");
            pad_before = 0;
            pad_after = 0;
        }
        if !use_dummy_bytes {
            log::warn!("Frame doesn't use dummy bytes, which is unusual");
            dummy_bytes = 0;
        }
        let frame_count = u16::from_be_bytes(*rd.take()?);
        let mut bytes_per_frame = (pad_before + bits_per_frame + pad_after) / 8;
        if compressed {
            bytes_per_frame += 7 - ((bytes_per_frame - 1) % 8);
        }

        // Skip through frame data.
        for i in 0..frame_count {
            if compressed {
                rd.skip_compressed(bytes_per_frame)?;
            } else {
                rd.skip(bytes_per_frame)?;
            }
            if crc_after_each || (check_crc && i == frame_count - 1) {
                self.check_crc(rd, current_crc)?;
            }
            rd.skip(dummy_bytes)?;
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_no_comment() {
        assert_eq!(
            BitstreamMeta::parse(&[
                0xFF, 0xFF, 0xBD, 0xB3, 0xFF, 0xFF, 0xFF, 0xFF,
                0xE2, 0x00, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78,
                0xFF, 0xFF, 0xFF, 0xFF, 0x79, 0xAB, 0x00, 0x00,
            ]),
            Err(ParseError::NoComment),
        );
    }

    #[test]
    fn test_no_preamble() {
        assert_eq!(
            BitstreamMeta::parse(&[
                0xFF, 0x00, b'c', b'o', b'm', b'm', b'e', b'n', b't', 0x00, 0xFF,
                0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                0xE2, 0x00, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78,
                0xFF, 0xFF, 0xFF, 0xFF, 0x79, 0xAB, 0x00, 0x00,
            ]),
            Err(ParseError::NoPreamble),
        );
    }

    #[test]
    fn test_parse_simple() {
        let mut crc = StoredCrc::new();
        crc.start(20);
        for i in &[20, 21, 22, 23, 32, 33, 34, 35] {
            crc.exclude(*i);
        }
        crc.finish(48);
        assert_eq!(
            BitstreamMeta::parse(&[
                0xFF, 0x00, b'c', b'o', b'm', b'm', 0x00, 0xFF,
                0xFF, 0xFF, 0xBD, 0xB3, 0xFF, 0xFF, 0xFF, 0xFF,
                0x3B, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
                0xE2, 0x00, 0x00, 0x00, 0x41, 0x11, 0x20, 0x43,
                0xFF, 0xFF, 0xFF, 0xFF, 0x79, 0xAB, 0x00, 0x00,
                0xC2, 0x80, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78, 0xF6, 0xA4,
            ]),
            Ok(BitstreamMeta {
                verify_id: Some((ECP5IDCODE::LFE5U_45, 28)),
                spi_mode: Some((0xAB, 37)),
                stored_crcs: vec![ crc ],
                comp_dict: None,
            }),
        );
    }
}