Merge pull request #74 from AndresNavarro82/machxo2-pullreq0
Bitstream decompression and better handling of crc & dummies (so they also work with MachXO2)
diff --git a/docs/architecture/bitstream_format.rst b/docs/architecture/bitstream_format.rst
index 273dfd5..3b7234d 100644
--- a/docs/architecture/bitstream_format.rst
+++ b/docs/architecture/bitstream_format.rst
@@ -22,7 +22,7 @@
The CRC16 is accumulated over all commands until a CRC16 check is reached. It is not reset at the end of commands
without a CRC16 check - except the ``LSC_RESET_CRC`` command, and after the actual bitstream payload
-(``LSC_PROG_INCR_RTI``).
+(``LSC_PROG_INCR_RTI`` or ``LSC_PROG_INCR_CMP``).
The CRC16 is calculated using the polynomial 0x8005 with no bit reversal. This algorithm is sometimes known as
"CRC16-BUYPASS".
@@ -45,6 +45,11 @@
| ``VERIFY_ID`` | E2 | - 24 bit info: all 0 | This checks the actual device ID against the given|
| | | - 32 bit device JTAG ID | value and fails if they do not match. |
+-------------------------------+-----+--------------------------+---------------------------------------------------+
+| ``LSC_WRITE_COMP_DIC`` | 02 | - 24 bit info: all 0 | This stores the 8 most common bytes in the frames |
+| | | - 8 bit Pattern7 | |
+| | | - ... (6 more patterns) | |
+| | | - 8 bit Pattern0 | |
++-------------------------------+-----+--------------------------+---------------------------------------------------+
| ``LSC_PROG_CNTRL0`` | 22 | - 24 bit info: all 0 | This sets the value of device control register 0 |
| | | - 32 bit CtlReg0 value | Normally 0x40000000 |
+-------------------------------+-----+--------------------------+---------------------------------------------------+
@@ -60,7 +65,8 @@
Configuration Data
-------------------
-The FPGA configuration data itself is programmed by using command ``LSC_PROG_INCR_RTI`` (0x82). Following this command,
+The FPGA configuration data itself is programmed by using command ``LSC_PROG_INCR_RTI`` (0x82) if no compression is
+used and command ``LSC_PROG_INCR_CMP`` (0xB8) when using compression. Following either of these commands,
there are some setup bits:
- 1 bit: CRC16 comparison flag, normally set
@@ -72,21 +78,59 @@
This is then followed by a number of frames, each in the following format:
- - The configuration frame itself, such that bit 0 of the first byte sent is the MSB of the frame,
- bit 7 of the first byte the MSB-7 and bit 0 of the last byte (if there are no dummy bits) being the LSB of the frame.
- - Any dummy bits needed to pad the frame to a whole number of bytes
- - A CRC-16 checksum:
-
- - For the first frame, this also covers any other commands sent
- before the programming command but after a CRC reset, and the programming command itself.
+ - The configuration frame itself (compressed in the case of the ``LSC_PROG_INCR_RTI`` command),
+ such that bit 0 bit 0 of the first byte is the MSB of the frame, bit 7 of the first byte the
+ MSB-7 and bit 0 of the last byte (if there are no dummy bits) being the LSB of the frame.
+ - Any dummy bits needed to pad the frame to a whole number of bytes.
+ - If the second flag is cleared (see above) a CRC-16 checksum:
+ - For the first frame, this also covers any other commands sent before the programming command
+ but after a CRC reset, and the programming command itself.
- For subsequent frames, this excludes dummy bytes between frames
- Dummy 0xFF bytes, usually only 1
The highest numbered frame in the chip is sent first.
-Separate commands are used if EBR needs to be configured in the bitstream. ``EBR_ADDRESS`` (0xF6) is used to select the
-EBR to program and the starting address in the EBR; and ``LSC_EBR_WRITE`` (0xB2) is used to program the EBR itself using
-72-bit frames. The specifics of these still need to be documented.
+If the second flag is set there's no CRC sent in between frames but there's still one CRC-16 checksum
+after all the frames (this also covers any other commands sent before the programming command but after a CRC reset,
+and the programming command itself.).
+
+Separate commands are used if EBR needs to be configured in the bitstream. EBR data can't use compression.
+``EBR_ADDRESS`` (0xF6) is used to select the EBR to program and the starting address in the EBR;
+and ``LSC_EBR_WRITE`` (0xB2) is used to program the EBR itself using 72-bit frames. The specifics of these
+still need to be documented.
+
+Compression Algorithm
+------------------------------
+
+ - Before compression, the frame is left padded with zeroes (0) to make the data frame 64-bit bounded.
+ - After compressing the frame data, the resulting bits are right padded with zeroes (0) to make the data
+frame byte bounded.
+
+After padding, every byte in the bitstream is compressed by a simple prefix-free code with just 4 cases:
+
++--------------+--------------+--------+---------------------+
+| Code | Argument | Length | Encoded byte |
++==============+==============+========+=====================+
+| 0 | | 1 | zero |
++--------------+--------------+--------+---------------------+
+| 100xxx | bit position | 6 | byte with 1 bit set |
++--------------+--------------+--------+---------------------+
+| 101xxx | byte index | 6 | stored byte |
++--------------+--------------+--------+---------------------+
+| 11xxxxxxxx | lit. byte | 10 | all others |
++--------------+--------------+--------+---------------------+
+
+- The first case is for the byte zero (``00000000``). That's just represented by a single zero bit (``0``).
+- The second case is for bytes with just one bit set. After a ``100`` the set bit position is encoded
+ in the following 3 bits. For example the byte ``00100000`` is encoded as ``100101`` because only the
+ bit 5 is set (with 0 being the lsb and 7 the msb).
+- The third case is for selecting one of the bytes stored by the ``LSC_WRITE_COMP_DIC`` instruction. Those
+ bytes are selected as the 8 most common bytes (ignoring the zero bytes and the bytes with just one bit set,
+ because those are encoded with the two previous cases). After a ``101`` the number of the selected pattern
+ is encoded with 3 bits. For example to select pattern3 the code would be ``101011``.
+- The fourth case is for all remaining bytes. In that case after a ``11`` the complete byte is copied. For example
+ byte ``11001010`` would be encoded as ``1111001010``.
+
Device-Specific Information
------------------------------
@@ -105,4 +149,4 @@
| LFE5U-85 | 0x41113043 | 13294 | 1136 | 0 |
+-----------+-------------+--------+-----------------------+----------------------+
| LFE5UM-85 | 0x01113043 | 13294 | 1136 | 0 |
-+-----------+-------------+--------+-----------------------+----------------------+
\ No newline at end of file
++-----------+-------------+--------+-----------------------+----------------------+
diff --git a/libtrellis/include/Bitstream.hpp b/libtrellis/include/Bitstream.hpp
index 72c475b..3d04cb6 100644
--- a/libtrellis/include/Bitstream.hpp
+++ b/libtrellis/include/Bitstream.hpp
@@ -51,6 +51,7 @@
static Bitstream generate_jump(uint32_t address);
// Deserialise a bitstream to a Chip
+ Chip deserialise_chip();
Chip deserialise_chip(boost::optional<uint32_t> idcode = boost::optional<uint32_t>());
// Write a Lattice .bit file (metadata + bitstream)
diff --git a/libtrellis/src/Bitstream.cpp b/libtrellis/src/Bitstream.cpp
index afaefc2..1ea0bdb 100644
--- a/libtrellis/src/Bitstream.cpp
+++ b/libtrellis/src/Bitstream.cpp
@@ -9,6 +9,7 @@
#include <boost/optional.hpp>
#include <iomanip>
#include <fstream>
+#include <array>
namespace Trellis {
@@ -69,6 +70,18 @@
return val;
}
+ // The command opcode is a byte so this works like get_byte
+ // but doesn't update the CRC if it's a dummy, because the docs
+ // says that dummy commands don't update the crc
+ inline BitstreamCommand get_command_opcode() {
+ assert(iter < data.end());
+ uint8_t val = *(iter++);
+ BitstreamCommand cmd = BitstreamCommand(val);
+ if (cmd != BitstreamCommand::DUMMY)
+ update_crc16(val);
+ return cmd;
+ }
+
// Write a single byte and update CRC
inline void write_byte(uint8_t b) {
data.push_back(b);
@@ -84,6 +97,83 @@
}
}
+ // Decompress and copy multiple bytes into an OutputIterator and update CRC
+ template<typename T>
+ void get_compressed_bytes(T out, size_t count, array<uint8_t, 8> compression_dict) {
+ // Here we store data already read by read_byte(), it may be more than 1 byte at times!!
+ uint16_t read_data = 0;
+ size_t remaining_bits = 0;
+ bool next_bit;
+
+ uint8_t udata;
+
+ //
+ // Every byte can be encoded by on of 4 cases
+ // It's a prefix-free code so we can identify each one just by looking at the first bits:
+ // 0 -> Byte zero (0000 0000)
+ // 100 xxx -> Stored byte in compression_dict, xxx is the index (0-7)
+ // 101 xxx -> Byte with a single bit set, xxx is the index of the set bit (0 is lsb, 7 is msb)
+ // 11 xxxxxxxx -> Literal byte, xxxxxxxx is the encoded byte
+ //
+ for (size_t i = 0; i < count; i++) {
+ // Make sure we have at least one bit in the buffer
+ if (!remaining_bits) {
+ read_data = (uint32_t) get_byte();
+ remaining_bits = 8;
+ }
+ next_bit = bool(read_data >> (remaining_bits-1) & 1);
+ remaining_bits--;
+
+ // Check the 4 cases leaving the uncompressed byte in udata
+ if (next_bit) {
+ // Starts with 1, so check next bit/bits
+ // For each of the 3 remaining cases we will need at least 5 more bits,
+ // so if we have less than that it's ok to read another byte
+ if (remaining_bits < 5) {
+ read_data = (read_data << 8) | ((uint32_t) get_byte());
+ remaining_bits += 8;
+ }
+ next_bit = bool(read_data >> (remaining_bits-1) & 1);
+ remaining_bits--;
+
+ if (next_bit) {
+ // 11 xxxx xxxx: Literal byte, just read the next 8 bits & use that
+ // we consumed 10 bits total
+ if (remaining_bits < 8) {
+ read_data = (read_data << 8) | ((uint32_t) get_byte());
+ remaining_bits += 8;
+ }
+ udata = uint8_t((read_data >> (remaining_bits - 8)) & 0xff);
+ remaining_bits -= 8;
+ } else {
+ // Starts with 10, it could be a stored literal or a single-bit-set byte
+ // 10 ? xxx: In both cases we need the index xxx, so extract it now
+ // We already have all the bits we need buffered
+ next_bit = bool(read_data >> (remaining_bits-1) & 1);
+ remaining_bits--;
+ size_t idx = (size_t) ((read_data >> (remaining_bits-3)) & 0x7);
+ remaining_bits -= 3;
+ if (next_bit) {
+ // 101 xxx: Stored byte. Just use xxx as index in the dictionary,
+ // we consumed 6 bits
+ udata = compression_dict[idx];
+ } else {
+ // 100 xxx: Single-bit-set byte, xxx is the index of the set bit
+ // we consumed 6 bits
+ udata = uint8_t(1 << idx);
+ }
+ }
+ } else {
+ // 0: the uncompressed byte is zero
+ // we consumed just one bit
+ udata = 0;
+ }
+ *out = udata;
+ ++out;
+ }
+ // if remaining bits > 0 they are just padding bits added to the end so we can ignore them
+ }
+
// Write multiple bytes from an InputIterator and update CRC
template<typename T>
void write_bytes(T in, size_t count) {
@@ -101,18 +191,6 @@
for (size_t i = 0; i < count; i++) write_byte(0x00);
}
- // Skip over a possible-dummy command section of N bytes, updating CRC only if command is not 0xFF
- uint8_t skip_possible_dummy(int size) {
- uint8_t cmd = *(iter++);
- if (cmd == 0xFF) {
- iter += (size - 1);
- } else {
- update_crc16(cmd);
- skip_bytes(size - 1);
- }
- return cmd;
- }
-
// Insert dummy bytes into the bitstream, without updating CRC
void insert_dummy(size_t count) {
for (size_t i = 0; i < count; i++)
@@ -236,11 +314,17 @@
static const vector<uint8_t> preamble = {0xFF, 0xFF, 0xBD, 0xB3};
+Chip Bitstream::deserialise_chip() {
+ return deserialise_chip(boost::none);
+}
+
Chip Bitstream::deserialise_chip(boost::optional<uint32_t> idcode) {
cerr << "bitstream size: " << data.size() * 8 << " bits" << endl;
BitstreamReadWriter rd(data);
boost::optional<Chip> chip;
bool found_preamble = rd.find_preamble(preamble);
+ boost::optional<array<uint8_t, 8>> compression_dict;
+
if (!found_preamble)
throw BitstreamParseError("preamble not found in bitstream");
@@ -248,8 +332,8 @@
int addr_in_ebr = 0;
while (!rd.is_end()) {
- uint8_t cmd = rd.get_byte();
- switch ((BitstreamCommand) cmd) {
+ BitstreamCommand cmd = rd.get_command_opcode();
+ switch (cmd) {
case BitstreamCommand::LSC_RESET_CRC:
BITSTREAM_DEBUG("reset crc");
rd.skip_bytes(3);
@@ -293,40 +377,88 @@
rd.check_crc16();
}
break;
+ case BitstreamCommand::LSC_WRITE_COMP_DIC: {
+ bool check_crc = (rd.get_byte() & 0x80) != 0;
+ rd.skip_bytes(2);
+ compression_dict = boost::make_optional(array<uint8_t, 8>());
+ // patterns are stored in the bitstream in reverse order: pattern7 to pattern0
+ for (int i = 7; i >= 0; i--) {
+ uint8_t pattern = rd.get_byte();
+ compression_dict.get()[i] = pattern;
+ }
+ BITSTREAM_DEBUG("write compression dictionary: " <<
+ "0x" << hex << setw(2) << setfill('0') << int(compression_dict.get()[0]) << " " <<
+ "0x" << hex << setw(2) << setfill('0') << int(compression_dict.get()[1]) << " " <<
+ "0x" << hex << setw(2) << setfill('0') << int(compression_dict.get()[2]) << " " <<
+ "0x" << hex << setw(2) << setfill('0') << int(compression_dict.get()[3]) << " " <<
+ "0x" << hex << setw(2) << setfill('0') << int(compression_dict.get()[4]) << " " <<
+ "0x" << hex << setw(2) << setfill('0') << int(compression_dict.get()[5]) << " " <<
+ "0x" << hex << setw(2) << setfill('0') << int(compression_dict.get()[6]) << " " <<
+ "0x" << hex << setw(2) << setfill('0') << int(compression_dict.get()[7]));;
+ if (check_crc)
+ rd.check_crc16();
+ }
+ break;
case BitstreamCommand::LSC_INIT_ADDRESS:
rd.skip_bytes(3);
BITSTREAM_DEBUG("init address");
break;
+ case BitstreamCommand::LSC_PROG_INCR_CMP:
+ // This is the main bitstream payload (compressed)
+ BITSTREAM_DEBUG("Compressed bitstream found");
+ if (!compression_dict)
+ throw BitstreamParseError("start of compressed bitstream data before compression dictionary was stored", rd.get_offset());
+ // fall through
case BitstreamCommand::LSC_PROG_INCR_RTI: {
// This is the main bitstream payload
if (!chip)
throw BitstreamParseError("start of bitstream data before chip was identified", rd.get_offset());
+ bool reversed_frames;
+ if (chip->info.family == "MachXO2")
+ reversed_frames = false;
+ else if (chip->info.family == "ECP5")
+ reversed_frames = true;
+ else
+ throw BitstreamParseError("Unknown chip family: " + chip->info.family);
+
uint8_t params[3];
rd.get_bytes(params, 3);
BITSTREAM_DEBUG("settings: " << hex << setw(2) << int(params[0]) << " " << int(params[1]) << " "
<< int(params[2]));
- size_t dummy_bytes = (params[0] & 0x0FU);
+ // I've only seen 0x81 for the ecp5 and 0x8e for the xo2 so far...
+ bool check_crc = params[0] & 0x80U;
+ // inverted value: a 0 means check after every frame
+ bool crc_after_each_frame = check_crc && !(params[0] & 0x40U);
+ // I don't know what these two are for I've seen both 1s (XO2) and both 0s (ECP5)
+ // The names are from the ECP5 docs
+ // bool include_dummy_bits = params[0] & 0x20U;
+ // bool include_dummy_bytes = params[0] & 0x10U;
+ size_t dummy_bytes = params[0] & 0x0FU;
size_t frame_count = (params[1] << 8U) | params[2];
- BITSTREAM_NOTE(
- "reading " << std::dec << frame_count << " config frames (with " << std::dec << dummy_bytes
- << " dummy bytes)");
+ BITSTREAM_NOTE("reading " << std::dec << frame_count << " config frames (with " << std::dec << dummy_bytes << " dummy bytes)");
size_t bytes_per_frame = (chip->info.bits_per_frame + chip->info.pad_bits_after_frame +
chip->info.pad_bits_before_frame) / 8U;
+ // If compressed 0 bits are added to the stream before compression to make it 64 bit bounded, so
+ // we should consider that space here but they shouldn't be copied to the output
+ if (cmd == BitstreamCommand::LSC_PROG_INCR_CMP)
+ bytes_per_frame += (7 - ((bytes_per_frame - 1) % 8));
unique_ptr<uint8_t[]> frame_bytes = make_unique<uint8_t[]>(bytes_per_frame);
for (size_t i = 0; i < frame_count; i++) {
- rd.get_bytes(frame_bytes.get(), bytes_per_frame);
+ size_t idx = reversed_frames? (chip->info.num_frames - 1) - i : i;
+ if (cmd == BitstreamCommand::LSC_PROG_INCR_CMP)
+ rd.get_compressed_bytes(frame_bytes.get(), bytes_per_frame, compression_dict.get());
+ else
+ rd.get_bytes(frame_bytes.get(), bytes_per_frame);
+
for (int j = 0; j < chip->info.bits_per_frame; j++) {
size_t ofs = j + chip->info.pad_bits_after_frame;
- chip->cram.bit((chip->info.num_frames - 1) - i, j) = (char) (
- (frame_bytes[(bytes_per_frame - 1) - (ofs / 8)] >> (ofs % 8)) & 0x01);
+ chip->cram.bit(idx, j) = (char)
+ ((frame_bytes[(bytes_per_frame - 1) - (ofs / 8)] >> (ofs % 8)) & 0x01);
}
- rd.check_crc16();
+ if (crc_after_each_frame || (check_crc && (i == frame_count-1)))
+ rd.check_crc16();
rd.skip_bytes(dummy_bytes);
}
- // Post-bitstream space for SECURITY and SED
- // TODO: process SECURITY and SED
- rd.skip_possible_dummy(8);
- rd.skip_possible_dummy(4);
}
break;
case BitstreamCommand::LSC_EBR_ADDRESS: {
diff --git a/libtrellis/src/PyTrellis.cpp b/libtrellis/src/PyTrellis.cpp
index 2d58620..52e0fbf 100644
--- a/libtrellis/src/PyTrellis.cpp
+++ b/libtrellis/src/PyTrellis.cpp
@@ -63,7 +63,7 @@
.def("write_bit", &Bitstream::write_bit_py)
.def_readwrite("metadata", &Bitstream::metadata)
.def_readwrite("data", &Bitstream::data)
- .def("deserialise_chip", &Bitstream::deserialise_chip);
+ .def("deserialise_chip", static_cast<Chip (Bitstream::*)()>(&Bitstream::deserialise_chip));
class_<DeviceLocator>("DeviceLocator")
.def_readwrite("family", &DeviceLocator::family)