use std::{fs::File, path::Path, io::Read, collections::HashSet, ops::Range};
use num_enum::TryFromPrimitive;
use crate::{Result, Error, ECP5IDCODE};
mod parser;
pub struct Bitstream {
data: Vec<u8>,
meta: Option<BitstreamMeta>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct BitstreamMeta {
verify_id: Option<(ECP5IDCODE, usize)>,
spi_mode: Option<(u8, usize)>,
comp_dict: Option<[u8; 8]>,
stored_crcs: Vec<StoredCrc>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, TryFromPrimitive)]
#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
#[repr(u8)]
enum BitstreamCommandId {
DUMMY = 0xFF,
VERIFY_ID = 0xE2,
SPI_MODE = 0x79,
JUMP = 0x7E,
LSC_RESET_CRC = 0x3B,
LSC_WRITE_COMP_DIC = 0x02,
LSC_PROG_CNTRL0 = 0x22,
LSC_INIT_ADDRESS = 0x46,
LSC_WRITE_ADDRESS = 0xB4,
ISC_PROGRAM_SECURITY= 0xCE,
ISC_PROGRAM_USERCODE= 0xC2,
ISC_PROGRAM_DONE = 0x5E,
LSC_PROG_INCR_RTI = 0x82,
LSC_PROG_INCR_CMP = 0xB8,
LSC_PROG_SED_CRC = 0xA2,
LSC_EBR_ADDRESS = 0xF6,
LSC_EBR_WRITE = 0xB2,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct StoredCrc {
start: usize,
pos: usize,
excluded: HashSet<usize>,
}
impl Bitstream {
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
let mut file = File::open(path)?;
Self::from_file(&mut file)
}
pub fn from_file(file: &mut File) -> Result<Self> {
let mut data = if let Ok(metadata) = file.metadata() {
Vec::with_capacity(metadata.len() as usize)
} else {
Vec::new()
};
file.read_to_end(&mut data)?;
Ok(Self::new(data))
}
pub fn from_data(data: &[u8]) -> Self {
Self::new(data.to_owned())
}
pub fn new(data: Vec<u8>) -> Self {
let meta = match BitstreamMeta::parse(&data[..]) {
Ok(meta) => Some(meta),
Err(e) => {
log::warn!("Failed to parse bitstream: {e}.");
log::warn!("Proceeding without IDCODE/SPIMODE checks.");
None
},
};
Self { data, meta }
}
pub fn data(&self) -> &[u8] {
&self.data[..]
}
pub fn check_and_fix_idcode(&mut self, idcode: ECP5IDCODE, fix: bool) -> Result<()> {
let meta = match &mut self.meta {
Some(meta) => meta,
None => {
log::debug!("Skipping check_and_fix_idcode as no metadata parsed.");
return Ok(());
}
};
let verify_id = match meta.verify_id {
Some(verify_id) => verify_id,
None => {
log::debug!("Skipping check_and_fix_idcode as no VERIFY_ID command found.");
return Ok(());
}
};
log::info!(
"Checking bitstream IDCODE 0x{:08X} against JTAG IDCODE 0x{:08X}",
verify_id.0 as u32, idcode as u32
);
if verify_id.0 == idcode {
log::debug!("IDCODEs match exactly, no action required.");
Ok(())
} else if verify_id.0.compatible(idcode) {
log::warn!(
"Bitstream IDCODE 0x{:08X} ({}) is compatible with JTAG IDCODE 0x{:08X} ({}).",
verify_id.0 as u32, verify_id.0.name(), idcode as u32, idcode.name(),
);
if fix {
log::warn!("Patching programmed bitstream to match JTAG IDCODE. \
Use --no-fix-idcode to disable patching.");
for (idx, byte) in (idcode as u32).to_be_bytes().iter().enumerate() {
self.data[verify_id.1 + idx] = *byte;
}
self.fix_crc_change(verify_id.1..(verify_id.1 + 4));
Ok(())
} else {
log::warn!("Not patching because --no-fix-idcode was set.");
Err(Error::IncompatibleIdcode {
bitstream: verify_id.0 as u32,
jtag: idcode as u32
})
}
} else {
log::error!(
"Bitstream IDCODE 0x{:08X} ({}) is not compatible with JTAG IDCODE 0x{:08X} ({}).",
verify_id.0 as u32, verify_id.0.name(), idcode as u32, idcode.name(),
);
Err(Error::IncompatibleIdcode {
bitstream: verify_id.0 as u32,
jtag: idcode as u32
})
}
}
pub fn remove_idcode(&mut self) -> Result<()> {
let meta = match &mut self.meta {
Some(meta) => meta,
None => {
log::error!("Cannot remove VERIFY_IDCODE as no metadata was parsed.");
return Err(Error::RemoveIdcodeNoMetadata);
}
};
let verify_id = match meta.verify_id {
Some(verify_id) => verify_id,
None => {
log::debug!("Skipping remove_verify_id as no VERIFY_ID command found.");
return Ok(());
}
};
let start = verify_id.1 - 4;
let end = verify_id.1 + 4;
log::info!("Replacing VERIFY_ID command at bytes {start}..{end} with NOOP");
for x in &mut self.data[start..end] {
*x = 0xFF;
}
self.fix_crc_exclude(start..end);
Ok(())
}
pub fn remove_spimode(&mut self) -> Result<()> {
let meta = match &mut self.meta {
Some(meta) => meta,
None => {
log::debug!("Skipping remove_spi_mode as no metadata was parsed.");
return Ok(());
}
};
let spi_mode = match meta.spi_mode {
Some(spi_mode) => spi_mode,
None => {
log::debug!("Skipping remove_spi_mode as no SPI_MODE command found.");
return Ok(());
}
};
let start = spi_mode.1 - 1;
let end = spi_mode.1 + 3;
log::warn!("Removing SPI_MODE command for programming SRAM. \
Disable with --no-remove-spimode.");
log::info!("Replacing SPI_MODE command at bytes {start}..{end} with NOOP");
for x in &mut self.data[start..end] {
*x = 0xFF;
}
self.fix_crc_exclude(start..end);
Ok(())
}
fn fix_crc_change(&mut self, changes: Range<usize>) {
log::debug!("Fixing CRC affected by changes to offset {changes:?}");
for crc in self.meta.as_mut().unwrap().stored_crcs.iter_mut() {
if crc.start <= changes.start && crc.pos > changes.end {
let new_crc = crc.compute(&self.data);
log::trace!("Fixing CRC {crc:?} to 0x{new_crc:02X}");
self.data[crc.pos] = (new_crc >> 8) as u8;
self.data[crc.pos + 1] = new_crc as u8;
break;
} else if crc.start > changes.start {
log::trace!("No affected CRC found, skipping");
break;
}
}
}
fn fix_crc_exclude(&mut self, exclude: Range<usize>) {
log::debug!("Fixing CRC affected by excluding {exclude:?}");
for crc in self.meta.as_mut().unwrap().stored_crcs.iter_mut() {
if crc.start <= exclude.start && crc.pos > exclude.end {
for offset in exclude {
crc.exclude(offset);
}
let new_crc = crc.compute(&self.data);
log::trace!("Fixing CRC {crc:?} to 0x{new_crc:02X}");
self.data[crc.pos] = (new_crc >> 8) as u8;
self.data[crc.pos + 1] = new_crc as u8;
break;
} else if crc.start > exclude.start {
log::trace!("No affected CRC found, skipping");
break;
}
}
}
}
impl StoredCrc {
fn new() -> Self {
Self { start: 0, pos: 0, excluded: HashSet::new() }
}
fn start(&mut self, start: usize) {
self.start = start;
self.pos = start;
self.excluded.clear();
}
fn exclude(&mut self, idx: usize) {
self.excluded.insert(idx);
}
fn finish(&mut self, pos: usize) {
self.pos = pos;
}
fn compute(&self, data: &[u8]) -> u16 {
let mut crc = 0;
for (idx, byte) in data[self.start..self.pos].iter().enumerate() {
if self.excluded.contains(&(idx + self.start)) {
continue;
}
crc ^= (*byte as u16) << 8;
for _ in 0..8 {
if crc & 0x8000 != 0 {
crc = (crc << 1) ^ 0x8005;
} else {
crc <<= 1;
}
}
}
crc
}
}