| // | |
| // Copyright (C) 2016 Clifford Wolf <clifford@clifford.at> | |
| // Copyright (C) 2019 Sylvain Munaut <tnt@246tNt.com> | |
| // | |
| // Permission to use, copy, modify, and/or distribute this software for any | |
| // purpose with or without fee is hereby granted, provided that the above | |
| // copyright notice and this permission notice appear in all copies. | |
| // | |
| // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
| // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
| // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
| // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
| // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
| // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
| // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
| // | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <assert.h> | |
| #include <stdint.h> | |
| #ifdef _WIN32 | |
| #define NOMINMAX | |
| #include "Windows.h" | |
| #undef NOMINMAX | |
| #else | |
| #include <unistd.h> | |
| #include <sys/time.h> | |
| #endif | |
| #include <map> | |
| #include <vector> | |
| #include <string> | |
| #include <fstream> | |
| #include <iostream> | |
| #include <boost/program_options.hpp> | |
| #include "ChipConfig.hpp" | |
| #include "Chip.hpp" | |
| #include "Database.hpp" | |
| #include "DatabasePath.hpp" | |
| using std::map; | |
| using std::pair; | |
| using std::vector; | |
| using std::string; | |
| using std::ifstream; | |
| using std::getline; | |
| uint64_t x; | |
| uint64_t xorshift64star(void) { | |
| x ^= x >> 12; // a | |
| x ^= x << 25; // b | |
| x ^= x >> 27; // c | |
| return x * UINT64_C(2685821657736338717); | |
| } | |
| void push_back_bitvector(vector<vector<bool>> &hexfile, const vector<int> &digits) | |
| { | |
| if (digits.empty()) | |
| return; | |
| hexfile.push_back(vector<bool>(digits.size() * 4)); | |
| for (int i = 0; i < int(digits.size()) * 4; i++) | |
| if ((digits.at(digits.size() - i/4 -1) & (1 << (i%4))) != 0) | |
| hexfile.back().at(i) = true; | |
| } | |
| void parse_hexfile_line(const char *filename, int linenr, vector<vector<bool>> &hexfile, string &line, int &address) | |
| { | |
| vector<int> digits; | |
| bool reading_address = false; | |
| for (char c : line) { | |
| if ('0' <= c && c <= '9') | |
| digits.push_back(c - '0'); | |
| else if ('a' <= c && c <= 'f') | |
| digits.push_back(10 + c - 'a'); | |
| else if ('A' <= c && c <= 'F') | |
| digits.push_back(10 + c - 'A'); | |
| else if ('x' == c || 'X' == c || | |
| 'z' == c || 'Z' == c) | |
| digits.push_back(0); | |
| else if ('_' == c) | |
| ; | |
| else if ('@' == c) { | |
| if (reading_address || !digits.empty()) | |
| goto error; | |
| else | |
| reading_address = true; | |
| } else if (' ' == c || '\t' == c || '\r' == c) { | |
| if (reading_address) { | |
| int file_address = 0; | |
| for (int i = 0; i < int(digits.size()); i++ ) { | |
| file_address <<= 4; | |
| file_address |= digits.at(i); | |
| } | |
| if (file_address != address) { | |
| fprintf(stderr, "Non-contiguous address (expected @%X) at line %d of %s: %s\n", address, linenr, filename, line.c_str()); | |
| exit(1); | |
| } | |
| } else { | |
| push_back_bitvector(hexfile, digits); | |
| if( !digits.empty() ) | |
| address++; | |
| } | |
| digits.clear(); | |
| reading_address = false; | |
| } else goto error; | |
| } | |
| push_back_bitvector(hexfile, digits); | |
| return; | |
| error: | |
| fprintf(stderr, "Can't parse line %d of %s: %s\n", linenr, filename, line.c_str()); | |
| exit(1); | |
| } | |
| int main(int argc, char **argv) | |
| { | |
| bool verbose = false; | |
| #ifdef _WIN32 | |
| uint32_t seed_nr = GetCurrentProcessId(); | |
| #else | |
| uint32_t seed_nr = getpid(); | |
| #endif | |
| namespace po = boost::program_options; | |
| std::string database_folder = get_database_path();; | |
| po::options_description options("Allowed options"); | |
| po::options_description options_any("Generic options"); | |
| options_any.add_options()("help,h", "show help"); | |
| options_any.add_options()("verbose,v", "verbose output"); | |
| po::options_description options_init("Initialize options"); | |
| options_init.add_options()("input,i", po::value<std::string>(), "input configuration file"); | |
| options_init.add_options()("output,o", po::value<std::string>(), "output configuration file"); | |
| options_init.add_options()("from,f", po::value<std::string>(), "original content hex file"); | |
| options_init.add_options()("to,t", po::value<std::string>(), "new content hex file"); | |
| po::options_description options_gen("Generate options"); | |
| options_gen.add_options()("generate,g", po::value<std::string>(), "Generate random hex of given geometry into given file"); | |
| options_gen.add_options()("seed,s", po::value<int>(), "seed random generator with fixed value"); | |
| options_gen.add_options()("width,w", po::value<int>(), "width of the BRAM content (in bits)"); | |
| options_gen.add_options()("depth,d", po::value<int>(), "idepth of the BRAM content (in # of words)"); | |
| options.add(options_any).add(options_init).add(options_gen); | |
| po::variables_map vm; | |
| try { | |
| po::parsed_options parsed = po::command_line_parser(argc, argv).options(options).run(); | |
| po::store(parsed, vm); | |
| po::notify(vm); | |
| } | |
| catch (std::exception &e) { | |
| cerr << e.what() << endl << endl; | |
| goto help; | |
| } | |
| if (vm.count("help")) { | |
| help: | |
| cerr << "Project Trellis - Open Source Tools for ECP5 FPGAs" << endl; | |
| cerr << argv[0] << ": ECP5 BRAM content initialization tool" << endl; | |
| cerr << endl; | |
| cerr << "Copyright (C) 2019 Sylvain Munaut <tnt@246tNt.com>" << endl; | |
| cerr << "Copyright (C) 2016 Clifford Wolf <clifford@clifford.at>" << endl; | |
| cerr << endl; | |
| cerr << options << endl; | |
| return vm.count("help") ? 0 : 1; | |
| } | |
| if (vm.count("verbose")) | |
| verbose = true; | |
| if (vm.count("generate")) | |
| { | |
| if (!vm.count("width") || !vm.count("depth")) | |
| goto help; | |
| int width = vm.at("width").as<int>(); | |
| int depth = vm.at("depth").as<int>(); | |
| if (width <= 0 || width % 4 != 0) { | |
| fprintf(stderr, "Hexfile width (%d bits) is not divisible by 4 or nonpositive!\n", width); | |
| return 1; | |
| } | |
| if (depth <= 0 || depth % 512 != 0) { | |
| fprintf(stderr, "Hexfile number of words (%d) is not divisible by 512 or nonpositive!\n", depth); | |
| return 1; | |
| } | |
| if (vm.count("seed")) { | |
| seed_nr = vm.at("seed").as<int>(); | |
| if (verbose) | |
| fprintf(stderr, "Seed: %d\n", seed_nr); | |
| } | |
| x = uint64_t(seed_nr) << 32; | |
| x ^= uint64_t(seed_nr) << 20; | |
| x ^= uint64_t(seed_nr); | |
| x ^= uint64_t(depth) << 16; | |
| x ^= uint64_t(width) << 10; | |
| xorshift64star(); | |
| xorshift64star(); | |
| xorshift64star(); | |
| if (!vm.count("seed")) { | |
| #ifdef _WIN32 | |
| SYSTEMTIME system_time; | |
| FILETIME file_time; | |
| uint64_t time; | |
| GetSystemTime(&system_time); | |
| SystemTimeToFileTime(&system_time, &file_time); | |
| x ^= uint64_t(file_time.dwLowDateTime); | |
| x ^= uint64_t(file_time.dwHighDateTime) << 32; | |
| #else | |
| struct timeval tv; | |
| gettimeofday(&tv, NULL); | |
| x ^= uint64_t(tv.tv_sec) << 20; | |
| x ^= uint64_t(tv.tv_usec); | |
| #endif | |
| } | |
| xorshift64star(); | |
| xorshift64star(); | |
| xorshift64star(); | |
| ofstream romfile(vm.at("generate").as<std::string>()); | |
| for (int i = 0; i < depth; i++) { | |
| for (int j = 0; j < width / 4; j++) { | |
| int digit = xorshift64star() & 15; | |
| romfile << "0123456789abcdef"[digit]; | |
| } | |
| romfile << std::endl; | |
| } | |
| return 0; | |
| } | |
| if (!vm.count("input") || !vm.count("output") || !vm.count("from") || !vm.count("to")) | |
| goto help; | |
| // ------------------------------------------------------- | |
| // Load from_hexfile and to_hexfile | |
| const char *from_hexfile_n = vm.at("from").as<std::string>().c_str(); | |
| ifstream from_hexfile_f(from_hexfile_n); | |
| vector<vector<bool>> from_hexfile; | |
| const char *to_hexfile_n = vm.at("to").as<std::string>().c_str(); | |
| ifstream to_hexfile_f(to_hexfile_n); | |
| vector<vector<bool>> to_hexfile; | |
| string line; | |
| for (int i = 1, address = 0; getline(from_hexfile_f, line); i++) | |
| parse_hexfile_line(from_hexfile_n, i, from_hexfile, line, address); | |
| for (int i = 1, address = 0; getline(to_hexfile_f, line); i++) | |
| parse_hexfile_line(to_hexfile_n, i, to_hexfile, line, address); | |
| if (to_hexfile.size() > 0 && from_hexfile.size() > to_hexfile.size()) { | |
| if (verbose) | |
| fprintf(stderr, "Padding to_hexfile from %d words to %d\n", | |
| int(to_hexfile.size()), int(from_hexfile.size())); | |
| do | |
| to_hexfile.push_back(vector<bool>(to_hexfile.at(0).size())); | |
| while (from_hexfile.size() > to_hexfile.size()); | |
| } | |
| if (from_hexfile.size() != to_hexfile.size()) { | |
| fprintf(stderr, "Hexfiles have different number of words! (%d vs. %d)\n", int(from_hexfile.size()), int(to_hexfile.size())); | |
| return 1; | |
| } | |
| if (from_hexfile.size() % 512 != 0) { | |
| fprintf(stderr, "Hexfile number of words (%d) is not divisible by 512!\n", int(from_hexfile.size())); | |
| return 1; | |
| } | |
| for (size_t i = 1; i < from_hexfile.size(); i++) | |
| if (from_hexfile.at(i-1).size() != from_hexfile.at(i).size()) { | |
| fprintf(stderr, "Inconsistent word width at line %d of %s!\n", int(i), from_hexfile_n); | |
| return 1; | |
| } | |
| for (size_t i = 1; i < to_hexfile.size(); i++) { | |
| while (to_hexfile.at(i-1).size() > to_hexfile.at(i).size()) | |
| to_hexfile.at(i).push_back(false); | |
| if (to_hexfile.at(i-1).size() != to_hexfile.at(i).size()) { | |
| fprintf(stderr, "Inconsistent word width at line %d of %s!\n", int(i+1), to_hexfile_n); | |
| return 1; | |
| } | |
| } | |
| if (from_hexfile.size() == 0 || from_hexfile.at(0).size() == 0) { | |
| fprintf(stderr, "Empty from/to hexfiles!\n"); | |
| return 1; | |
| } | |
| if (verbose) | |
| fprintf(stderr, "Loaded pattern for %d bits wide and %d words deep memory.\n", int(from_hexfile.at(0).size()), int(from_hexfile.size())); | |
| // ------------------------------------------------------- | |
| // Create bitslices from pattern data | |
| map<vector<bool>, pair<vector<bool>, int>> pattern; | |
| for (int i = 0; i < int(from_hexfile.at(0).size()); i++) | |
| { | |
| vector<bool> pattern_from, pattern_to; | |
| for (int j = 0; j < int(from_hexfile.size()); j++) | |
| { | |
| pattern_from.push_back(from_hexfile.at(j).at(i)); | |
| pattern_to.push_back(to_hexfile.at(j).at(i)); | |
| if (pattern_from.size() == 512) { | |
| if (pattern.count(pattern_from)) { | |
| fprintf(stderr, "Conflicting from pattern for bit slice from_hexfile[%d:%d][%d]!\n", j, j-255, i); | |
| return 1; | |
| } | |
| pattern[pattern_from] = std::make_pair(pattern_to, 0); | |
| pattern_from.clear(), pattern_to.clear(); | |
| } | |
| } | |
| assert(pattern_from.empty()); | |
| assert(pattern_to.empty()); | |
| } | |
| if (verbose) | |
| fprintf(stderr, "Extracted %d bit slices from from/to hexfile data.\n", int(pattern.size())); | |
| // ------------------------------------------------------- | |
| // Load database and config | |
| ifstream config_file(vm.at("input").as<string>()); | |
| try { | |
| Trellis::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>()); | |
| Trellis::ChipConfig cc; | |
| try { | |
| cc = Trellis::ChipConfig::from_string(textcfg); | |
| } catch (runtime_error &e) { | |
| cerr << "Failed to process input config: " << e.what() << endl; | |
| return 1; | |
| } | |
| // ------------------------------------------------------- | |
| // Replace bram data | |
| int max_replace_cnt = 0; | |
| for (auto &bram_it : cc.bram_data) | |
| { | |
| auto &bram_data = bram_it.second; | |
| const int W[] = { 1, 2, 4, 9, 18, 36 }; | |
| const int NW[] = { 8, 8, 8, 9, 9, 9 }; | |
| const int B[] = { 32, 32, 32, 36, 36, 36 }; | |
| for (int i = 0; i < 6; i++) | |
| { | |
| for (int j = 0; j < B[i]; j++) | |
| { | |
| vector<bool> from_bitslice; | |
| for (int k = 0; k < 512; k++) | |
| { | |
| int bn = (k * W[i]) + (j % W[i]) + ((j/W[i]) * 512 * W[i]); | |
| int word = bn / NW[i]; | |
| int bit = bn % NW[i]; | |
| from_bitslice.push_back((bram_data.at(word) & (1 << bit)) != 0); | |
| } | |
| auto p = pattern.find(from_bitslice); | |
| if (p != pattern.end()) | |
| { | |
| auto &to_bitslice = p->second.first; | |
| for (int k = 0; k < 512; k++) | |
| { | |
| int bn = (k * W[i]) + (j % W[i]) + ((j/W[i]) * 512 * W[i]); | |
| int word = bn / NW[i]; | |
| int bit = bn % NW[i]; | |
| if (to_bitslice.at(k)) | |
| bram_data.at(word) |= (1 << bit); | |
| else | |
| bram_data.at(word) &= ~(1 << bit); | |
| } | |
| max_replace_cnt = std::max(++p->second.second, max_replace_cnt); | |
| } | |
| } | |
| } | |
| } | |
| // ------------------------------------------------------- | |
| // Save the new config | |
| ofstream new_config_file(vm.at("output").as<std::string>()); | |
| new_config_file << cc.to_string(); | |
| return 0; | |
| } |