| #include "ChipConfig.hpp" |
| #include "Bitstream.hpp" |
| #include "Chip.hpp" |
| #include "Database.hpp" |
| #include "DatabasePath.hpp" |
| #include "Tile.hpp" |
| #include "BitDatabase.hpp" |
| #include "version.hpp" |
| #include "wasmexcept.hpp" |
| #include <iostream> |
| #include <boost/program_options.hpp> |
| #include <stdexcept> |
| #include <streambuf> |
| #include <fstream> |
| #include <iomanip> |
| |
| using namespace std; |
| |
| uint8_t reverse_byte(uint8_t byte) { |
| uint8_t rev = 0; |
| for (int i = 0; i < 8; i++) |
| if (byte & (1 << i)) |
| rev |= (1 << (7 - i)); |
| return rev; |
| } |
| |
| uint32_t convert_hexstring(std::string value_str) |
| { |
| return uint32_t(strtoul(value_str.c_str(), nullptr, 0)); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| using namespace Trellis; |
| namespace po = boost::program_options; |
| |
| std::string database_folder = get_database_path(); |
| |
| po::options_description options("Allowed options"); |
| options.add_options()("help,h", "show help"); |
| options.add_options()("verbose,v", "verbose output"); |
| options.add_options()("db", po::value<std::string>(), "Trellis database folder location"); |
| options.add_options()("usercode", po::value<uint32_t>(), "USERCODE to set in bitstream"); |
| options.add_options()("idcode", po::value<std::string>(), "IDCODE to override in bitstream"); |
| options.add_options()("freq", po::value<std::string>(), "config frequency in MHz"); |
| 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()("compress", "compress bitstream to reduce size"); |
| 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"); |
| options.add_options()("bootaddr", po::value<std::string>(), "set next BOOTADDR in bitstream and enable multi-boot"); |
| po::positional_options_description pos; |
| options.add_options()("input", po::value<std::string>()->required(), "input textual configuration"); |
| pos.add("input", 1); |
| options.add_options()("bit", po::value<std::string>(), "output bitstream file"); |
| pos.add("bit", 1); |
| options.add_options()("version", "show current version and exit"); |
| |
| po::variables_map vm; |
| |
| try { |
| po::parsed_options parsed = po::command_line_parser(argc, argv).options(options).positional(pos).run(); |
| po::store(parsed, vm); |
| |
| if (vm.count("version")) { |
| cerr << "Project Trellis ecppack Version " << git_describe_str << endl; |
| return 0; |
| } |
| |
| po::notify(vm); |
| } |
| catch (po::required_option& e) { |
| cerr << "Error: input file is mandatory." << endl << endl; |
| goto help; |
| } |
| catch (std::exception& e) { |
| cerr << "Error: " << e.what() << endl << endl; |
| goto help; |
| } |
| |
| if (vm.count("help")) { |
| help: |
| cerr << "Project Trellis - Open Source Tools for ECP5 FPGAs" << endl; |
| cerr << "Version " << git_describe_str << endl; |
| cerr << argv[0] << ": ECP5 bitstream packer" << endl; |
| cerr << endl; |
| cerr << "Copyright (C) 2018 gatecat <gatecat@ds0.me>" << endl; |
| cerr << endl; |
| cerr << "Usage: " << argv[0] << " input.config [output.bit] [options]" << endl; |
| cerr << options << endl; |
| return vm.count("help") ? 0 : 1; |
| } |
| |
| ifstream config_file(vm["input"].as<string>()); |
| if (!config_file) { |
| cerr << "Failed to open input file" << endl; |
| return 1; |
| } |
| |
| if (vm.count("db")) { |
| database_folder = vm["db"].as<string>(); |
| } |
| |
| try { |
| load_database(database_folder); |
| } catch (runtime_error &e) { |
| cerr << "Failed to load Trellis database: " << e.what() << endl; |
| return 1; |
| } |
| |
| string textcfg((std::istreambuf_iterator<char>(config_file)), std::istreambuf_iterator<char>()); |
| |
| ChipConfig cc; |
| try { |
| cc = ChipConfig::from_string(textcfg); |
| } catch (runtime_error &e) { |
| cerr << "Failed to process input config: " << e.what() << endl; |
| return 1; |
| } |
| |
| Chip c = cc.to_chip(); |
| if (vm.count("usercode")) |
| c.usercode = vm["usercode"].as<uint32_t>(); |
| |
| if (vm.count("idcode")) { |
| string idcode_str = vm["idcode"].as<string>(); |
| uint32_t idcode = uint32_t(strtoul(idcode_str.c_str(), nullptr, 0)); |
| if (idcode == 0) { |
| cerr << "Invalid idcode: " << idcode_str << endl; |
| return 1; |
| } |
| c.info.idcode = idcode; |
| } |
| |
| map<string, string> bitopts; |
| |
| // Apply options passed from nextpnr via SYSCONFIG |
| if (cc.sysconfig.count("MCCLK_FREQ")) { |
| std::string freq = cc.sysconfig.at("MCCLK_FREQ"); |
| if (freq == "62") |
| freq = "62.0"; |
| bitopts["freq"] = freq; |
| } |
| |
| if (cc.sysconfig.count("COMPRESS_CONFIG") && cc.sysconfig.at("COMPRESS_CONFIG") == "ON") |
| bitopts["compress"] = "yes"; |
| |
| // Override with command line options |
| if (vm.count("freq")) |
| bitopts["freq"] = vm["freq"].as<string>(); |
| |
| if (vm.count("spimode")) |
| bitopts["spimode"] = vm["spimode"].as<string>(); |
| |
| if (vm.count("compress")) |
| bitopts["compress"] = "yes"; |
| |
| 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"; |
| } |
| |
| if (vm.count("bootaddr")) { |
| uint32_t bootaddr = convert_hexstring(vm["bootaddr"].as<string>()); |
| |
| if (bootaddr & 0xffff) { |
| cerr << "Error: Boot Address must be 64k aligned !" << endl; |
| return 1; |
| } |
| |
| bootaddr = (bootaddr & 0x00ff0000) >> 16; |
| |
| auto tile_db = get_tile_bitdata(TileLocator{c.info.family, c.info.name, "EFB1_PICB1"}); |
| WordSettingBits wsb = tile_db->get_data_for_setword("BOOTADDR"); |
| auto tile = c.get_tiles_by_type("EFB1_PICB1"); |
| |
| if (tile.size() != 1) { |
| cerr << "EFB1_PICB1 Frame is wrong size. Can't proceed" << endl; |
| return 1; |
| } |
| |
| for(uint32_t j=0; j < wsb.bits.size(); j++) { |
| auto bg = wsb.bits.at(j); |
| for (auto bit : bg.bits) { |
| bool value = (bootaddr & (1 << j)) > 0; |
| tile[0]->cram.set_bit(bit.frame, bit.bit, value); |
| } |
| } |
| |
| bitopts["multiboot"] = "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) { |
| cerr << "Failed to open output file" << endl; |
| return 1; |
| } |
| b.write_bit(bit_file); |
| } |
| |
| if (vm.count("svf")) { |
| // Create JTAG bitstream without SPI flash related settings, as these |
| // seem to confuse the chip sometimes when configuring over JTAG |
| if (!bitopts.empty() && !(bitopts.size() == 1 && bitopts.count("compress"))) { |
| bitopts.erase("spimode"); |
| bitopts.erase("freq"); |
| b = Bitstream::serialise_chip(c, bitopts); |
| } |
| |
| vector<uint8_t> bitstream = b.get_bytes(); |
| int max_row_size = 8000; |
| if (vm.count("svf-rowsize")) |
| max_row_size = vm["svf-rowsize"].as<int>(); |
| if ((max_row_size % 8) != 0 || max_row_size <= 0) { |
| cerr << "SVF row size must be an exact positive number of bytes" << endl; |
| return 1; |
| } |
| ofstream svf_file(vm["svf"].as<string>()); |
| if (!svf_file) { |
| cerr << "Failed to open output SVF file" << endl; |
| return 1; |
| } |
| svf_file << "HDR\t0;" << endl; |
| svf_file << "HIR\t0;" << endl; |
| svf_file << "TDR\t0;" << endl; |
| svf_file << "TIR\t0;" << endl; |
| svf_file << "ENDDR\tDRPAUSE;" << endl; |
| svf_file << "ENDIR\tIRPAUSE;" << endl; |
| svf_file << "STATE\tIDLE;" << endl; |
| svf_file << "SIR\t8\tTDI (E0);" << endl; |
| svf_file << "SDR\t32\tTDI (00000000)" << endl; |
| 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; |
| 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; |
| svf_file << endl; |
| svf_file << "SIR\t8\tTDI (7A);" << endl; |
| svf_file << "RUNTEST\tIDLE\t2 TCK\t1.00E-02 SEC;" << endl; |
| size_t i = 0; |
| while(i < bitstream.size()) { |
| size_t len = min(size_t(max_row_size / 8), bitstream.size() - i); |
| if (len == 0) |
| break; |
| svf_file << "SDR\t" << setw(0) << dec << (8 * len) << "\tTDI ("; |
| svf_file << hex << uppercase << setw(2) << setfill('0'); |
| for (int j = len - 1; j >= 0; j--) { |
| svf_file << setw(2) << unsigned(reverse_byte(uint8_t(bitstream[j + i]))); |
| if (j % 40 == 0 && j != 0) |
| svf_file << endl << "\t\t\t"; |
| } |
| svf_file << ");" << endl; |
| i += len; |
| } |
| 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; |
| 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; |
| } |