Skip to content

Commit

Permalink
Merge pull request YosysHQ#104 from SymbiFlow/partial
Browse files Browse the repository at this point in the history
ecppack support for partial bitstreams and BACKGROUND_RECONFIG
  • Loading branch information
gatecat committed Oct 4, 2019
2 parents 63253bf + 3815ebb commit 1c2c59f
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 32 deletions.
18 changes: 18 additions & 0 deletions docs/architecture/bitstream_format.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ Control commands seen in a typical uncompressed, unencrypted bitstream are:
+-------------------------------+-----+--------------------------+---------------------------------------------------+
| ``LSC_INIT_ADDRESS`` | 46 | 24 bit info: all 0 | Resets the frame address register |
+-------------------------------+-----+--------------------------+---------------------------------------------------+
| ``LSC_WRITE_ADDRESS`` | B4 | - 24 bit info: all 0 | Loads the frame address register |
| | | - 32 bit frame address | |
+-------------------------------+-----+--------------------------+---------------------------------------------------+
| ``ISC_PROGRAM_SECURITY`` | CE | 24 bit info: all 0 | Program the security bit (prevents readback (?) ) |
+-------------------------------+-----+--------------------------+---------------------------------------------------+
| ``ISC_PROGRAM_USERCODE`` | C2 | - CRC bit set, 23 bits 0 | Sets the value of the USERCODE register |
Expand Down Expand Up @@ -131,6 +134,21 @@ After padding, every byte in the bitstream is compressed by a simple prefix-free
- 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``.

Partial Bitstreams
------------------------------

``LSC_WRITE_ADDRESS`` can be used to make partial bitstreams. Combined with background reconfiguration and
the ability to reload frames glitchlessly; partial reconfiguration is possible on ECP5.

``LSC_WRITE_ADDRESS`` takes a frame address; however frame addressing is not strictly linear. It has only
been fully documented for the 45k device and is as follows:
- the first 7 bits are always between 0 and 105 (each group of 106 frames is a column)
- the next 5 bits are the column index within a tap region
- the MSBs from bit 12 onwards are the tap region index

To enable loading of partial bitstreams the ``BACKGROUND_RECONFIG`` sysCONFIG option must be set. Then, to
avoid reinitialising the whole device, instructions 0x79 with no data and 0x74 followed by 0x00 must be
sent over JTAG before the partial bitstream data.

