blob: 9a66029563b1b8129d64c46dbc42f6f060927401 [file] [log] [blame]
/*
* Copyright 2020-2022 F4PGA Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*
* ---
*
* XDC commands
*
* This plugin operates on the existing design and modifies its structure
* based on the content of the XDC (Xilinx Design Constraints) file.
* Since the XDC file consists of Tcl commands it is read using Yosys's
* Tcl interpreter and processed by the new XDC commands imported to the
* Tcl interpreter.
*/
#include "../common/bank_tiles.h"
#include "../common/utils.h"
#include "kernel/log.h"
#include "kernel/register.h"
#include "kernel/rtlil.h"
#include "libs/json11/json11.hpp"
#include <cassert>
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
static bool isInputPort(RTLIL::Wire *wire) { return wire->port_input; }
static bool isOutputPort(RTLIL::Wire *wire) { return wire->port_output; }
enum class SetPropertyOptions { INTERNAL_VREF, IOSTANDARD, SLEW, DRIVE, IN_TERM, IO_LOC_PAIRS };
const std::unordered_map<std::string, SetPropertyOptions> set_property_options_map = {{"INTERNAL_VREF", SetPropertyOptions::INTERNAL_VREF},
{"IOSTANDARD", SetPropertyOptions::IOSTANDARD},
{"SLEW", SetPropertyOptions::SLEW},
{"DRIVE", SetPropertyOptions::DRIVE},
{"IN_TERM", SetPropertyOptions::IN_TERM},
{"LOC", SetPropertyOptions::IO_LOC_PAIRS},
{"PACKAGE_PIN", SetPropertyOptions::IO_LOC_PAIRS}};
// Apart from the common I/OBUFs there is also the GTPE2_CHANNEL primitive which has a total
// of four IOPADs (2 IPADs and 2 OPADs) which are directly connected to the GTP[RT]X[PN] ports
// of the BEL. The GTPE2_CHANNEL holds all the placement constraints information of the
// corresponding PADs
const std::unordered_map<std::string, std::vector<std::string>> supported_primitive_parameters = {
{"OBUF", {"IO_LOC_PAIRS", "IOSTANDARD", "DRIVE", "SLEW", "IN_TERM"}},
{"OBUFDS", {"IO_LOC_PAIRS", "IOSTANDARD", "SLEW", "IN_TERM"}},
{"OBUFTDS", {"IO_LOC_PAIRS", "IOSTANDARD", "SLEW", "IN_TERM"}},
{"IBUF", {"IO_LOC_PAIRS", "IOSTANDARD"}},
{"IOBUF", {"IO_LOC_PAIRS", "IOSTANDARD", "DRIVE", "SLEW", "IN_TERM"}},
{"IOBUFDS", {"IO_LOC_PAIRS", "IOSTANDARD", "SLEW", "IN_TERM"}},
{"IBUFDS_GTE2", {"IO_LOC_PAIRS"}},
{"GTPE2_CHANNEL", {"IO_LOC_PAIRS"}}};
void register_in_tcl_interpreter(const std::string &command)
{
Tcl_Interp *interp = yosys_get_tcl_interp();
std::string tcl_script = stringf("proc %s args { return [yosys %s {*}$args] }", command.c_str(), command.c_str());
Tcl_Eval(interp, tcl_script.c_str());
}
struct GetIOBanks : public Pass {
GetIOBanks(std::function<const BankTilesMap &()> get_bank_tiles) : Pass("get_iobanks", "Set IO Bank number"), get_bank_tiles(get_bank_tiles)
{
register_in_tcl_interpreter(pass_name);
}
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" get_iobanks <bank_number>\n");
log("\n");
log("Get IO Bank number\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *) override
{
if (args.size() < 2) {
log_cmd_error("%s: Missing bank number.\n", pass_name.c_str());
}
auto bank_tiles = get_bank_tiles();
if (bank_tiles.count(std::atoi(args[1].c_str())) == 0) {
log_cmd_error("%s:Bank number %s is not present in the target device.\n", args[1].c_str(), pass_name.c_str());
}
Tcl_Interp *interp = yosys_get_tcl_interp();
Tcl_SetResult(interp, const_cast<char *>(args[1].c_str()), NULL);
log("%s\n", args[1].c_str());
}
std::function<const BankTilesMap &()> get_bank_tiles;
};
struct SetProperty : public Pass {
SetProperty(std::function<const BankTilesMap &()> get_bank_tiles) : Pass("set_property", "Set a given property"), get_bank_tiles(get_bank_tiles)
{
register_in_tcl_interpreter(pass_name);
}
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" set_property PROPERTY VALUE OBJECT\n");
log("or\n");
log(" set_property -dict { PROPERTY VALUE PROPERTY2 VALUE2 } OBJECT\n");
log("\n");
log("Set the given property to the specified value on an object\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
if (design->top_module() == nullptr) {
log_cmd_error("No top module detected\n");
}
if (args.at(1) == "-dict") {
std::string dict_args = args.at(2);
trim(dict_args);
std::stringstream args_stream(dict_args);
std::vector<std::string> tokens;
std::string intermediate;
while (getline(args_stream, intermediate, ' ')) {
if (intermediate != "\0") {
tokens.push_back(intermediate);
}
}
if (tokens.size() % 2 != 0) {
log_cmd_error("Invalid number of dict parameters: %lu.\n", tokens.size());
}
for (long unsigned int i = 0; i < tokens.size(); i += 2) {
std::vector<std::string> new_args(args);
new_args.at(1) = tokens[i];
new_args.at(2) = tokens[i + 1];
read_property(new_args, design);
}
} else {
read_property(args, design);
}
}
void read_property(std::vector<std::string> args, RTLIL::Design *design)
{
std::string option(args[1]);
if (set_property_options_map.count(option) == 0) {
log_warning("set_property: %s option is currently not supported\n", option.c_str());
return;
}
switch (set_property_options_map.at(option)) {
case SetPropertyOptions::INTERNAL_VREF:
process_vref(std::vector<std::string>(args.begin() + 2, args.end()), design);
break;
case SetPropertyOptions::IOSTANDARD:
case SetPropertyOptions::SLEW:
case SetPropertyOptions::DRIVE:
case SetPropertyOptions::IN_TERM:
process_port_parameter(std::vector<std::string>(args.begin() + 1, args.end()), design);
break;
case SetPropertyOptions::IO_LOC_PAIRS: {
// args "set_property LOC PAD PORT" become "IO_LOC_PAIRS PORT:PAD PORT"
std::vector<std::string> new_args(args.begin() + 1, args.end());
new_args.at(0) = "IO_LOC_PAIRS";
new_args.at(1) = new_args.at(2) + ":" + new_args.at(1);
process_port_parameter(new_args, design);
break;
}
default:
assert(false);
}
}
void process_vref(std::vector<std::string> args, RTLIL::Design *design)
{
if (args.size() < 2) {
log_error("set_property INTERNAL_VREF: Incorrect number of arguments.\n");
}
int iobank = std::atoi(args[1].c_str());
auto bank_tiles = get_bank_tiles();
if (bank_tiles.count(iobank) == 0) {
log_cmd_error("set_property INTERNAL_VREF: Invalid IO bank.\n");
}
int internal_vref = 1000 * std::atof(args[0].c_str());
if (internal_vref != 600 && internal_vref != 675 && internal_vref != 750 && internal_vref != 900) {
log("set_property INTERNAL_VREF: Incorrect INTERNAL_VREF value\n");
return;
}
// Create a new BANK module if it hasn't been created so far
RTLIL::Module *top_module = design->top_module();
if (!design->has(ID(BANK))) {
std::string fasm_extra_modules_dir(proc_share_dirname() + "/plugins/fasm_extra_modules");
Pass::call(design, "read_verilog " + fasm_extra_modules_dir + "/BANK.v");
}
// Set parameters on a new bank instance or update an existing one
char bank_cell_name[16];
snprintf(bank_cell_name, 16, "\\bank_cell_%d", iobank);
RTLIL::Cell *bank_cell = top_module->cell(RTLIL::IdString(bank_cell_name));
if (!bank_cell) {
bank_cell = top_module->addCell(RTLIL::IdString(bank_cell_name), ID(BANK));
}
bank_cell->setParam(ID(FASM_EXTRA), RTLIL::Const("INTERNAL_VREF"));
bank_cell->setParam(ID(NUMBER), RTLIL::Const(iobank));
bank_cell->setParam(ID(INTERNAL_VREF), RTLIL::Const(internal_vref));
}
void process_port_parameter(std::vector<std::string> args, RTLIL::Design *design)
{
if (args.size() < 1) {
log_error("set_property: Incorrect number of arguments.\n");
}
std::string parameter(args.at(0));
if (args.size() < 3 || args.at(2).size() == 0) {
log_error("set_property %s: Incorrect number of arguments.\n", parameter.c_str());
}
std::string port_name(args.at(2));
std::string value(args.at(1));
auto port_signal = extract_signal(port_name);
std::string port(port_signal.first);
int port_bit = port_signal.second;
RTLIL::Wire *wire = design->top_module()->wire(RTLIL::escape_id(port));
if (wire == nullptr) {
log_error("Couldn't find port %s\n", port_name.c_str());
}
if (!isInputPort(wire) && !isOutputPort(wire)) {
log_error("Port %s is not a top port\n", port_name.c_str());
}
if (port_bit < wire->start_offset || port_bit >= wire->start_offset + wire->width) {
log_error("Incorrect top port index %d in port %s\n", port_bit, port_name.c_str());
}
// Traverse the port wire
traverse_wire(port_name, design->top_module());
RTLIL::IdString parameter_id(RTLIL::escape_id(parameter));
for (auto cell_obj : design->top_module()->cells_) {
RTLIL::IdString cell_id = cell_obj.first;
RTLIL::Cell *cell = cell_obj.second;
// Check if the cell is of the type we are looking for
auto cell_type_str = RTLIL::unescape_id(cell->type.str());
auto primitive_parameters_iter = supported_primitive_parameters.find(cell_type_str);
if (primitive_parameters_iter == supported_primitive_parameters.end()) {
continue;
}
// Set the parameter on the cell connected to the selected port
for (auto connection : cell->connections_) {
RTLIL::SigSpec cell_signal = connection.second;
if (is_signal_port(cell_signal, port_name)) {
// Check if the attribute is allowed for this module
auto primitive_parameters = primitive_parameters_iter->second;
if (std::find(primitive_parameters.begin(), primitive_parameters.end(), parameter) == primitive_parameters.end()) {
log_error("Cell %s of type %s doesn't support the %s attribute\n", cell->name.c_str(), cell->type.c_str(),
parameter_id.c_str());
}
if (parameter_id == ID(IO_LOC_PAIRS) and cell->hasParam(parameter_id)) {
std::string cur_value(cell->getParam(parameter_id).decode_string());
value = cur_value + "," + value;
}
cell->setParam(parameter_id, RTLIL::Const(value));
log("Setting parameter %s to value %s on cell %s \n", parameter_id.c_str(), value.c_str(), cell_obj.first.c_str());
}
}
}
log("\n");
}
// Search module's connections for the specified destination port
// and traverse from the specified destination wire to the source wire
void traverse_wire(std::string &port_name, RTLIL::Module *module)
{
auto port_signal = extract_signal(port_name);
std::string signal_name(port_signal.first);
auto signal_name_idstr = RTLIL::IdString(RTLIL::escape_id(signal_name));
int port_bit = port_signal.second;
for (auto connection : module->connections_) {
auto dst_sig = connection.first;
auto src_sig = connection.second;
if (dst_sig.is_chunk()) {
auto chunk = dst_sig.as_chunk();
if (chunk.wire) {
if (chunk.wire->name != signal_name_idstr) {
continue;
}
if (port_bit < chunk.offset || port_bit >= (chunk.offset + chunk.width)) {
continue;
}
auto src_wires = src_sig.to_sigbit_vector();
auto src_wire_sigbit = src_wires.at(port_bit - chunk.offset);
if (src_wire_sigbit.wire) {
port_name = src_wires.at(port_bit - chunk.offset).wire->name.str();
if (src_wire_sigbit.offset > 0) {
port_name += "[" + std::to_string(src_wire_sigbit.offset) + "]";
}
return;
}
}
}
}
}
// Extract signal name and port bit information from port name
std::pair<std::string, int> extract_signal(const std::string &port_name)
{
int port_bit(0);
std::string port_str(port_name.size(), '\0');
sscanf(port_name.c_str(), "%[^[][%d]", &port_str[0], &port_bit);
port_str.resize(strlen(port_str.c_str()));
return std::make_pair(port_str, port_bit);
}
// Check if the specified port name is part of the provided connection signal
bool is_signal_port(RTLIL::SigSpec signal, const std::string &port_name)
{
auto port_signal = extract_signal(port_name);
std::string port(port_signal.first);
int port_bit = port_signal.second;
if (signal.is_chunk()) {
auto chunk = signal.as_chunk();
if (chunk.wire) {
// chunk.offset is always indexed from 0. Because of that port_bit must be
// corrected with the chunk.wire->start_offset of the port wire in case it is not 0-indexed.
// Not doing this would cause lack of some properties (e.g. IO_LOC_PAIRS) for
// non-0-indexed ports in final eblif file
return (chunk.wire->name == RTLIL::IdString(RTLIL::escape_id(port))) && ((port_bit - chunk.wire->start_offset) == chunk.offset);
}
}
return false;
}
std::function<const BankTilesMap &()> get_bank_tiles;
};
struct ReadXdc : public Frontend {
ReadXdc()
: Frontend("xdc", "Read XDC file"), GetIOBanks(std::bind(&ReadXdc::get_bank_tiles, this)),
SetProperty(std::bind(&ReadXdc::get_bank_tiles, this))
{
}
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" read_xdc -part_json <part_json_filename> <filename>\n");
log("\n");
log("Read XDC file.\n");
log("\n");
}
void execute(std::istream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *) override
{
if (args.size() < 2) {
log_cmd_error("Missing script file.\n");
}
size_t argidx = 1;
bank_tiles.clear();
if (args[argidx] == "-part_json" && argidx + 1 < args.size()) {
bank_tiles = ::get_bank_tiles(args[++argidx]);
argidx++;
}
extra_args(f, filename, args, argidx);
std::string content{std::istreambuf_iterator<char>(*f), std::istreambuf_iterator<char>()};
log("%s\n", content.c_str());
// According to page 6 of UG903 XDC is tcl, hence quoting of bracketed numbers,
// such as bus indexes, is required. For example "signal[5]" would be typically
// expanded to the concatenation of the string "signal" and result of the function call "5"
// with no arguments. Therefore in TCL the signal indices have to be wrapped in curly braces
// e.g "{signal[5]}" in order for the interpreter to not perform any variable substitution
// or function calls on the wrapped content.
//
// Nevertheless, it's quite common for EDA tools to allow for specifying signal indices
// (e.g. "signal[5]") without using non-expanding quotes.
// Possible TCL implementations of such a feature include registering a TCL command
// for each integer which returns itself but surrounded with brackets or using the 'unknown'
// command which is invoked by the Tcl interpreter whenever a script tries to invoke a command
// that does not exist. In the XDC plugin the latter approach is used, however it's limited to
// the 'read_xdc' command, hence the 'unknown' command works solely on the content of the XDC file.
//
// In this implementation the signal "signal[5]" is expanded in TCL to the concatenation of a string
// and function call, however this time the handling of the non-existent command '5' is passed by
// the interpreter to the 'unknown' command which returns a string that consists of the indice
// integer surrounded by square brackets, i.e. "[5]", effectively expanding the signal to "signal[5]"
// string.
//
Tcl_Interp *interp = yosys_get_tcl_interp();
Tcl_Eval(interp, "rename unknown _original_unknown");
Tcl_Eval(interp, "proc unknown args { return \\[[lindex $args 0]\\] }");
if (Tcl_EvalFile(interp, args[argidx].c_str()) != TCL_OK) {
log_cmd_error("TCL interpreter returned an error: %s\n", Tcl_GetStringResult(interp));
}
Tcl_Eval(interp, "rename unknown \"\"");
Tcl_Eval(interp, "rename _original_unknown unknown");
}
const BankTilesMap &get_bank_tiles() { return bank_tiles; }
BankTilesMap bank_tiles;
struct GetIOBanks GetIOBanks;
struct SetProperty SetProperty;
} ReadXdc;
struct GetBankTiles : public Pass {
GetBankTiles() : Pass("get_bank_tiles", "Inspect IO Bank tiles") { register_in_tcl_interpreter(pass_name); }
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" get_bank_tiles <part_json_file>\n");
log("\n");
log("Inspect IO Bank tiles for the specified part based on the provided JSON file.\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *) override
{
if (args.size() < 2) {
log_cmd_error("Missing JSON file.\n");
}
// Check if the part has the specified bank
auto bank_tiles = get_bank_tiles(args[1]);
if (bank_tiles.size()) {
log("Available bank tiles:\n");
for (auto bank : bank_tiles) {
log("Bank: %d, Tile: %s\n", bank.first, bank.second.c_str());
}
log("\n");
} else {
log("No bank tiles available.\n");
}
}
} GetBankTiles;
PRIVATE_NAMESPACE_END