summaryrefslogtreecommitdiff
path: root/lib/python/bitfile.py
blob: c9d0df84ee9568fa7c128ddfa7d50ce83b4ce3a8 (plain)
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
#    Copyright 2007 Jeff Epler <jepler@unpythonic.net>
#
#    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