245 lines
6.3 KiB
C++
245 lines
6.3 KiB
C++
#include "bitstreamverifier.h"
|
|
|
|
#include <QFile>
|
|
#include <QByteArray>
|
|
|
|
#include "mydebug.h"
|
|
|
|
struct xilinx_db_t {
|
|
const char* sign;
|
|
const char* name;
|
|
unsigned int expected_size;
|
|
};
|
|
|
|
static const xilinx_db_t xdb[]=
|
|
{
|
|
{"xq7z045", "ZynQ", 0},
|
|
{"xcku040", "Xilinx/CARRIER", 0},
|
|
{0,0, 0}
|
|
};
|
|
|
|
BitstreamVerifier::BitstreamVerifier()
|
|
{
|
|
|
|
}
|
|
|
|
class BitstreamDecoder
|
|
{
|
|
public:
|
|
BitstreamDecoder(const void* data, unsigned int size):
|
|
d((unsigned char*)data),
|
|
s(size),
|
|
pos(0)
|
|
|
|
{
|
|
}
|
|
|
|
unsigned int getFieldSize(unsigned int max_size=512)
|
|
{
|
|
unsigned short l=(d[pos]<<8)+d[pos+1];
|
|
if ((l>max_size) || ((pos+l)>s))
|
|
{
|
|
throw "overrun";
|
|
}
|
|
skip(2);
|
|
return l;
|
|
}
|
|
|
|
void skip(unsigned int len)
|
|
{
|
|
if ((pos+len)>=s)
|
|
throw "overrun";
|
|
pos+=len;
|
|
}
|
|
|
|
unsigned char getCode()
|
|
{
|
|
unsigned char c=d[pos];
|
|
skip(1);
|
|
return c;
|
|
}
|
|
|
|
const char* str()
|
|
{
|
|
return (const char*)&d[pos];
|
|
}
|
|
|
|
const unsigned char* d;
|
|
unsigned int s;
|
|
|
|
unsigned int pos;
|
|
};
|
|
|
|
bool BitstreamVerifier::verify(const QString& filename)
|
|
{
|
|
QFile f(filename);
|
|
|
|
bool ok=f.open(QIODevice::ReadOnly);
|
|
if (!ok)
|
|
return false;
|
|
|
|
QByteArray d=f.read(8*1024);
|
|
|
|
return verify(d);
|
|
}
|
|
|
|
bool BitstreamVerifier::verify(const QByteArray& d)
|
|
{
|
|
if (d.size()<8*1024)
|
|
return false;
|
|
|
|
BitstreamDecoder decoder(d.constData(), d.size());
|
|
|
|
try
|
|
{
|
|
//Field1: 9 bytes of some sort of header
|
|
unsigned int l=decoder.getFieldSize();
|
|
if (l!=9)
|
|
return false;
|
|
decoder.skip(l);
|
|
//Field 2: letter 'a'
|
|
l=decoder.getFieldSize();
|
|
if (l!=1)
|
|
return false;
|
|
unsigned char c=decoder.getCode();
|
|
if (c!='a')
|
|
return false;
|
|
//Field 3: a string with some info as "name;UserID=...;Version=...\0
|
|
l=decoder.getFieldSize();
|
|
const char* p=decoder.str();
|
|
if (p[l-1]!=0)
|
|
{
|
|
MyDebug()<<"BS:"<<"Unserminated string";
|
|
return false;
|
|
}
|
|
QString name(p);
|
|
auto nl=name.split(QChar(';'));
|
|
if (nl.size()<1)
|
|
return false;
|
|
m_info.name=nl[0];
|
|
for(int i=1; i<nl.size(); ++i)
|
|
{
|
|
QString tmp=nl[i];
|
|
auto vl=tmp.split(QChar('='));
|
|
if (vl.size()==2)
|
|
{
|
|
if (vl[0]=="UserID")
|
|
{
|
|
m_info.user=vl[1];
|
|
MyDebug()<<"BS:"<<"User="<<m_info.user;
|
|
}
|
|
if (vl[0]=="Version")
|
|
{
|
|
m_info.generator=vl[1];
|
|
MyDebug()<<"BS:"<<"Version="<<m_info.generator;
|
|
}
|
|
}
|
|
}
|
|
decoder.skip(l);
|
|
//Field 4: part num ('b')
|
|
c=decoder.getCode();
|
|
if (c!='b')
|
|
{
|
|
return false;
|
|
}
|
|
l=decoder.getFieldSize();
|
|
m_info.partNumber=decoder.str();
|
|
MyDebug()<<"BS:"<<"P/N"<<m_info.partNumber;
|
|
decoder.skip(l);
|
|
{//Rep type
|
|
for(int i=0; xdb[i].sign; ++i)
|
|
{
|
|
if (m_info.partNumber.startsWith(xdb[i].sign))
|
|
{
|
|
m_info.repType=xdb[i].name;
|
|
}
|
|
}
|
|
}
|
|
//Field 5: date ('c')
|
|
c=decoder.getCode();
|
|
if (c!='c')
|
|
return true;
|
|
l=decoder.getFieldSize();
|
|
m_info.date=decoder.str();
|
|
decoder.skip(l);
|
|
//Filed 6: time ('d')
|
|
c=decoder.getCode();
|
|
if (c!='d')
|
|
return true;
|
|
l=decoder.getFieldSize();
|
|
m_info.date=m_info.date+" "+decoder.str();
|
|
decoder.skip(l);
|
|
} catch(...)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
*
|
|
* http://www.fpga-faq.com/FAQ_Pages/0026_Tell_me_about_bit_files.htm
|
|
|
|
The Xilinx .bit format is pretty simple. It uses keys and lengths to divide the file.
|
|
Here is an example. Below is a hex dump from the beginning of a .bit file:
|
|
|
|
00000000: 00 09 0f f0 0f f0 0f f0 0f f0 00 00 01 61 00 0a .............a..
|
|
00000010: 78 66 6f 72 6d 2e 6e 63 64 00 62 00 0c 76 31 30 xform.ncd.b..v10
|
|
00000020: 30 30 65 66 67 38 36 30 00 63 00 0b 32 30 30 31 00efg860.c..2001
|
|
00000030: 2f 30 38 2f 31 30 00 64 00 09 30 36 3a 35 35 3a /08/10.d..06:55:
|
|
00000040: 30 34 00 65 00 0c 28 18 ff ff ff ff aa 99 55 66 04.e..(.......Uf
|
|
|
|
Field 1
|
|
2 bytes length 0x0009 (big endian)
|
|
9 bytes some sort of header
|
|
|
|
Field 2
|
|
2 bytes length 0x0001
|
|
1 byte key 0x61 (The letter "a")
|
|
|
|
Field 3
|
|
2 bytes length 0x000a (value depends on file name length)
|
|
10 bytes string design name "xform.ncd" (including a trailing 0x00)
|
|
|
|
Field 4
|
|
1 byte key 0x62 (The letter "b")
|
|
2 bytes length 0x000c (value depends on part name length)
|
|
12 bytes string part name "v1000efg860" (including a trailing 0x00)
|
|
|
|
Field 4
|
|
1 byte key 0x63 (The letter "c")
|
|
2 bytes length 0x000b
|
|
11 bytes string date "2001/08/10" (including a trailing 0x00)
|
|
|
|
Field 5
|
|
1 byte key 0x64 (The letter "d")
|
|
2 bytes length 0x0009
|
|
9 bytes string time "06:55:04" (including a trailing 0x00)
|
|
|
|
Field 6
|
|
1 byte key 0x65 (The letter "e")
|
|
4 bytes length 0x000c9090 (value depends on device type,
|
|
and maybe design details)
|
|
8233440 bytes raw bit stream starting with 0xffffffff aa995566 sync
|
|
word documented below.
|
|
|
|
Once you get the raw bits, XAPP138 "Virtex FPGA Series Configuration and
|
|
Readback" and XAPP139 "Configuration and Readback of Virtex FPGAs using
|
|
(JTAG) Boundary-Scan" will tell you what to do with them.
|
|
|
|
Note the "Enable .bit File Compression" option doesn't change the file
|
|
format at all. It only changes how the bitstream is interpreted by the
|
|
configuration state machine inside the Xilinx part. (and of course the length of the file)
|
|
|
|
The following documents at Xilinx are also great evening reading
|
|
|
|
http://www.xilinx.com/bvdocs/appnotes/xapp138.pdf
|
|
|
|
http://www.xilinx.com/bvdocs/appnotes/xapp139.pdf
|
|
|
|
http://www.xilinx.com/bvdocs/appnotes/xapp151.pdf
|
|
*/
|