Device-Specific Information
------------------------------
Expand Down
2 changes: 1 addition & 1 deletion libtrellis/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ ecppll
libtrellis.dylib
*~
ecpmulti
generated/
generated/
2 changes: 2 additions & 0 deletions libtrellis/include/Bitstream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ enum class BitstreamCommand : uint8_t {
LSC_WRITE_COMP_DIC = 0b00000010,
LSC_PROG_CNTRL0 = 0b00100010,
LSC_INIT_ADDRESS = 0b01000110,
LSC_WRITE_ADDRESS = 0b10110100,
LSC_PROG_INCR_CMP = 0b10111000,
LSC_PROG_INCR_RTI = 0b10000010,
LSC_PROG_SED_CRC = 0b10100010,
Expand Down Expand Up @@ -48,6 +49,7 @@ class Bitstream {

// Serialise a Chip back to a bitstream
static Bitstream serialise_chip(const Chip &chip, const map<string, string> options);
static Bitstream serialise_chip_partial(const Chip &chip, const vector<uint32_t> &frames, const map<string, string> options);
static Bitstream generate_jump(uint32_t address);

// Deserialise a bitstream to a Chip
Expand Down
1 change: 0 additions & 1 deletion libtrellis/include/CRAM.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ class CRAM {
// Make a view to the CRAM given frame and bit offset; and frames and bits per frame in the view
CRAMView make_view(int frame_offset, int bit_offset, int frame_count, int bit_count);

private:
// Using a shared_ptr so views are not invalidated even if the CRAM itself is deleted
// A vector of type char is used as the optimisations in vector<bool> are not worth the loss of bool& etc
shared_ptr<vector<vector<char>>> data;
Expand Down
99 changes: 99 additions & 0 deletions libtrellis/src/Bitstream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ static const vector<pair<std::string, uint8_t>> spi_modes =
{"qspi", 0x59}};

static const uint32_t multiboot_flag = 1 << 20;
static const uint32_t background_flag = 0x2E000000;

// The BitstreamReadWriter class stores state (including CRC16) whilst reading
// the bitstream
Expand Down Expand Up @@ -609,6 +610,12 @@ Bitstream Bitstream::serialise_chip(const Chip &chip, const map<string, string>
else
ctrl0 &= ~multiboot_flag;
}
if (options.count("background")) {
if (options.at("background") == "yes")
ctrl0 |= background_flag;
else
ctrl0 &= ~background_flag;
}
wr.write_uint32(ctrl0);
// Init address
wr.write_byte(uint8_t(BitstreamCommand::LSC_INIT_ADDRESS));
Expand Down Expand Up @@ -680,6 +687,98 @@ Bitstream Bitstream::serialise_chip(const Chip &chip, const map<string, string>
return Bitstream(wr.get(), chip.metadata);
}

Bitstream Bitstream::serialise_chip_partial(const Chip &chip, const vector<uint32_t> &frames, const map<string, string> options)
{
BitstreamReadWriter wr;

// Address encoding for partial frame writes
static const map<uint32_t, uint32_t> msb_weights_45k = {
{{0x0000}, { 0*106}},
{{0x1000}, {12*106}},
{{0x2000}, {30*106}},
{{0x3000}, {49*106}},
{{0x4000}, {68*106}},
{{0x5000}, {95*106}},
};

auto encode_frame_address = [&](uint32_t frame) {
if (chip.info.name.find("45F") == std::string::npos)
throw runtime_error("FIXME: partial bitstreams only supported for ECP5-45k");
uint32_t msbw = 0x0000;
while (msb_weights_45k.count(msbw + 0x1000) && frame >= msb_weights_45k.at(msbw + 0x1000))
msbw += 0x1000;
frame -= msb_weights_45k.at(msbw);
uint32_t num_106s = 0;
while (frame >= 106) {
num_106s += 1;
frame -= 106;
}
return msbw | (num_106s << 7) | frame;
};

// Preamble
wr.write_bytes(preamble.begin(), preamble.size());
// Padding
wr.insert_dummy(4);

if (options.count("spimode")) {
auto spimode = find_if(spi_modes.begin(), spi_modes.end(), [&](const pair<string, uint8_t> &fp){
return fp.first == options.at("spimode");
});
if (spimode == spi_modes.end())
throw runtime_error("bad spimode option " + options.at("spimode"));

wr.write_byte(uint8_t(BitstreamCommand::SPI_MODE));
wr.write_byte(uint8_t(spimode->second));
wr.insert_zeros(2);
}

// Reset CRC
wr.write_byte(uint8_t(BitstreamCommand::LSC_RESET_CRC));
wr.insert_zeros(3);
wr.reset_crc16();
// Verify ID
wr.write_byte(uint8_t(BitstreamCommand::VERIFY_ID));
wr.insert_zeros(3);
wr.write_uint32(chip.info.idcode);

for (auto frame : frames) {
// Init address
wr.write_byte(uint8_t(BitstreamCommand::LSC_WRITE_ADDRESS));
wr.insert_zeros(3);
wr.write_uint32(encode_frame_address((chip.info.num_frames - 1) - frame));
// Bitstream data
wr.write_byte(uint8_t(BitstreamCommand::LSC_PROG_INCR_RTI));
wr.write_byte(0x91); //CRC check, 1 dummy byte
uint16_t num_frames = 1;
wr.write_byte(uint8_t((num_frames >> 8) & 0xFF));
wr.write_byte(uint8_t(num_frames & 0xFF));
size_t bytes_per_frame = (chip.info.bits_per_frame + chip.info.pad_bits_after_frame +
chip.info.pad_bits_before_frame) / 8U;
unique_ptr<uint8_t[]> frame_bytes = make_unique<uint8_t[]>(bytes_per_frame);
for (size_t i = 0; i < num_frames; i++) {
fill(frame_bytes.get(), frame_bytes.get() + bytes_per_frame, 0x00);
for (int j = 0; j < chip.info.bits_per_frame; j++) {
size_t ofs = j + chip.info.pad_bits_after_frame;
assert(((bytes_per_frame - 1) - (ofs / 8)) < bytes_per_frame);
frame_bytes[(bytes_per_frame - 1) - (ofs / 8)] |=
(chip.cram.bit(frame, j) & 0x01) << (ofs % 8);
}
wr.write_bytes(frame_bytes.get(), bytes_per_frame);
wr.insert_crc16();
wr.write_byte(0xFF);
}
}


// Program DONE
wr.write_byte(uint8_t(BitstreamCommand::ISC_PROGRAM_DONE));
wr.insert_zeros(3);
// Trailing padding
wr.insert_dummy(4);
return Bitstream(wr.get(), chip.metadata);
}

void Bitstream::write_bit(ostream &out) {
// Write metadata header
out.put(char(0xFF));
Expand Down
118 changes: 88 additions & 30 deletions libtrellis/tools/ecppack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
#include "Chip.hpp"
#include "Database.hpp"
#include "DatabasePath.hpp"
#include "Tile.hpp"
#include "BitDatabase.hpp"
#include "version.hpp"

#include <iostream>
#include <boost/program_options.hpp>
#include <stdexcept>
Expand Down Expand Up @@ -38,7 +41,8 @@ int main(int argc, char *argv[])
options.add_options()("svf", po::value<std::string>(), "output SVF file");
options.add_options()("svf-rowsize", po::value<int>(), "SVF row size in bits (default 8000)");
options.add_options()("spimode", po::value<std::string>(), "SPI Mode to use (fast-read, dual-spi, qspi)");

options.add_options()("background", "enable background reconfiguration in bitstream");
options.add_options()("delta", po::value<std::string>(), "create a delta partial bitstream given a reference config");
po::positional_options_description pos;
options.add_options()("input", po::value<std::string>()->required(), "input textual configuration");
pos.add("input", 1);
Expand Down Expand Up @@ -122,7 +126,43 @@ int main(int argc, char *argv[])
if (vm.count("spimode"))
bitopts["spimode"] = vm["spimode"].as<string>();

Bitstream b = Bitstream::serialise_chip(c, bitopts);
if (vm.count("background")) {
auto tile_db = get_tile_bitdata(TileLocator{c.info.family, c.info.name, "EFB0_PICB0"});
auto esb = tile_db->get_data_for_enum("SYSCONFIG.BACKGROUND_RECONFIG");
auto tile = c.get_tiles_by_type("EFB0_PICB0");
for (const auto &bit : esb.options["ON"].bits)
tile[0]->cram.set_bit(bit.frame, bit.bit, bit.inv ? 0 : 1);
bitopts["background"] = "yes";
}

bool partial_mode = false;
vector<uint32_t> partial_frames;
if (vm.count("delta")) {
ifstream delta_file(vm["delta"].as<string>());
if (!delta_file) {
cerr << "Failed to open reference config file" << endl;
return 1;
}
string refcfg((std::istreambuf_iterator<char>(delta_file)), std::istreambuf_iterator<char>());

ChipConfig ref_cc;
try {
ref_cc = ChipConfig::from_string(refcfg);
} catch (runtime_error &e) {
cerr << "Failed to process reference config: " << e.what() << endl;
return 1;
}
Chip ref_c = ref_cc.to_chip();
for (int frame = 0; frame < c.cram.frames(); frame++) {
if (ref_c.cram.data->at(frame) != c.cram.data->at(frame)) {
partial_frames.push_back(frame);
}
}

partial_mode = true;
}

Bitstream b = partial_mode ? Bitstream::serialise_chip_partial(c, partial_frames, bitopts) : Bitstream::serialise_chip(c, bitopts);
if (vm.count("bit")) {
ofstream bit_file(vm["bit"].as<string>(), ios::binary);
if (!bit_file) {
Expand All @@ -138,7 +178,9 @@ int main(int argc, char *argv[])
// seem to confuse the chip sometimes when configuring over JTAG
if (!bitopts.empty()) {
bitopts.clear();
b = Bitstream::serialise_chip(c, bitopts);
if (vm.count("background"))
bitopts["background"] = "yes";
b = Bitstream::serialise_chip(c, bitopts);
}

vector<uint8_t> bitstream = b.get_bytes();
Expand Down Expand Up @@ -166,19 +208,31 @@ int main(int argc, char *argv[])
svf_file << "\t\t\tTDO (" << setw(8) << hex << uppercase << setfill('0') << c.info.idcode << ")" << endl;
svf_file << "\t\t\tMASK (FFFFFFFF);" << endl;
svf_file << endl;
svf_file << "SIR\t8\tTDI (1C);" << endl;
svf_file << "SDR\t510\tTDI (3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" << endl;
svf_file << "\t\t\t\tFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);" << endl;
svf_file << endl;
svf_file << "SIR\t8\tTDI (C6);" << endl;
svf_file << "SDR\t8\tTDI (00);" << endl;
svf_file << "RUNTEST\tIDLE\t2 TCK\t1.00E-02 SEC;" << endl;
svf_file << endl;
svf_file << "SIR\t8\tTDI (3C);" << endl;
svf_file << "SDR\t32\tTDI (00000000)" << endl;
svf_file << "\t\t\tTDO (00000000)" << endl;
svf_file << "\t\t\tMASK (0000B000);" << endl;
svf_file << endl;
if (!partial_mode) {
svf_file << "SIR\t8\tTDI (1C);" << endl;
svf_file << "SDR\t510\tTDI (3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" << endl;
svf_file << "\t\t\t\tFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);" << endl;
svf_file << endl;
svf_file << "SIR\t8\tTDI (C6);" << endl;
svf_file << "SDR\t8\tTDI (00);" << endl;
svf_file << "RUNTEST\tIDLE\t2 TCK\t1.00E-02 SEC;" << endl;
svf_file << endl;
svf_file << "SIR\t8\tTDI (0E);" << endl;
svf_file << "SDR\t8\tTDI (01);" << endl;
svf_file << "RUNTEST\tIDLE\t2 TCK\t1.00E-02 SEC;" << endl;
svf_file << endl;
svf_file << "SIR\t8\tTDI (3C);" << endl;
svf_file << "SDR\t32\tTDI (00000000)" << endl;
svf_file << "\t\t\tTDO (00000000)" << endl;
svf_file << "\t\t\tMASK (0000B000);" << endl;
svf_file << endl;
} else {
svf_file << "SIR\t8\tTDI (79);" << endl;
svf_file << "RUNTEST\tIDLE\t2 TCK\t1.00E-02 SEC;" << endl;
svf_file << "SIR\t8\tTDI (74);" << endl;
svf_file << "SDR\t8\tTDI (00);" << endl;
svf_file << "RUNTEST\tIDLE\t2 TCK\t1.00E-02 SEC;" << endl;
}
svf_file << "SIR\t8\tTDI (46);" << endl;
svf_file << "SDR\t8\tTDI (01);" << endl;
svf_file << "RUNTEST\tIDLE\t2 TCK\t1.00E-02 SEC;" << endl;
Expand All @@ -200,26 +254,30 @@ int main(int argc, char *argv[])
svf_file << ");" << endl;
i += len;
}
svf_file << endl;
svf_file << "SIR\t8\tTDI (FF);" << endl;
svf_file << "RUNTEST\tIDLE\t100 TCK\t1.00E-02 SEC;" << endl;
svf_file << endl;
svf_file << "SIR\t8\tTDI (C0);" << endl;
svf_file << "RUNTEST\tIDLE\t2 TCK\t1.00E-03 SEC;" << endl;
svf_file << "SDR\t32\tTDI (00000000)" << endl;
svf_file << "\t\t\tTDO (00000000)" << endl;
svf_file << "\t\t\tMASK (FFFFFFFF);" << endl;
svf_file << endl;
if (!partial_mode) {
svf_file << endl;
svf_file << "SIR\t8\tTDI (FF);" << endl;
svf_file << "RUNTEST\tIDLE\t100 TCK\t1.00E-02 SEC;" << endl;
svf_file << endl;
svf_file << "SIR\t8\tTDI (C0);" << endl;
svf_file << "RUNTEST\tIDLE\t2 TCK\t1.00E-03 SEC;" << endl;
svf_file << "SDR\t32\tTDI (00000000)" << endl;
svf_file << "\t\t\tTDO (00000000)" << endl;
svf_file << "\t\t\tMASK (FFFFFFFF);" << endl;
svf_file << endl;
}
svf_file << "SIR\t8\tTDI (26);" << endl;
svf_file << "RUNTEST\tIDLE\t2 TCK\t2.00E-01 SEC;" << endl;
svf_file << endl;
svf_file << "SIR\t8\tTDI (FF);" << endl;
svf_file << "RUNTEST\tIDLE\t2 TCK\t1.00E-03 SEC;" << endl;
svf_file << endl;
svf_file << "SIR\t8\tTDI (3C);" << endl;
svf_file << "SDR\t32\tTDI (00000000)" << endl;
svf_file << "\t\t\tTDO (00000100)" << endl;
svf_file << "\t\t\tMASK (00002100);" << endl;
if (!partial_mode) {
svf_file << "SIR\t8\tTDI (3C);" << endl;
svf_file << "SDR\t32\tTDI (00000000)" << endl;
svf_file << "\t\t\tTDO (00000100)" << endl;
svf_file << "\t\t\tMASK (00002100);" << endl;
}
}

return 0;
Expand Down
3 changes: 3 additions & 0 deletions libtrellis/tools/ecpunpack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ int main(int argc, char *argv[])
}
out_file << cc.to_string();
return 0;
} catch (BitstreamParseError &e) {
cerr << "Failed to process input bitstream: " << e.what() << endl;
return 1;
} catch (runtime_error &e) {
cerr << "Failed to process input bitstream: " << e.what() << endl;
return 1;
Expand Down

0 comments on commit 1c2c59f

Please sign in to comment.