| #include "BitDatabase.hpp" | |
| #include "CRAM.hpp" | |
| #include "TileConfig.hpp" | |
| #include "Tile.hpp" | |
| #include "RoutingGraph.hpp" | |
| #include <algorithm> | |
| #include <fstream> | |
| #include <boost/thread/shared_lock_guard.hpp> | |
| #include <boost/thread/lock_guard.hpp> | |
| #include <boost/range/algorithm/copy.hpp> | |
| #include <boost/range/adaptors.hpp> | |
| namespace Trellis { | |
| ConfigBit cbit_from_str(const string &s) | |
| { | |
| size_t idx = 0; | |
| ConfigBit b; | |
| if (s[idx] == '!') { | |
| b.inv = true; | |
| ++idx; | |
| } else { | |
| b.inv = false; | |
| } | |
| assert(s[idx] == 'F'); | |
| ++idx; | |
| size_t b_pos = s.find('B'); | |
| assert(b_pos != string::npos); | |
| b.frame = stoi(s.substr(idx, b_pos - idx)); | |
| b.bit = stoi(s.substr(b_pos + 1)); | |
| return b; | |
| } | |
| BitGroup::BitGroup() | |
| {} | |
| BitGroup::BitGroup(const CRAMDelta &delta) | |
| { | |
| for (const auto &bit: delta) { | |
| if (bit.delta != 0) | |
| bits.insert(ConfigBit{bit.frame, bit.bit, (bit.delta < 0)}); | |
| } | |
| } | |
| bool BitGroup::match(const CRAMView &tile) const | |
| { | |
| return all_of(bits.begin(), bits.end(), [tile](const ConfigBit &b) { | |
| return tile.bit(b.frame, b.bit) != b.inv; | |
| }); | |
| } | |
| void BitGroup::set_group(CRAMView &tile) const | |
| { | |
| for (auto bit : bits) | |
| tile.bit(bit.frame, bit.bit) = !bit.inv; | |
| } | |
| void BitGroup::clear_group(Trellis::CRAMView &tile) const | |
| { | |
| for (auto bit : bits) | |
| tile.bit(bit.frame, bit.bit) = bit.inv; | |
| } | |
| void BitGroup::add_coverage(Trellis::BitSet &known_bits, bool value) const | |
| { | |
| for (const auto &b : bits) { | |
| if (b.inv != value) | |
| known_bits.insert(ConfigBit{b.frame, b.bit}); | |
| } | |
| } | |
| ostream &operator<<(ostream &out, const BitGroup &bits) | |
| { | |
| bool first = true; | |
| if (bits.bits.empty()) { | |
| out << "-"; | |
| } else { | |
| for (auto bit : bits.bits) { | |
| if (!first) | |
| out << " "; | |
| out << to_string(bit); | |
| first = false; | |
| } | |
| } | |
| return out; | |
| } | |
| istream &operator>>(istream &in, BitGroup &bits) | |
| { | |
| bits.bits.clear(); | |
| while (!skip_check_eol(in)) { | |
| string s; | |
| in >> s; | |
| if (s == "-") | |
| break; | |
| bits.bits.insert(cbit_from_str(s)); | |
| } | |
| return in; | |
| } | |
| vector<string> MuxBits::get_sources() const | |
| { | |
| vector<string> result; | |
| boost::copy(arcs | boost::adaptors::map_keys, back_inserter(result)); | |
| return result; | |
| } | |
| boost::optional<string> MuxBits::get_driver(const CRAMView &tile, boost::optional<BitSet &> coverage) const | |
| { | |
| boost::optional<const ArcData &> bestmatch; | |
| size_t bestbits = 0; | |
| for (const auto &arc : arcs) { | |
| if (arc.second.bits.match(tile) && arc.second.bits.bits.size() >= bestbits) { | |
| bestmatch = arc.second; | |
| bestbits = arc.second.bits.bits.size(); | |
| } | |
| } | |
| if (!bestmatch) { | |
| return boost::optional<string>(); | |
| } else { | |
| if (coverage) | |
| bestmatch->bits.add_coverage(*coverage); | |
| return boost::optional<string>(bestmatch->source); | |
| } | |
| } | |
| void MuxBits::set_driver(Trellis::CRAMView &tile, const string &driver) const | |
| { | |
| auto drv = arcs.find(driver); | |
| if (drv == arcs.end()) { | |
| throw runtime_error("sink " + sink + " has no driver named " + driver); | |
| } | |
| drv->second.bits.set_group(tile); | |
| } | |
| ostream &operator<<(ostream &out, const MuxBits &mux) | |
| { | |
| out << ".mux " << mux.sink << endl; | |
| for (const auto &arc : mux.arcs) { | |
| out << arc.first << " " << arc.second.bits << endl; | |
| } | |
| return out; | |
| } | |
| istream &operator>>(istream &in, MuxBits &mux) | |
| { | |
| in >> mux.sink; | |
| mux.arcs.clear(); | |
| // Read arc source-bits pairs until end of record | |
| while (!skip_check_eor(in)) { | |
| ArcData a; | |
| a.sink = mux.sink; | |
| in >> a.source >> a.bits; | |
| mux.arcs[a.source] = a; | |
| } | |
| return in; | |
| } | |
| boost::optional<vector<bool>> | |
| WordSettingBits::get_value(const CRAMView &tile, boost::optional<BitSet &> coverage) const | |
| { | |
| vector<bool> val; | |
| transform(bits.begin(), bits.end(), back_inserter(val), [tile, coverage](const BitGroup &b) { | |
| bool m = b.match(tile); | |
| if (coverage) | |
| b.add_coverage(*coverage, m); | |
| return m; | |
| }); | |
| if (val == defval) | |
| return boost::optional<vector<bool>>(); | |
| else | |
| return boost::optional<vector<bool>>(val); | |
| } | |
| void WordSettingBits::set_value(Trellis::CRAMView &tile, const vector<bool> &value) const | |
| { | |
| assert(value.size() == bits.size()); | |
| for (size_t i = 0; i < bits.size(); i++) { | |
| if (value.at(i)) | |
| bits.at(i).set_group(tile); | |
| else | |
| bits.at(i).clear_group(tile); | |
| } | |
| } | |
| ostream &operator<<(ostream &out, const WordSettingBits &ws) | |
| { | |
| out << ".config " << ws.name << " " << to_string(ws.defval) << endl; | |
| for (const auto &bit : ws.bits) { | |
| out << bit << endl; | |
| } | |
| return out; | |
| } | |
| istream &operator>>(istream &in, WordSettingBits &ws) | |
| { | |
| in >> ws.name; | |
| bool have_default = false; | |
| if (!skip_check_eol(in)) { | |
| in >> ws.defval; | |
| have_default = true; | |
| } | |
| ws.bits.clear(); | |
| while (!skip_check_eor(in)) { | |
| BitGroup bg; | |
| in >> bg; | |
| ws.bits.push_back(bg); | |
| } | |
| if (!have_default) { | |
| ws.defval.clear(); | |
| ws.defval.resize(ws.bits.size(), false); | |
| } | |
| return in; | |
| } | |
| void EnumSettingBits::set_defval(string val) | |
| { | |
| defval = val; | |
| } | |
| string EnumSettingBits::get_defval() const | |
| { | |
| if (defval) | |
| return *defval; | |
| else | |
| return ""; | |
| } | |
| vector<string> EnumSettingBits::get_options() const | |
| { | |
| vector<string> result; | |
| boost::copy(options | boost::adaptors::map_keys, back_inserter(result)); | |
| return result; | |
| } | |
| boost::optional<string> EnumSettingBits::get_value(const CRAMView &tile, boost::optional<BitSet &> coverage) const | |
| { | |
| boost::optional<const pair<const string, BitGroup> &> bestmatch; | |
| size_t bestbits = 0; | |
| for (const auto &opt : options) { | |
| if (opt.second.match(tile) && opt.second.bits.size() >= bestbits) { | |
| bestmatch = opt; | |
| bestbits = opt.second.bits.size(); | |
| } | |
| } | |
| if (!bestmatch) { | |
| if (defval) { | |
| return boost::optional<string>("_NONE_"); | |
| } else { | |
| return boost::optional<string>(); | |
| } | |
| } else { | |
| if (coverage) | |
| bestmatch->second.add_coverage(*coverage); | |
| if (defval && (options.at(*defval) == bestmatch->second)) { | |
| return boost::optional<string>(); | |
| } else { | |
| return boost::optional<string>(bestmatch->first); | |
| } | |
| } | |
| } | |
| void EnumSettingBits::set_value(Trellis::CRAMView &tile, const string &value) const | |
| { | |
| if (value != "_NONE_") { | |
| if(options.find(value) != options.end()) { | |
| auto grp = options.at(value); | |
| grp.set_group(tile); | |
| } | |
| else { | |
| cerr << "EnumSettingBits::set_value: cannot set " << value << endl; | |
| cerr << "In Options: " << endl; | |
| for(auto it = options.begin(); it != options.end(); ++it){ | |
| cerr << it->first << " -> " << it->second << endl; | |
| } | |
| exit(1); | |
| } | |
| } | |
| } | |
| ostream &operator<<(ostream &out, const EnumSettingBits &es) | |
| { | |
| out << ".config_enum " << es.name; | |
| if (es.defval) | |
| out << " " << *(es.defval); | |
| out << endl; | |
| for (const auto &opt : es.options) { | |
| out << opt.first << " " << opt.second << endl; | |
| } | |
| return out; | |
| } | |
| istream &operator>>(istream &in, EnumSettingBits &es) | |
| { | |
| in >> es.name; | |
| if (!skip_check_eol(in)) { | |
| string s; | |
| in >> s; | |
| es.defval = boost::make_optional(s); | |
| } else { | |
| es.defval = boost::optional<string>(); | |
| } | |
| es.options.clear(); | |
| while (!skip_check_eor(in)) { | |
| string opt; | |
| BitGroup bg; | |
| in >> opt >> bg; | |
| es.options[opt] = bg; | |
| } | |
| return in; | |
| } | |
| ostream &operator<<(ostream &out, const FixedConnection &es) | |
| { | |
| out << ".fixed_conn " << es.sink << " " << es.source << endl; | |
| return out; | |
| } | |
| istream &operator>>(istream &in, FixedConnection &es) | |
| { | |
| in >> es.sink >> es.source; | |
| return in; | |
| } | |
| TileBitDatabase::TileBitDatabase(const string &filename) : filename(filename) | |
| { | |
| load(); | |
| } | |
| void TileBitDatabase::config_to_tile_cram(const TileConfig &cfg, CRAMView &tile, bool is_tilegroup, set<string> *tg_matches) const | |
| { | |
| boost::shared_lock_guard<boost::shared_mutex> guard(db_mutex); | |
| for (auto arc : cfg.carcs) | |
| muxes.at(arc.sink).set_driver(tile, arc.source); | |
| set<string> found_words, found_enums; | |
| // Make sure "base" enums like IO type are applied first, other settings may overlay onto them later | |
| const string base_prefix = "BASE_"; | |
| for (auto ce : cfg.cenums) { | |
| if (ce.name.substr(0, base_prefix.length()) == base_prefix) { | |
| if (is_tilegroup && !enums.count(ce.name)) | |
| continue; | |
| if (is_tilegroup && !enums.at(ce.name).options.count(ce.value)) | |
| continue; | |
| if (tg_matches) | |
| tg_matches->insert(ce.name); | |
| enums.at(ce.name).set_value(tile, ce.value); | |
| found_enums.insert(ce.name); | |
| } | |
| } | |
| for (auto cw : cfg.cwords) { | |
| if (is_tilegroup && !words.count(cw.name)) | |
| continue; | |
| if (tg_matches) | |
| tg_matches->insert(cw.name); | |
| words.at(cw.name).set_value(tile, cw.value); | |
| found_words.insert(cw.name); | |
| } | |
| for (auto ce : cfg.cenums) { | |
| if (ce.name.substr(0, base_prefix.length()) != base_prefix) { | |
| if (is_tilegroup && !enums.count(ce.name)) | |
| continue; | |
| if (is_tilegroup && !enums.at(ce.name).options.count(ce.value)) | |
| continue; | |
| if (tg_matches) | |
| tg_matches->insert(ce.name); | |
| enums.at(ce.name).set_value(tile, ce.value); | |
| found_enums.insert(ce.name); | |
| } | |
| } | |
| for (auto unk : cfg.cunknowns) { | |
| tile.bit(unk.frame, unk.bit) = 1; | |
| } | |
| // Apply default values if not overriden in cfg | |
| if (!is_tilegroup) { | |
| for (auto w : words) | |
| if (found_words.find(w.first) == found_words.end()) | |
| w.second.set_value(tile, w.second.defval); | |
| for (auto e : enums) | |
| if (found_enums.find(e.first) == found_enums.end()) | |
| if (e.second.defval) | |
| e.second.set_value(tile, *e.second.defval); | |
| } | |
| } | |
| TileConfig TileBitDatabase::tile_cram_to_config(const CRAMView &tile) const | |
| { | |
| boost::shared_lock_guard<boost::shared_mutex> guard(db_mutex); | |
| TileConfig cfg; | |
| BitSet coverage; | |
| for (auto mux : muxes) { | |
| auto sink = mux.second.get_driver(tile, coverage); | |
| if (sink && mux.second.arcs.at(*sink).bits.bits.size() > 0) | |
| cfg.carcs.push_back(ConfigArc{mux.first, *sink}); | |
| } | |
| for (auto cw : words) { | |
| auto val = cw.second.get_value(tile, coverage); | |
| if (val) | |
| cfg.cwords.push_back(ConfigWord{cw.first, *val}); | |
| } | |
| for (auto ce : enums) { | |
| auto val = ce.second.get_value(tile, coverage); | |
| if (val) | |
| cfg.cenums.push_back(ConfigEnum{ce.first, *val}); | |
| } | |
| for (int f = 0; f < tile.frames(); f++) { | |
| for (int b = 0; b < tile.bits(); b++) { | |
| if (tile.bit(f, b)) { | |
| if (coverage.find(ConfigBit{f, b, false}) == coverage.end()) { | |
| cfg.cunknowns.push_back(ConfigUnknown{f, b}); | |
| } else { | |
| cfg.total_known_bits++; | |
| } | |
| } | |
| } | |
| }; | |
| return cfg; | |
| } | |
| void TileBitDatabase::load() | |
| { | |
| boost::lock_guard<boost::shared_mutex> guard(db_mutex); | |
| ifstream in(filename); | |
| if (!in) { | |
| throw runtime_error("failed to open tilebit database file " + filename); | |
| } | |
| muxes.clear(); | |
| words.clear(); | |
| enums.clear(); | |
| while (!skip_check_eof(in)) { | |
| string token; | |
| in >> token; | |
| if (token == ".mux") { | |
| MuxBits mux; | |
| in >> mux; | |
| muxes[mux.sink] = mux; | |
| } else if (token == ".config") { | |
| WordSettingBits cw; | |
| in >> cw; | |
| words[cw.name] = cw; | |
| } else if (token == ".config_enum") { | |
| EnumSettingBits ce; | |
| in >> ce; | |
| enums[ce.name] = ce; | |
| } else if (token == ".fixed_conn") { | |
| FixedConnection c; | |
| in >> c; | |
| fixed_conns[c.sink].insert(c); | |
| } else { | |
| throw runtime_error("unexpected token " + token + " while parsing database file " + filename); | |
| } | |
| } | |
| } | |
| void TileBitDatabase::save() | |
| { | |
| boost::lock_guard<boost::shared_mutex> guard(db_mutex); | |
| ofstream out(filename); | |
| if (!out) { | |
| throw runtime_error("failed to open tilebit database file " + filename + " for writing"); | |
| } | |
| out << "# Routing Mux Bits" << endl; | |
| for (auto mux : muxes) | |
| out << mux.second << endl; | |
| out << endl << "# Non-Routing Configuration" << endl; | |
| for (auto word : words) | |
| out << word.second << endl; | |
| for (auto senum : enums) | |
| out << senum.second << endl; | |
| out << endl << "# Fixed Connections" << endl; | |
| for (auto conns : fixed_conns) | |
| for (auto conn2 : conns.second) | |
| out << conn2 << endl; | |
| dirty = false; | |
| } | |
| vector<string> TileBitDatabase::get_sinks() const | |
| { | |
| boost::shared_lock_guard<boost::shared_mutex> guard(db_mutex); | |
| vector<string> result; | |
| boost::copy(muxes | boost::adaptors::map_keys, back_inserter(result)); | |
| return result; | |
| } | |
| MuxBits TileBitDatabase::get_mux_data_for_sink(const string &sink) const | |
| { | |
| boost::shared_lock_guard<boost::shared_mutex> guard(db_mutex); | |
| return muxes.at(sink); | |
| } | |
| vector<string> TileBitDatabase::get_settings_words() const | |
| { | |
| boost::shared_lock_guard<boost::shared_mutex> guard(db_mutex); | |
| vector<string> result; | |
| boost::copy(words | boost::adaptors::map_keys, back_inserter(result)); | |
| return result; | |
| } | |
| WordSettingBits TileBitDatabase::get_data_for_setword(const string &name) const | |
| { | |
| boost::shared_lock_guard<boost::shared_mutex> guard(db_mutex); | |
| return words.at(name); | |
| } | |
| vector<string> TileBitDatabase::get_settings_enums() const | |
| { | |
| boost::shared_lock_guard<boost::shared_mutex> guard(db_mutex); | |
| vector<string> result; | |
| boost::copy(enums | boost::adaptors::map_keys, back_inserter(result)); | |
| return result; | |
| } | |
| EnumSettingBits TileBitDatabase::get_data_for_enum(const string &name) const | |
| { | |
| boost::shared_lock_guard<boost::shared_mutex> guard(db_mutex); | |
| return enums.at(name); | |
| } | |
| vector<FixedConnection> TileBitDatabase::get_fixed_conns() const | |
| { | |
| boost::shared_lock_guard<boost::shared_mutex> guard(db_mutex); | |
| vector<FixedConnection> result; | |
| for (const auto &csink : fixed_conns) { | |
| for (const auto &conn : csink.second) { | |
| result.push_back(conn); | |
| } | |
| } | |
| return result; | |
| } | |
| vector<pair<string, bool>> TileBitDatabase::get_downhill_wires(const string &wire) const | |
| { | |
| vector<pair<string, bool>> dhwires; | |
| for (const auto &mux : muxes) { | |
| for (const auto &arc : mux.second.arcs) { | |
| if (arc.second.source == wire) | |
| dhwires.push_back(make_pair(arc.second.sink, true)); | |
| } | |
| } | |
| for (const auto &csink : fixed_conns) { | |
| for (const auto &conn : csink.second) { | |
| if (conn.source == wire) | |
| dhwires.push_back(make_pair(conn.sink, false)); | |
| } | |
| } | |
| return dhwires; | |
| } | |
| void TileBitDatabase::add_routing(const TileInfo &tile, RoutingGraph &graph) const | |
| { | |
| boost::shared_lock_guard<boost::shared_mutex> guard(db_mutex); | |
| int row, col; | |
| tie(row, col) = tile.get_row_col(); | |
| Location loc(col, row); | |
| for (const auto &mux : muxes) { | |
| RoutingId sink = graph.globalise_net(row, col, mux.second.sink); | |
| if (sink == RoutingId()) | |
| continue; | |
| for (const auto &arc : mux.second.arcs) { | |
| RoutingId src = graph.globalise_net(row, col, arc.second.source); | |
| if (src == RoutingId()) | |
| continue; | |
| RoutingArc rarc; | |
| rarc.id = graph.ident(arc.second.source + "->" + arc.second.sink); | |
| rarc.source = src; | |
| rarc.sink = sink; | |
| rarc.tiletype = graph.ident(tile.type); | |
| rarc.configurable = true; | |
| graph.add_arc(loc, rarc); | |
| } | |
| } | |
| for (const auto &fcs : fixed_conns) { | |
| for (const auto &fc : fcs.second) { | |
| RoutingId sink = graph.globalise_net(row, col, fc.sink); | |
| if (sink == RoutingId()) | |
| continue; | |
| RoutingId src = graph.globalise_net(row, col, fc.source); | |
| if (src == RoutingId()) | |
| continue; | |
| RoutingArc rarc; | |
| rarc.id = graph.ident(fc.source + "=>" + fc.sink); | |
| rarc.source = src; | |
| rarc.sink = sink; | |
| rarc.tiletype = graph.ident(tile.type); | |
| rarc.configurable = false; | |
| graph.add_arc(loc, rarc); | |
| } | |
| } | |
| } | |
| void TileBitDatabase::add_mux_arc(const ArcData &arc) | |
| { | |
| boost::lock_guard<boost::shared_mutex> guard(db_mutex); | |
| dirty = true; | |
| if (muxes.find(arc.sink) == muxes.end()) { | |
| MuxBits mux; | |
| mux.sink = arc.sink; | |
| muxes[mux.sink] = mux; | |
| } | |
| MuxBits &curr = muxes.at(arc.sink); | |
| auto found = curr.arcs.find(arc.source); | |
| if (found == curr.arcs.end()) { | |
| curr.arcs[arc.source] = arc; | |
| } else { | |
| if (found->second.bits == arc.bits) { | |
| // In DB already, no-op | |
| } else { | |
| throw DatabaseConflictError(fmt("database conflict: arc " << arc.source << " -> " << arc.sink << | |
| " already in DB, but config bits " << | |
| arc.bits | |
| << " don't match existing DB bits " << | |
| found->second.bits)); | |
| } | |
| } | |
| } | |
| void TileBitDatabase::add_setting_word(const WordSettingBits &wsb) | |
| { | |
| boost::lock_guard<boost::shared_mutex> guard(db_mutex); | |
| dirty = true; | |
| if (words.find(wsb.name) != words.end()) { | |
| WordSettingBits &curr = words.at(wsb.name); | |
| if (curr.bits.size() != wsb.bits.size()) { | |
| throw DatabaseConflictError(fmt("word " << curr.name << " already exists in DB, but new size " | |
| << wsb.bits.size() << " does not match existing size " | |
| << curr.bits.size())); | |
| } | |
| for (size_t i = 0; i < curr.bits.size(); i++) { | |
| if (!(curr.bits.at(i) == wsb.bits.at(i))) { | |
| throw DatabaseConflictError(fmt("bit " << wsb.name << "[" << i << "] already in DB, but config bits " | |
| << wsb.bits.at(i) << " don't match existing DB bits " | |
| << curr.bits.at(i))); | |
| } | |
| } | |
| } else { | |
| words[wsb.name] = wsb; | |
| } | |
| } | |
| void TileBitDatabase::add_setting_enum(const EnumSettingBits &esb) | |
| { | |
| boost::lock_guard<boost::shared_mutex> guard(db_mutex); | |
| dirty = true; | |
| if (enums.find(esb.name) != enums.end()) { | |
| EnumSettingBits &curr = enums.at(esb.name); | |
| for (const auto &opt : esb.options) { | |
| if (curr.options.find(opt.first) == curr.options.end()) { | |
| curr.options[opt.first] = opt.second; | |
| } else { | |
| if (curr.options.at(opt.first) == opt.second) { | |
| // No-op | |
| } else { | |
| throw DatabaseConflictError( | |
| fmt("option " << opt.first << " of " << esb.name << " already in DB, but config bits " | |
| << opt.second << " don't match existing DB bits " | |
| << curr.options.at(opt.first))); | |
| } | |
| } | |
| } | |
| } | |
| enums[esb.name] = esb; | |
| } | |
| void TileBitDatabase::add_fixed_conn(const Trellis::FixedConnection &conn) | |
| { | |
| boost::lock_guard<boost::shared_mutex> guard(db_mutex); | |
| fixed_conns[conn.sink].insert(conn); | |
| dirty = true; | |
| } | |
| TileBitDatabase::TileBitDatabase(const TileBitDatabase &other) | |
| { | |
| UNUSED(other); | |
| assert(false); | |
| terminate(); | |
| } | |
| void TileBitDatabase::remove_fixed_sink(const string &sink) | |
| { | |
| boost::lock_guard<boost::shared_mutex> guard(db_mutex); | |
| fixed_conns.erase(sink); | |
| } | |
| void TileBitDatabase::remove_setting_enum(const string &enum_name) | |
| { | |
| boost::lock_guard<boost::shared_mutex> guard(db_mutex); | |
| enums.erase(enum_name); | |
| } | |
| void TileBitDatabase::remove_setting_word(const string &word_name) | |
| { | |
| boost::lock_guard<boost::shared_mutex> guard(db_mutex); | |
| words.erase(word_name); | |
| } | |
| DatabaseConflictError::DatabaseConflictError(const string &desc) : runtime_error(desc) | |
| {} | |
| TileBitDatabase::~TileBitDatabase() | |
| { | |
| if (dirty) | |
| save(); | |
| } | |
| } |