From fe26ce447155c88f13be1846b36de013e91c13a4 Mon Sep 17 00:00:00 2001 From: Clifford Wolf Date: Sat, 23 Jun 2018 16:20:31 +0200 Subject: Move json parser from frontend/json/ to json/ Signed-off-by: Clifford Wolf --- json/jsonparse.cc | 778 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ json/jsonparse.h | 33 +++ 2 files changed, 811 insertions(+) create mode 100644 json/jsonparse.cc create mode 100644 json/jsonparse.h (limited to 'json') diff --git a/json/jsonparse.cc b/json/jsonparse.cc new file mode 100644 index 00000000..463efdd2 --- /dev/null +++ b/json/jsonparse.cc @@ -0,0 +1,778 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 SymbioticEDA + * + * jsonparse.cc -- liberally copied from the yosys file of the same name by + * + * Copyright (C) 2018 Clifford Wolf + * + * 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 "jsonparse.h" +#include +#include +#include +#include +#include +#include +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +extern bool check_all_nets_driven(Context *ctx); + +namespace JsonParser { + +const bool json_debug = false; + +typedef std::string string; + +template int GetSize(const T &obj) { return obj.size(); } + +struct JsonNode +{ + char type; // S=String, N=Number, A=Array, D=Dict + string data_string; + int data_number; + std::vector data_array; + std::map data_dict; + std::vector data_dict_keys; + + JsonNode(std::istream &f) + { + type = 0; + data_number = 0; + + while (1) { + int ch = f.get(); + + if (ch == EOF) + log_error("Unexpected EOF in JSON file.\n"); + + if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') + continue; + + if (ch == '\"') { + type = 'S'; + + while (1) { + ch = f.get(); + + if (ch == EOF) + log_error("Unexpected EOF in JSON string.\n"); + + if (ch == '\"') + break; + + if (ch == '\\') { + int ch = f.get(); + + if (ch == EOF) + log_error("Unexpected EOF in JSON string.\n"); + } + + data_string += ch; + } + + break; + } + + if ('0' <= ch && ch <= '9') { + type = 'N'; + data_number = ch - '0'; + data_string += ch; + + while (1) { + ch = f.get(); + + if (ch == EOF) + break; + + if (ch == '.') + goto parse_real; + + if (ch < '0' || '9' < ch) { + f.unget(); + break; + } + + data_number = data_number * 10 + (ch - '0'); + data_string += ch; + } + + data_string = ""; + break; + + parse_real: + type = 'S'; + data_number = 0; + data_string += ch; + + while (1) { + ch = f.get(); + + if (ch == EOF) + break; + + if (ch < '0' || '9' < ch) { + f.unget(); + break; + } + + data_string += ch; + } + + break; + } + + if (ch == '[') { + type = 'A'; + + while (1) { + ch = f.get(); + + if (ch == EOF) + log_error("Unexpected EOF in JSON file.\n"); + + if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == ',') + continue; + + if (ch == ']') + break; + + f.unget(); + data_array.push_back(new JsonNode(f)); + } + + break; + } + + if (ch == '{') { + type = 'D'; + + while (1) { + ch = f.get(); + + if (ch == EOF) + log_error("Unexpected EOF in JSON file.\n"); + + if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == ',') + continue; + + if (ch == '}') + break; + + f.unget(); + JsonNode key(f); + + while (1) { + ch = f.get(); + + if (ch == EOF) + log_error("Unexpected EOF in JSON file.\n"); + + if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == ':') + continue; + + f.unget(); + break; + } + + JsonNode *value = new JsonNode(f); + + if (key.type != 'S') + log_error("Unexpected non-string key in JSON dict.\n"); + + data_dict[key.data_string] = value; + data_dict_keys.push_back(key.data_string); + } + + break; + } + + log_error("Unexpected character in JSON file: '%c'\n", ch); + } + } + + ~JsonNode() + { + for (auto it : data_array) + delete it; + for (auto &it : data_dict) + delete it.second; + } +}; + +NetInfo *ground_net(Context *ctx, NetInfo *net) +{ + CellInfo *cell = new CellInfo; + PortInfo port_info; + PortRef port_ref; + + cell->name = ctx->id(net->name.str(ctx) + ".GND"); + cell->type = ctx->id("GND"); + + port_info.name = ctx->id(cell->name.str(ctx) + "[]"); + port_info.net = net; + port_info.type = PORT_OUT; + + port_ref.cell = cell; + port_ref.port = port_info.name; + + net->driver = port_ref; + + cell->ports[port_info.name] = port_info; + + return net; +} + +NetInfo *vcc_net(Context *ctx, NetInfo *net) +{ + CellInfo *cell = new CellInfo; + PortInfo port_info; + PortRef port_ref; + + cell->name = ctx->id(net->name.str(ctx) + ".VCC"); + cell->type = ctx->id("VCC"); + + port_info.name = ctx->id(cell->name.str(ctx) + "[]"); + port_info.net = net; + port_info.type = PORT_OUT; + + port_ref.cell = cell; + port_ref.port = port_info.name; + + net->driver = port_ref; + + cell->ports[port_info.name] = port_info; + + return net; +} + +NetInfo *floating_net(Context *ctx, NetInfo *net) +{ + PortInfo port_info; + PortRef port_ref; + + port_info.name = ctx->id(net->name.str(ctx) + ".floating"); + port_info.net = net; + port_info.type = PORT_OUT; + + port_ref.cell = NULL; + port_ref.port = port_info.name; + + net->driver = port_ref; + + return net; +} + +// +// is_blackbox +// +// Checks the JsonNode for an attributes dictionary, with a "blackbox" entry. +// An item is deemed to be a blackbox if this entry exists and if its +// value is not zero. If the item is a black box, this routine will return +// true, false otherwise +bool is_blackbox(JsonNode *node) +{ + JsonNode *attr_node, *bbox_node; + + if (node->data_dict.count("attributes") == 0) + return false; + attr_node = node->data_dict.at("attributes"); + if (attr_node == NULL) + return false; + if (attr_node->type != 'D') + return false; + if (GetSize(attr_node->data_dict) == 0) + return false; + if (attr_node->data_dict.count("blackbox") == 0) + return false; + bbox_node = attr_node->data_dict.at("blackbox"); + if (bbox_node == NULL) + return false; + if (bbox_node->type != 'N') + log_error("JSON module blackbox is not a number\n"); + if (bbox_node->data_number == 0) + return false; + return true; +} + +void json_import_cell_params(Context *ctx, string &modname, CellInfo *cell, JsonNode *param_node, + std::unordered_map *dest, int param_id) +{ + // + JsonNode *param; + IdString pId; + // + param = param_node->data_dict.at(param_node->data_dict_keys[param_id]); + + pId = ctx->id(param_node->data_dict_keys[param_id]); + if (param->type == 'N') { + (*dest)[pId] = std::to_string(param->data_number); + } else if (param->type == 'S') + (*dest)[pId] = param->data_string; + else + log_error("JSON parameter type of \"%s\' of cell \'%s\' not supported\n", pId.c_str(ctx), + cell->name.c_str(ctx)); + + if (json_debug) + log_info(" Added parameter \'%s\'=%s to cell \'%s\' " + "of module \'%s\'\n", + pId.c_str(ctx), cell->params[pId].c_str(), cell->name.c_str(ctx), modname.c_str()); +} + +static int const_net_idx = 0; + +template +void json_import_ports(Context *ctx, const string &modname, const std::vector &netnames, + const string &obj_name, const string &port_name, JsonNode *dir_node, JsonNode *wire_group_node, + F visitor) +{ + // Examine a port of a cell or the design. For every bit of the port, + // the connected net will be processed and `visitor` will be called + // with (PortType dir, std::string name, NetInfo *net) + assert(dir_node); + + if (json_debug) + log_info(" Examining port %s, node %s\n", port_name.c_str(), obj_name.c_str()); + + if (!wire_group_node) + log_error("JSON no connection match " + "for port_direction \'%s\' of node \'%s\' " + "in module \'%s\'\n", + port_name.c_str(), obj_name.c_str(), modname.c_str()); + + assert(wire_group_node); + + assert(dir_node->type == 'S'); + assert(wire_group_node->type == 'A'); + + PortInfo port_info; + + port_info.name = ctx->id(port_name); + if (dir_node->data_string.compare("input") == 0) + port_info.type = PORT_IN; + else if (dir_node->data_string.compare("output") == 0) + port_info.type = PORT_OUT; + else if (dir_node->data_string.compare("inout") == 0) + port_info.type = PORT_INOUT; + else + log_error("JSON unknown port direction \'%s\' in node \'%s\' " + "of module \'%s\'\n", + dir_node->data_string.c_str(), obj_name.c_str(), modname.c_str()); + // + // Find an update, or create a net to connect + // to this port. + // + NetInfo *this_net = nullptr; + bool is_bus; + + // + // If this port references a bus, then there will be multiple nets + // connected to it, all specified as part of an array. + // + is_bus = (wire_group_node->data_array.size() > 1); + + // Now loop through all of the connections to this port. + if (wire_group_node->data_array.size() == 0) { + // + // There is/are no connections to this port. + // + // Create the port, but leave the net NULL + + visitor(port_info.type, port_info.name.str(ctx), nullptr); + + if (json_debug) + log_info(" Port \'%s\' has no connection in \'%s\'\n", port_info.name.c_str(ctx), obj_name.c_str()); + + } else + for (int index = 0; index < int(wire_group_node->data_array.size()); index++) { + // + JsonNode *wire_node; + PortInfo this_port; + IdString net_id; + // + wire_node = wire_group_node->data_array[index]; + // + // Pick a name for this port + if (is_bus) + this_port.name = ctx->id(port_info.name.str(ctx) + "[" + std::to_string(index) + "]"); + else + this_port.name = port_info.name; + this_port.type = port_info.type; + + if (wire_node->type == 'N') { + int net_num; + + // A simple net, specified by a number + net_num = wire_node->data_number; + if (net_num < int(netnames.size())) + net_id = netnames.at(net_num); + else + net_id = ctx->id(std::to_string(net_num)); + if (ctx->nets.count(net_id) == 0) { + // The net doesn't exist in the design (yet) + // Create in now + + if (json_debug) + log_info(" Generating a new net, \'%d\'\n", net_num); + + this_net = new NetInfo; + this_net->name = net_id; + this_net->driver.cell = NULL; + this_net->driver.port = IdString(); + ctx->nets[net_id] = this_net; + } else { + // + // The net already exists within the design. + // We'll connect to it + // + this_net = ctx->nets[net_id]; + if (json_debug) + log_info(" Reusing net \'%s\', id \'%s\', " + "with driver \'%s\'\n", + this_net->name.c_str(ctx), net_id.c_str(ctx), + (this_net->driver.cell != NULL) ? this_net->driver.port.c_str(ctx) : "NULL"); + } + + } else if (wire_node->type == 'S') { + // Strings are only used to drive wires for the fixed + // values "0", "1", and "x". Handle those constant + // values here. + // + // Constants always get their own new net + this_net = new NetInfo; + this_net->name = ctx->id("$const_" + std::to_string(const_net_idx++)); + + if (wire_node->data_string.compare(string("0")) == 0) { + + if (json_debug) + log_info(" Generating a constant " + "zero net\n"); + this_net = ground_net(ctx, this_net); + + } else if (wire_node->data_string.compare(string("1")) == 0) { + + if (json_debug) + log_info(" Generating a constant " + "one net\n"); + this_net = vcc_net(ctx, this_net); + + } else if (wire_node->data_string.compare(string("x")) == 0) { + + this_net = floating_net(ctx, this_net); + log_warning(" Floating wire node value, " + "\'%s\' of port \'%s\' " + "in cell \'%s\' of module \'%s\'\n", + wire_node->data_string.c_str(), port_name.c_str(), obj_name.c_str(), modname.c_str()); + + } else + log_error(" Unknown fixed type wire node " + "value, \'%s\'\n", + wire_node->data_string.c_str()); + } + + if (json_debug) + log_info(" Inserting port \'%s\' into cell \'%s\'\n", this_port.name.c_str(ctx), obj_name.c_str()); + visitor(this_port.type, this_port.name.str(ctx), this_net); + + if (ctx->nets.count(this_net->name) == 0) + ctx->nets[this_net->name] = this_net; + } +} + +void json_import_cell(Context *ctx, string modname, const std::vector &netnames, JsonNode *cell_node, + string cell_name) +{ + JsonNode *cell_type, *param_node, *attr_node; + + cell_type = cell_node->data_dict.at("type"); + if (cell_type == NULL) + return; + + CellInfo *cell = new CellInfo; + + cell->name = ctx->id(cell_name); + assert(cell_type->type == 'S'); + cell->type = ctx->id(cell_type->data_string); + // No BEL assignment here/yet + + if (json_debug) + log_info(" Processing %s $ %s\n", modname.c_str(), cell->name.c_str(ctx)); + + param_node = cell_node->data_dict.at("parameters"); + if (param_node->type != 'D') + log_error("JSON parameter list of \'%s\' is not a data dictionary\n", cell->name.c_str(ctx)); + + // + // Loop through all parameters, adding them into the + // design to annotate the cell + // + for (int paramid = 0; paramid < GetSize(param_node->data_dict_keys); paramid++) { + + json_import_cell_params(ctx, modname, cell, param_node, &cell->params, paramid); + } + + attr_node = cell_node->data_dict.at("attributes"); + if (attr_node->type != 'D') + log_error("JSON attribute list of \'%s\' is not a data dictionary\n", cell->name.c_str(ctx)); + + // + // Loop through all attributes, adding them into the + // design to annotate the cell + // + for (int attrid = 0; attrid < GetSize(attr_node->data_dict_keys); attrid++) { + + json_import_cell_params(ctx, modname, cell, attr_node, &cell->attrs, attrid); + } + + // + // Now connect the ports of this module. The ports are defined by + // both the port directions node as well as the connections node. + // Both should contain dictionaries having the same keys. + // + + JsonNode *pdir_node = NULL; + if (cell_node->data_dict.count("port_directions") > 0) { + + pdir_node = cell_node->data_dict.at("port_directions"); + if (pdir_node->type != 'D') + log_error("JSON port_directions node of \'%s\' " + "in module \'%s\' is not a " + "dictionary\n", + cell->name.c_str(ctx), modname.c_str()); + + } else if (cell_node->data_dict.count("ports") > 0) { + pdir_node = cell_node->data_dict.at("ports"); + if (pdir_node->type != 'D') + log_error("JSON ports node of \'%s\' " + "in module \'%s\' is not a " + "dictionary\n", + cell->name.c_str(ctx), modname.c_str()); + } + + JsonNode *connections = cell_node->data_dict.at("connections"); + if (connections->type != 'D') + log_error("JSON connections node of \'%s\' " + "in module \'%s\' is not a " + "dictionary\n", + cell->name.c_str(ctx), modname.c_str()); + + if (GetSize(pdir_node->data_dict_keys) != GetSize(connections->data_dict_keys)) + log_error("JSON number of connections doesnt " + "match number of ports in node \'%s\' " + "of module \'%s\'\n", + cell->name.c_str(ctx), modname.c_str()); + + // + // Loop through all of the ports of this logic element + // + for (int portid = 0; portid < GetSize(pdir_node->data_dict_keys); portid++) { + // + string port_name; + JsonNode *dir_node, *wire_group_node; + // + + port_name = pdir_node->data_dict_keys[portid]; + dir_node = pdir_node->data_dict.at(port_name); + wire_group_node = connections->data_dict.at(port_name); + + json_import_ports(ctx, modname, netnames, cell->name.str(ctx), port_name, dir_node, wire_group_node, + [cell, ctx](PortType type, const std::string &name, NetInfo *net) { + cell->ports[ctx->id(name)] = PortInfo{ctx->id(name), net, type}; + PortRef pr; + pr.cell = cell; + pr.port = ctx->id(name); + if (net != nullptr) { + if (type == PORT_IN || type == PORT_INOUT) { + net->users.push_back(pr); + } else if (type == PORT_OUT) { + assert(net->driver.cell == nullptr); + net->driver = pr; + } + } + }); + } + + ctx->cells[cell->name] = cell; + // check_all_nets_driven(ctx); +} + +static void insert_iobuf(Context *ctx, NetInfo *net, PortType type, const string &name) +{ + // Instantiate a architecture-independent IO buffer connected to a given + // net, of a given type, and named after the IO port. + // + // During packing, this generic IO buffer will be converted to an + // architecure primitive. + // + CellInfo *iobuf = new CellInfo(); + iobuf->name = ctx->id(name); + std::copy(net->attrs.begin(), net->attrs.end(), std::inserter(iobuf->attrs, iobuf->attrs.begin())); + if (type == PORT_IN) { + if (ctx->verbose) + log_info("processing input port %s\n", name.c_str()); + iobuf->type = ctx->id("$nextpnr_ibuf"); + iobuf->ports[ctx->id("O")] = PortInfo{ctx->id("O"), net, PORT_OUT}; + // Special case: input, etc, directly drives inout + if (net->driver.cell != nullptr) { + assert(net->driver.cell->type == ctx->id("$nextpnr_iobuf")); + net = net->driver.cell->ports.at(ctx->id("I")).net; + } + assert(net->driver.cell == nullptr); + net->driver.port = ctx->id("O"); + net->driver.cell = iobuf; + } else if (type == PORT_OUT) { + if (ctx->verbose) + log_info("processing output port %s\n", name.c_str()); + iobuf->type = ctx->id("$nextpnr_obuf"); + iobuf->ports[ctx->id("I")] = PortInfo{ctx->id("I"), net, PORT_IN}; + PortRef ref; + ref.cell = iobuf; + ref.port = ctx->id("I"); + net->users.push_back(ref); + } else if (type == PORT_INOUT) { + if (ctx->verbose) + log_info("processing inout port %s\n", name.c_str()); + iobuf->type = ctx->id("$nextpnr_iobuf"); + iobuf->ports[ctx->id("I")] = PortInfo{ctx->id("I"), nullptr, PORT_IN}; + + // Split the input and output nets for bidir ports + NetInfo *net2 = new NetInfo(); + net2->name = ctx->id("$" + net->name.str(ctx) + "$iobuf_i"); + net2->driver = net->driver; + if (net->driver.cell != nullptr) { + net2->driver.cell->ports[net2->driver.port].net = net2; + net->driver.cell = nullptr; + } + ctx->nets[net2->name] = net2; + iobuf->ports[ctx->id("I")].net = net2; + PortRef ref; + ref.cell = iobuf; + ref.port = ctx->id("I"); + net2->users.push_back(ref); + + iobuf->ports[ctx->id("O")] = PortInfo{ctx->id("O"), net, PORT_OUT}; + assert(net->driver.cell == nullptr); + net->driver.port = ctx->id("O"); + net->driver.cell = iobuf; + } else { + assert(false); + } + ctx->cells[iobuf->name] = iobuf; +} + +void json_import_toplevel_port(Context *ctx, const string &modname, const std::vector &netnames, + const string &portname, JsonNode *node) +{ + JsonNode *dir_node = node->data_dict.at("direction"); + JsonNode *nets_node = node->data_dict.at("bits"); + json_import_ports( + ctx, modname, netnames, "Top Level IO", portname, dir_node, nets_node, + [ctx](PortType type, const std::string &name, NetInfo *net) { insert_iobuf(ctx, net, type, name); }); +} + +void json_import(Context *ctx, string modname, JsonNode *node) +{ + if (is_blackbox(node)) + return; + + log_info("Importing module %s\n", modname.c_str()); + + // Import netnames + std::vector netnames; + if (node->data_dict.count("netnames")) { + JsonNode *cell_parent = node->data_dict.at("netnames"); + for (int nnid = 0; nnid < GetSize(cell_parent->data_dict_keys); nnid++) { + JsonNode *here; + + here = cell_parent->data_dict.at(cell_parent->data_dict_keys[nnid]); + std::string basename = cell_parent->data_dict_keys[nnid]; + if (here->data_dict.count("bits")) { + JsonNode *bits = here->data_dict.at("bits"); + assert(bits->type == 'A'); + size_t num_bits = bits->data_array.size(); + for (size_t i = 0; i < num_bits; i++) { + int netid = bits->data_array.at(i)->data_number; + if (netid >= int(netnames.size())) + netnames.resize(netid + 1); + netnames.at(netid) = ctx->id( + basename + (num_bits == 1 ? "" : std::string("[") + std::to_string(i) + std::string("]"))); + } + } + } + } + + if (node->data_dict.count("cells")) { + JsonNode *cell_parent = node->data_dict.at("cells"); + // + // + // Loop through all of the logic elements in a flattened design + // + // + for (int cellid = 0; cellid < GetSize(cell_parent->data_dict_keys); cellid++) { + JsonNode *here = cell_parent->data_dict.at(cell_parent->data_dict_keys[cellid]); + json_import_cell(ctx, modname, netnames, here, cell_parent->data_dict_keys[cellid]); + } + } + + if (node->data_dict.count("ports")) { + JsonNode *ports_parent = node->data_dict.at("ports"); + + // N.B. ports must be imported after cells for tristate behaviour + // to be correct + // Loop through all ports + for (int portid = 0; portid < GetSize(ports_parent->data_dict_keys); portid++) { + JsonNode *here; + + here = ports_parent->data_dict.at(ports_parent->data_dict_keys[portid]); + json_import_toplevel_port(ctx, modname, netnames, ports_parent->data_dict_keys[portid], here); + } + } + check_all_nets_driven(ctx); +} +}; // End Namespace JsonParser + +bool parse_json_file(std::istream &f, std::string &filename, Context *ctx) +{ + try { + using namespace JsonParser; + + JsonNode root(f); + + if (root.type != 'D') + log_error("JSON root node is not a dictionary.\n"); + + if (root.data_dict.count("modules") != 0) { + JsonNode *modules = root.data_dict.at("modules"); + + if (modules->type != 'D') + log_error("JSON modules node is not a dictionary.\n"); + + for (auto &it : modules->data_dict) + json_import(ctx, it.first, it.second); + } + + log_info("Checksum: 0x%08x\n", ctx->checksum()); + log_break(); + return true; + } catch (log_execution_error_exception) { + return false; + } +} + +NEXTPNR_NAMESPACE_END diff --git a/json/jsonparse.h b/json/jsonparse.h new file mode 100644 index 00000000..fe71444f --- /dev/null +++ b/json/jsonparse.h @@ -0,0 +1,33 @@ +/* + * nextpnr -- Next Generation Place and Route + * + * Copyright (C) 2018 SymbioticEDA + * + * 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. + * + */ + +#ifndef JSON_PARSER +#define JSON_PARSER + +#include +#include +#include "nextpnr.h" + +NEXTPNR_NAMESPACE_BEGIN + +extern bool parse_json_file(std::istream &, std::string &, Context *); + +NEXTPNR_NAMESPACE_END + +#endif -- cgit v1.2.3