# Copyright 2007 Jeff Epler # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # # Info about Xilinx bitfiles: # # The format consists of several variable length chunks, preceded by what # seems to be a constant header (a magic number). # # After the header, each "chunk" consists of a one byte "tag", a two or # four byte length, and "length" bytes of data (the body). # # In some chunks, the body is a zero terminated printable string. In # others it is a blob of binary data. The file format doesn't care. # # Standard Xilinx files use 5 chunks: 'a' through 'd' are zero-terminated # strings with information about the file. 'e' is a large binary blob # with the actual bitstream in it. Xilinx uses 2 byte lengths for chunks # 'a' thru 'd', and 4 bytes for chunk 'e'. This library allows other # chunks, and assume that all have 4 byte lengths, except 'a' thru 'd'. # import struct class BitFile: MAXCHUNKS = 50 SMALLCHUNKS = "abcd" MAGIC = "\x00\x09" \ "\x0f\xf0\x0f\xf0" \ "\x0f\xf0\x0f\xf0" \ "\x00\x00\x01" ORDER = "abcde" def __init__(self, chunks={}): self.chunks = dict(chunks) def __getitem__(self, item): return self.chunks[item] def __setitem__(self, item, value): self.chunks[item] = value def __delitem__(self, item): del self.chunks[item] def chunkorder(self, (tag, value)): if tag in self.ORDER: return self.ORDER.index(tag) return 256 + ord(tag) @classmethod def fromstring(cls, data): if not data.startswith(cls.MAGIC): raise ValueError, "data does not start with the magic number" i = len(cls.MAGIC) chunks = {} while i < len(data): tag = data[i] if tag in cls.SMALLCHUNKS: chunksize = struct.unpack(">H", data[i+1:i+3])[0] i = i + 3 else: chunksize = struct.unpack(">I", data[i+1:i+5])[0] i = i + 5 chunkdata = data[i:i+chunksize] if tag in chunks: raise ValueError, "bitfile has chunk %r more than once" % tag chunks[tag] = chunkdata i = i + chunksize return cls(chunks) @classmethod def fromfile(cls, file): return cls.fromstring(file.read()) @classmethod def fromfilename(cls, filename): return cls.fromstring(open(filename, "rb").read()) def tostring(self): result = self.MAGIC for tag, chunkdata in sorted(self.chunks.items(), key=self.chunkorder): if len(tag) != 1: raise ValueError, "Tag %r must be a length-1 string" % tag result = result + tag if tag in self.SMALLCHUNKS: result += struct.pack(">H", len(chunkdata)) else: result += struct.pack(">I", len(chunkdata)) result += chunkdata return result def tofile(self, file): return file.write(self.tostring()) def tofilename(self, filename): return open(filename, "wb").write(self.tostring()) if __name__ == '__main__': import sys if len(sys.argv) < 2: c = BitFile() c['a'] = "hello world" c['e'] = "goodbye" c['A'] = "shazam" s = c.tostring() print repr(s) d = BitFile.fromstring(s) print d.chunks assert d.tostring() == s for bitfile in sys.argv[1:]: bits = open(bitfile, "rb").read() c = BitFile.fromstring(bits) for k, v in sorted(c.chunks.items()): if k in 'abcd': print k, v else: print k, len(v) newbits = c.tostring() assert bits == newbits # Assuming the original is in canonical order! print