Hi,
thanks for the description! I was trying to decompress an EP3C25 bitstream. I found the description with "swapping nibbles" slightly confusing as in the end it's really just if it's "most significant nibble" or "least significant nibble" first (it's LSN). Also, depending on how the compressed bitstream is stored, it may be bit-reversed. (An .RBF file that I created compressed isn't reversed, but one that I dumped from flash apparently is).
Also there's a header block (that probably isn't fixed-size, but I was too lazy to figure out the details) that goes through uncompressed.
So, here's my implementation - i partially verified the result by producing a compressed and uncompressed image with quartus, and then checking that the images are identical (other than some bits in the header that likely indicate whether the bitstream is compressed; they would have to be reset).
Replace the usage of "nibbler" with "nibbler_bitrev" if your input file is bit-reversed. A good indication is the data at 0x20: It's either 56 EF (then it's reversed), or 6A F7 (then it's unreversed), but then again I don't know what the header means...
Next step (for me): extracting M9K contents.
import sys
data = open(sys.argv[1], "rb")
bitrev = [0b0000, 0b1000, 0b0100, 0b1100, 0b0010, 0b1010, 0b0110, 0b1110, 0b0001, 0b1001, 0b0101, 0b1101, 0b0011, 0b1011, 0b0111, 0b1111]
for i in range(16):
assert bitrev[bitrev[i]] == i
def nibbler_bitrev(data):
for i in range(len(data)):
yield bitrev[(data[i] >> 4) & 0xF]
yield bitrev[data[i] & 0xF]
def nibbler(data):
for i in range(len(data)):
yield data[i] & 0xF
yield (data[i] >> 4) & 0xF
n = nibbler(data)
def decompress(n):
while True:
presence = next(n)
for i in range(4):
if presence & 1:
yield next(n)
else:
yield 0
presence >>= 1
def denibble(n):
while True:
a = next(n)
a |= next(n) << 4
yield a
with open(sys.argv[2], "wb") as fo:
be = 0x1d3a # pass through header
fo.write(data[:be])
fo.write(bytes(denibble(decompress(nibbler(data[be:])))))