use jtag_idcode;
use std::collections::HashMap;
use std::io::Read;
mod error;
mod port;
mod scancell;
mod string_helpers;
pub use error::BsdlError;
use crate::port::Ports;
pub use crate::port::{Port, PortDirection};
pub use crate::scancell::ScanCell;
use crate::string_helpers::*;
pub use scancell::CellFunction;
pub use scancell::CellType;
pub use scancell::DisableResult;
pub use scancell::LogicVal;
#[derive(Debug, Clone)]
pub struct Item {
pub itemtype: String,
pub content: String,
pub line: usize,
}
impl Item {
fn new() -> Item {
Item {
itemtype: String::new(),
content: String::new(),
line: 0,
}
}
}
#[derive(Debug)]
pub struct Entity {
name: String,
use_statements: Vec<String>,
component_conformance: String,
opcodes: HashMap<String, u64>,
instruction_length: usize,
boundary_length: usize,
boundary_register: Vec<ScanCell>,
idcode_register: Vec<jtag_idcode::IDCodeMasked>,
attributes: HashMap<String, Item>,
generics: HashMap<String, String>,
ports: Ports,
maxtck_hz: Option<f32>,
tck_both: bool,
}
impl std::fmt::Display for Entity {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let name = &self.name;
write!(f, "name: {name}")
}
}
impl Entity {
pub fn name(&self) -> &str {
&self.name.as_str()
}
pub fn instruction_length(&self) -> usize {
self.instruction_length
}
pub fn boundary_length(&self) -> usize {
self.boundary_length
}
pub fn opcodes(&self) -> &HashMap<String, u64> {
&self.opcodes
}
pub fn ports(&self) -> &Vec<Port> {
&self.ports.v
}
pub fn port_from_pinname(&self, name: &str) -> Option<&Port> {
if let Some(idx) = self.ports.d_pin.get(name) {
Some(&self.ports.v[*idx])
} else {
None
}
}
pub fn port_from_portname(&self, name: &str) -> Option<&Port> {
if let Some(idx) = self.ports.d_port.get(name) {
Some(&self.ports.v[*idx])
} else {
None
}
}
pub(crate) fn port_number_from_portname(&self, name: &str) -> Option<usize> {
if let Some(idx) = self.ports.d_port.get(name) {
Some(*idx)
} else {
None
}
}
pub fn boundary_register(&self) -> &Vec<ScanCell> {
&self.boundary_register
}
pub fn use_statements(&self) -> &Vec<String> {
&self.use_statements
}
pub fn component_conformance(&self) -> &str {
&self.component_conformance.as_str()
}
pub fn attributes(&self) -> &HashMap<String, Item> {
&self.attributes
}
pub fn generics(&self) -> &HashMap<String, String> {
&self.generics
}
pub fn tck_both(&self) -> bool {
self.tck_both
}
pub fn idcode_register(&self) -> &Vec<jtag_idcode::IDCodeMasked> {
&self.idcode_register
}
pub fn maxtck_hz(&self) -> Option<f32> {
self.maxtck_hz
}
pub fn get_safe(&self, a: &mut [u8]) -> Result<(), BsdlError> {
let nbytes = (self.boundary_length + 7) >> 3;
if a.len() < nbytes {
return Err(BsdlError::OverflowError);
}
for i in 0..a.len() {
a[i] = 0;
}
for i in 0..self.boundary_length {
let bit = &self.boundary_register[i].safe;
if *bit == LogicVal::One {
let byte = i >> 3;
let bit = 1 << (i & 7);
a[byte] |= bit;
}
}
Ok(())
}
pub fn parse(filename: &std::path::Path, pinmap: Option<&str>) -> Result<Entity, BsdlError> {
let mut file = std::fs::File::open(filename)?;
let mut content = Vec::new();
file.read_to_end(&mut content)?;
let buf = String::from_utf8_lossy(&content);
let mut lines = buf.lines().enumerate();
let mut bsdl = Entity {
name: String::new(),
use_statements: Vec::new(),
component_conformance: String::new(),
opcodes: HashMap::new(),
instruction_length: 0,
boundary_length: 0,
boundary_register: Vec::new(),
idcode_register: Vec::new(),
attributes: HashMap::new(),
generics: HashMap::new(),
ports: Ports::new(),
maxtck_hz: None,
tck_both: false,
};
loop {
if let Some((i, line)) = lines.next() {
let line = strip_whitespace_comments(line);
if line.len() == 0 {
continue;
}
let line = line.to_lowercase();
let mut s = line.split_whitespace();
if s.next() != Some("entity") {
return Err(BsdlError::ParseError(format!(
"in {filename:?} at line {i} expected 'entity' found {line}"
)));
}
match s.next() {
Some(entity) => {
bsdl.name.push_str(entity);
}
None => {
return Err(BsdlError::ParseError(format!(
"in {filename:?} at line {i} expected 'entity' found {line}"
)));
}
}
if s.next() != Some("is") {
return Err(BsdlError::ParseError(format!(
"in {filename:?} at line {i} expected 'is' as 3rd word, found {line}"
)));
}
break;
} else {
return Err(BsdlError::ParseError(format!(
"in {filename:?} failed to find 'entity'"
)));
}
}
loop {
let mut item = Item::new();
let mut openparen = 0usize; let mut quote_continues = false;
let mut first = true; 'inner: loop {
if let Some((i, line)) = lines.next() {
let mut line = strip_whitespace_comments(line);
if line.len() == 0 {
continue;
}
if first {
item.line = i + 1;
first = false;
let space = line.find(char::is_whitespace);
match space {
Some(space) => {
item.itemtype = line[0..space].to_lowercase();
line = strip_whitespace_comments(&line[space + 1..]);
if line.len() == 0 {
continue;
}
}
None => {
item.itemtype = line.to_lowercase();
continue;
}
}
}
let mut in_quote = false;
for char in line.chars() {
if quote_continues & (char != '"') {
if char.is_whitespace() {
continue;
}
return Err(BsdlError::ParseError(format!(
"in {filename:?} at line {i} unexpected character {char} after quote continuation"
)));
} else if in_quote {
if char == '"' {
in_quote = false;
}
item.content.push(char);
} else if char == '&' {
quote_continues = true;
} else if char == '"' {
in_quote = true;
if quote_continues {
quote_continues = false;
while item.content.pop() != Some('"') {}
item.content.push(' ');
} else {
item.content.push(char);
}
} else if (openparen == 0) & (char == ';') {
break 'inner;
} else {
if char.is_ascii() {
item.content.push(char.to_ascii_lowercase());
} else {
item.content.push(char);
}
if char == '(' {
openparen += 1;
} else if char == ')' {
if openparen == 0 {
return Err(BsdlError::ParseError(format!(
"in {filename:?} at line {i} parenthesis closed but not opened"
)));
}
openparen -= 1;
}
}
}
if in_quote {
return Err(BsdlError::ParseError(format!(
"in {filename:?} at line {i} quotation not closed: {line}"
)));
}
item.content.push(' ');
} else {
return Err(BsdlError::ParseError(format!(
"in {filename:?} unexpected EOF"
)));
}
}
match item.itemtype.as_str() {
"attribute" => bsdl.parse_attribute(&item),
"constant" => bsdl.parse_constant(&item, pinmap),
"port" => bsdl.parse_port(&item),
"use" => bsdl.parse_use(&item),
"generic" => bsdl.parse_generic(&item),
"end" => {
break;
}
_ => Ok(()),
}
.map_err(|e| {
BsdlError::ParseError(format!("in {:?} line {} {:?}", filename, item.line, e))
})?;
}
Ok(bsdl)
}
fn parse_attribute(&mut self, item: &Item) -> Result<(), BsdlError> {
let s = item.content.as_str();
let (name, s2) = s.split_once("of").ok_or(BsdlError::AttributeError)?;
let name = name.trim();
let (of, of_type) = s2.split_once(":").ok_or(BsdlError::AttributeError)?;
let (of_type, val) = of_type.split_once("is").ok_or(BsdlError::AttributeError)?;
let of = of.trim();
let of_type = of_type.trim();
let val = val.trim();
match name {
"boundary_length" => {
self.boundary_length = usize::from_str_radix(val, 10)?;
}
"boundary_register" => {
if of != self.name {
return Err(BsdlError::ParseError(format!(
"boundary register 'of' parameter not same as entity"
)));
}
if of_type != "entity" {
return Err(BsdlError::ParseError(format!(
"boundary register 'of' type must be entity"
)));
}
let mut val = strip_prefix(val, '"')?;
self.boundary_register = vec![ScanCell::default(); self.boundary_length];
while val.len() != 0 {
val = val.trim_start();
let (num, rest) = val
.split_once(char::is_whitespace)
.ok_or(BsdlError::AttributeError)?;
let cellnum = usize::from_str_radix(num, 10).map_err(|_| {
BsdlError::ParseError(format!(
"Error parsing BOUNDARY_REGISTER cell number {num}"
))
})?;
if cellnum > self.boundary_length {
return Err(BsdlError::ParseError(format!(
"boundary register index out of range"
)));
}
let (in_paren, rest) = split_parenthesis(rest)?;
let bsc = ScanCell::from_string(in_paren, self, cellnum);
self.boundary_register[cellnum] = bsc?;
val = rest.trim_start();
if let Some(_) = val.strip_prefix(',') {
val = val.strip_prefix(',').ok_or(BsdlError::AttributeError)?;
val = val.trim_start();
} else {
break;
}
}
}
"instruction_length" => {
self.instruction_length = val.parse::<usize>().map_err(|_| {
BsdlError::ParseError(format!("instruction_length {val} invalid"))
})?;
if self.instruction_length > 64 {
return Err(BsdlError::ParseError(format!(
"instruction_length {} exceeds limit of 64",
self.instruction_length
)));
}
let bypass = match self.instruction_length {
64 => u64::MAX,
x => (1 << x) - 1,
};
self.opcodes.insert("BYPASS".to_string(), bypass);
self.opcodes.insert("EXTEST".to_string(), 0);
}
"instruction_opcode" => {
self.parse_opcodes(val)?;
}
"idcode_register" => {
let val = strip_quotes(val)?;
for substr in val.split(',') {
self.idcode_register
.push(jtag_idcode::IDCodeMasked::from_str(substr)?);
}
}
"tap_scan_clock" => {
let val = strip_parenthesis(val)?;
let (freq_str, edge) = val.split_once(',').ok_or(BsdlError::AttributeError)?;
self.tck_both = edge.trim() == "both";
let freq = freq_str.parse::<f32>();
match freq {
Ok(freq) => {
self.maxtck_hz = Some(freq);
}
_ => return Err(BsdlError::AttributeError),
}
}
"port_grouping" => {
self.ports.apply_port_grouping(val)?;
}
_ => {}
}
self.attributes.insert(name.to_uppercase(), item.clone());
Ok(())
}
fn parse_opcodes(&mut self, val: &str) -> Result<(), BsdlError> {
let mut val = strip_quotes(val)?;
loop {
val = val.trim();
if val.len() == 0 {
return Ok(());
}
let comma = find_first_comma_outside_paren(val)?;
let a = if let Some(comma) = comma {
let rv = &val[..comma];
val = &val[comma + 1..];
rv
} else {
let rv = val;
val = "";
rv
};
let (a, b) = a.split_once('(').ok_or(BsdlError::AttributeError)?;
let a = a.trim();
let b = b.trim();
let b = b.strip_suffix(')').ok_or(BsdlError::AttributeError)?;
let b = b.trim();
let comma = b.find(',');
let opcode_str: &str = match comma {
Some(x) => &b[..x].trim(),
None => b,
};
let instr = a.to_uppercase();
let mut opcode = 0u64;
for c in opcode_str.chars() {
match c {
'0' | 'x' | 'X' => {
if opcode & (1 << 63) != 0 {
return Err(BsdlError::ParseError(format!(
"opcode {instr} exceeds 64 bit limit"
)));
}
opcode = opcode << 1;
}
'1' => {
if opcode & (1 << 63) != 0 {
return Err(BsdlError::ParseError(format!(
"opcode {instr} exceeds 64 bit limit"
)));
}
opcode = opcode << 1 | 1;
}
' ' | '\t' => {}
_ => {
return Err(BsdlError::ParseError(format!(
"opcode {instr} contains invalid character _{c}_"
)));
}
}
}
self.opcodes.insert(instr, opcode);
}
}
fn parse_port(&mut self, item: &Item) -> Result<(), BsdlError> {
let s = item.content.as_str();
self.ports = Ports::from_str(s)?;
Ok(())
}
fn parse_constant(&mut self, item: &Item, pinmap: Option<&str>) -> Result<(), BsdlError> {
let s = item.content.as_str();
let s = s.trim();
let (name, rest) = s.split_once(':').ok_or(BsdlError::ConstantError)?;
let (ctype, rest) = rest.split_once(":=").ok_or(BsdlError::ConstantError)?;
let name = name.trim();
let ctype = ctype.trim();
let rest = rest.trim();
match ctype {
"pin_map_string" => {
self.parse_pin_map(name, rest, pinmap)?;
}
_ => {}
}
Ok(())
}
fn parse_pin_map(
&mut self,
name: &str,
s: &str,
pinmap: Option<&str>,
) -> Result<(), BsdlError> {
if let Some(p) = pinmap {
if name != p {
return Ok(());
}
}
if self.ports.pinmap_applied {
return Ok(());
}
self.ports.apply_pinmap(s)?;
Ok(())
}
fn parse_use(&mut self, item: &Item) -> Result<(), BsdlError> {
let s = item.content.as_str();
self.use_statements.push(s.to_string());
Ok(())
}
fn parse_generic(&mut self, item: &Item) -> Result<(), BsdlError> {
let s = item.content.as_str();
let s = strip_parenthesis(s)?;
let (k, v) = s.split_once(':').ok_or(BsdlError::GenericError)?;
let k = k.trim();
let v = v.trim();
let (gtype, v) = v.split_once(":=").ok_or(BsdlError::GenericError)?;
let gtype = gtype.trim();
let v = if gtype == "string" {
strip_quotes(v)?
} else {
v
};
self.generics.insert(k.to_string(), v.to_string());
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::OsStr;
use std::fs;
use std::path::PathBuf;
#[test]
fn read_bsdl() {
let home = PathBuf::from(env!("HOME"));
let bsdl_file = home
.join("software")
.join("bsdl")
.join("xcsu35p_sbvb625.bsd");
let bsdl = Entity::parse(&bsdl_file, None);
println!("{bsdl:?}");
let k = bsdl.unwrap();
println!("opcodes = {:?}", k.opcodes);
println!("idcodes = {:?}", k.idcode_register);
let bsdl_file = home
.join("software")
.join("bsdl")
.join("ccgm1a1_fbga324.bsd");
let bsdl = Entity::parse(&bsdl_file, None);
let bsdl = bsdl.unwrap();
println!("parse successful, entity = {}", bsdl);
}
#[test]
fn test_example() {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let test_bsd = manifest_dir
.join("tests")
.join("test_data")
.join("sample.bsd");
println!("dir = {manifest_dir:?}, {test_bsd:?}");
let bsdl = Entity::parse(&test_bsd, None);
if bsdl.is_err() {
println!("error {bsdl:?}");
}
let bsdl = bsdl.unwrap();
println!("opcode USER = {:?}", bsdl.opcodes.get("USER"));
let idcode = bsdl.idcode_register;
println!(
"idcode = {} vendor = {:?}",
idcode[0],
idcode[0].vendor_string()
);
println!("generics = {:?}", bsdl.generics);
println!("use_statements = {:?}", bsdl.use_statements);
println!("ports = {}", bsdl.ports);
println!("bscs = {:?}", bsdl.boundary_register);
}
#[test]
fn test_many() {
let home = PathBuf::from(env!("HOME"));
let bsdl_dir = home.join("software").join("bsdl");
println!("bsdl_dir = {:?}", bsdl_dir);
let mut count = 0;
if let Ok(dir) = fs::read_dir(bsdl_dir) {
for entry in dir {
if let Ok(entry) = entry {
let path = entry.path();
let is_bsd = path.extension().and_then(OsStr::to_str) == Some("bsd");
if !is_bsd {
continue;
}
let bsdl = Entity::parse(&path, None);
println!("entry = {:?} {}", path, bsdl.is_ok());
if bsdl.is_err() {
println!("error {:?}", bsdl);
panic!();
}
count += 1;
}
}
}
println!("processed {count} bsdls");
}
}