mirror of
https://github.com/YosysHQ/yosys
synced 2025-10-09 09:21:58 +00:00
Merge 0aa2a4c260
into 89f32a415b
This commit is contained in:
commit
ea84ab0bf3
14 changed files with 950 additions and 5 deletions
1
Makefile
1
Makefile
|
@ -886,6 +886,7 @@ MK_TEST_DIRS += tests/arch/xilinx
|
||||||
MK_TEST_DIRS += tests/bugpoint
|
MK_TEST_DIRS += tests/bugpoint
|
||||||
MK_TEST_DIRS += tests/opt
|
MK_TEST_DIRS += tests/opt
|
||||||
MK_TEST_DIRS += tests/sat
|
MK_TEST_DIRS += tests/sat
|
||||||
|
MK_TEST_DIRS += tests/sdc
|
||||||
MK_TEST_DIRS += tests/sim
|
MK_TEST_DIRS += tests/sim
|
||||||
MK_TEST_DIRS += tests/svtypes
|
MK_TEST_DIRS += tests/svtypes
|
||||||
MK_TEST_DIRS += tests/techmap
|
MK_TEST_DIRS += tests/techmap
|
||||||
|
|
|
@ -188,7 +188,7 @@ extern char yosys_path[PATH_MAX];
|
||||||
#endif
|
#endif
|
||||||
#ifdef YOSYS_ENABLE_TCL
|
#ifdef YOSYS_ENABLE_TCL
|
||||||
namespace Yosys {
|
namespace Yosys {
|
||||||
extern int yosys_tcl_iterp_init(Tcl_Interp *interp);
|
extern int yosys_tcl_interp_init(Tcl_Interp *interp);
|
||||||
extern void yosys_tcl_activate_repl();
|
extern void yosys_tcl_activate_repl();
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
@ -610,7 +610,7 @@ int main(int argc, char **argv)
|
||||||
if (run_tcl_shell) {
|
if (run_tcl_shell) {
|
||||||
#ifdef YOSYS_ENABLE_TCL
|
#ifdef YOSYS_ENABLE_TCL
|
||||||
yosys_tcl_activate_repl();
|
yosys_tcl_activate_repl();
|
||||||
Tcl_Main(argc, argv, yosys_tcl_iterp_init);
|
Tcl_Main(argc, argv, yosys_tcl_interp_init);
|
||||||
#else
|
#else
|
||||||
log_error("Can't exectue TCL shell: this version of yosys is not built with TCL support enabled.\n");
|
log_error("Can't exectue TCL shell: this version of yosys is not built with TCL support enabled.\n");
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -533,7 +533,7 @@ static int tcl_set_param(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj *cons
|
||||||
return TCL_OK;
|
return TCL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int yosys_tcl_iterp_init(Tcl_Interp *interp)
|
int yosys_tcl_interp_init(Tcl_Interp *interp)
|
||||||
{
|
{
|
||||||
if (Tcl_Init(interp)!=TCL_OK)
|
if (Tcl_Init(interp)!=TCL_OK)
|
||||||
log_warning("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno()));
|
log_warning("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno()));
|
||||||
|
|
|
@ -95,6 +95,7 @@ CellTypes yosys_celltypes;
|
||||||
|
|
||||||
#ifdef YOSYS_ENABLE_TCL
|
#ifdef YOSYS_ENABLE_TCL
|
||||||
Tcl_Interp *yosys_tcl_interp = NULL;
|
Tcl_Interp *yosys_tcl_interp = NULL;
|
||||||
|
Tcl_Interp *yosys_sdc_interp = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::set<std::string> yosys_input_files, yosys_output_files;
|
std::set<std::string> yosys_input_files, yosys_output_files;
|
||||||
|
@ -392,17 +393,18 @@ void rewrite_filename(std::string &filename)
|
||||||
#ifdef YOSYS_ENABLE_TCL
|
#ifdef YOSYS_ENABLE_TCL
|
||||||
|
|
||||||
// defined in tclapi.cc
|
// defined in tclapi.cc
|
||||||
extern int yosys_tcl_iterp_init(Tcl_Interp *interp);
|
extern int yosys_tcl_interp_init(Tcl_Interp *interp);
|
||||||
|
|
||||||
extern Tcl_Interp *yosys_get_tcl_interp()
|
extern Tcl_Interp *yosys_get_tcl_interp()
|
||||||
{
|
{
|
||||||
if (yosys_tcl_interp == NULL) {
|
if (yosys_tcl_interp == NULL) {
|
||||||
yosys_tcl_interp = Tcl_CreateInterp();
|
yosys_tcl_interp = Tcl_CreateInterp();
|
||||||
yosys_tcl_iterp_init(yosys_tcl_interp);
|
yosys_tcl_interp_init(yosys_tcl_interp);
|
||||||
}
|
}
|
||||||
return yosys_tcl_interp;
|
return yosys_tcl_interp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Also see SdcPass
|
||||||
struct TclPass : public Pass {
|
struct TclPass : public Pass {
|
||||||
TclPass() : Pass("tcl", "execute a TCL script file") { }
|
TclPass() : Pass("tcl", "execute a TCL script file") { }
|
||||||
void help() override {
|
void help() override {
|
||||||
|
@ -445,6 +447,7 @@ struct TclPass : public Pass {
|
||||||
Tcl_Release(interp);
|
Tcl_Release(interp);
|
||||||
}
|
}
|
||||||
} TclPass;
|
} TclPass;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(__linux__) || defined(__CYGWIN__)
|
#if defined(__linux__) || defined(__CYGWIN__)
|
||||||
|
|
|
@ -57,3 +57,5 @@ OBJS += passes/cmds/abstract.o
|
||||||
OBJS += passes/cmds/test_select.o
|
OBJS += passes/cmds/test_select.o
|
||||||
OBJS += passes/cmds/timeest.o
|
OBJS += passes/cmds/timeest.o
|
||||||
OBJS += passes/cmds/linecoverage.o
|
OBJS += passes/cmds/linecoverage.o
|
||||||
|
|
||||||
|
include $(YOSYS_SRC)/passes/cmds/sdc/Makefile.inc
|
||||||
|
|
3
passes/cmds/sdc/Makefile.inc
Normal file
3
passes/cmds/sdc/Makefile.inc
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
OBJS += passes/cmds/sdc/sdc.o
|
||||||
|
|
||||||
|
$(eval $(call add_share_file,share/sdc,passes/cmds/sdc/graph-stubs.sdc))
|
18
passes/cmds/sdc/graph-stubs.sdc
Normal file
18
passes/cmds/sdc/graph-stubs.sdc
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
proc unknown {args} {
|
||||||
|
global sdc_call_index
|
||||||
|
global sdc_calls
|
||||||
|
if {![info exists sdc_call_index]} {
|
||||||
|
set sdc_call_index 0
|
||||||
|
}
|
||||||
|
if {![info exists sdc_calls]} {
|
||||||
|
set sdc_calls {}
|
||||||
|
}
|
||||||
|
set ret "YOSYS_SDC_MAGIC_NODE_$sdc_call_index"
|
||||||
|
incr sdc_call_index
|
||||||
|
lappend sdc_calls $args
|
||||||
|
# puts "unknown $args, returning YOSYS_SDC_MAGIC_NODE_$sdc_call_index"
|
||||||
|
return $ret
|
||||||
|
}
|
||||||
|
proc list {args} {
|
||||||
|
return [unknown "list" {*}$args]
|
||||||
|
}
|
762
passes/cmds/sdc/sdc.cc
Normal file
762
passes/cmds/sdc/sdc.cc
Normal file
|
@ -0,0 +1,762 @@
|
||||||
|
#ifdef YOSYS_ENABLE_TCL
|
||||||
|
|
||||||
|
#include "kernel/register.h"
|
||||||
|
#include "kernel/rtlil.h"
|
||||||
|
#include "kernel/log.h"
|
||||||
|
#include <tcl.h>
|
||||||
|
#include <list>
|
||||||
|
#include <optional>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
|
||||||
|
USING_YOSYS_NAMESPACE
|
||||||
|
PRIVATE_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
struct TclCall {
|
||||||
|
Tcl_Interp *interp;
|
||||||
|
int objc;
|
||||||
|
Tcl_Obj* const* objv;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int redirect_unknown(TclCall call);
|
||||||
|
static size_t get_node_count(Tcl_Interp* interp);
|
||||||
|
|
||||||
|
struct BitSelection {
|
||||||
|
bool all = false;
|
||||||
|
std::vector<bool> bits = {};
|
||||||
|
void set_all() {
|
||||||
|
bits.clear();
|
||||||
|
all = true;
|
||||||
|
}
|
||||||
|
void clear() {
|
||||||
|
bits.clear();
|
||||||
|
all = false;
|
||||||
|
}
|
||||||
|
void set(size_t idx) {
|
||||||
|
if (all)
|
||||||
|
return;
|
||||||
|
if (idx >= bits.size())
|
||||||
|
bits.resize(idx + 1);
|
||||||
|
bits[idx] = true;
|
||||||
|
}
|
||||||
|
void merge(const BitSelection& other) {
|
||||||
|
if (all)
|
||||||
|
return;
|
||||||
|
if (other.all) {
|
||||||
|
set_all();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (other.bits.size() > bits.size())
|
||||||
|
bits.resize(other.bits.size());
|
||||||
|
for (size_t other_idx = 0; other_idx < other.bits.size(); other_idx++) {
|
||||||
|
bool other_bit = other.bits[other_idx];
|
||||||
|
if (other_bit)
|
||||||
|
set(other_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void dump() {
|
||||||
|
if (!all) {
|
||||||
|
for (size_t i = 0; i < bits.size(); i++)
|
||||||
|
if (bits[i])
|
||||||
|
log("\t\t [%zu]\n", i);
|
||||||
|
} else {
|
||||||
|
log("\t\t FULL\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool is_set(size_t idx) const {
|
||||||
|
if (all)
|
||||||
|
return true;
|
||||||
|
if (idx >= bits.size())
|
||||||
|
return false;
|
||||||
|
return bits[idx];
|
||||||
|
}
|
||||||
|
// TODO actually use this
|
||||||
|
void compress(size_t size) {
|
||||||
|
if (bits.size() < size)
|
||||||
|
return;
|
||||||
|
for (size_t i = 0; i < size; i++)
|
||||||
|
if (!bits[i])
|
||||||
|
return;
|
||||||
|
bits.clear();
|
||||||
|
bits.shrink_to_fit();
|
||||||
|
all = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SdcObjects {
|
||||||
|
enum CollectMode {
|
||||||
|
// getter-side object tracking with minimal features
|
||||||
|
SimpleGetter,
|
||||||
|
// getter-side object tracking with everything
|
||||||
|
FullGetter,
|
||||||
|
// constraint-side tracking
|
||||||
|
FullConstraint,
|
||||||
|
} collect_mode;
|
||||||
|
using CellPin = std::pair<Cell*, IdString>;
|
||||||
|
Design* design;
|
||||||
|
std::vector<std::pair<std::string, Wire*>> design_ports;
|
||||||
|
std::vector<std::pair<std::string, Cell*>> design_cells;
|
||||||
|
std::vector<std::pair<std::string, CellPin>> design_pins;
|
||||||
|
std::vector<std::pair<std::string, Wire*>> design_nets;
|
||||||
|
|
||||||
|
using PortPattern = std::tuple<std::string, Wire*, BitSelection>;
|
||||||
|
using PinPattern = std::tuple<std::string, SdcObjects::CellPin, BitSelection>;
|
||||||
|
std::vector<std::vector<PortPattern>> resolved_port_pattern_sets;
|
||||||
|
std::vector<std::vector<PinPattern>> resolved_pin_pattern_sets;
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
dict<std::pair<std::string, Wire*>, BitSelection> constrained_ports;
|
||||||
|
pool<std::pair<std::string, Cell*>> constrained_cells;
|
||||||
|
dict<std::pair<std::string, CellPin>, BitSelection> constrained_pins;
|
||||||
|
dict<std::pair<std::string, Wire*>, BitSelection> constrained_nets;
|
||||||
|
|
||||||
|
void sniff_module(std::list<std::string>& hierarchy, Module* mod) {
|
||||||
|
std::string prefix;
|
||||||
|
for (auto mod_name : hierarchy) {
|
||||||
|
if (prefix.length())
|
||||||
|
prefix += "/"; // TODO seperator?
|
||||||
|
prefix += mod_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto* wire : mod->wires()) {
|
||||||
|
std::string name = wire->name.str();
|
||||||
|
log_assert(name.length());
|
||||||
|
// TODO: really skip internal wires?
|
||||||
|
if (name[0] == '$')
|
||||||
|
continue;
|
||||||
|
name = name.substr(1);
|
||||||
|
std::string path = prefix;
|
||||||
|
if (path.length())
|
||||||
|
path += "/";
|
||||||
|
path += name;
|
||||||
|
design_nets.push_back(std::make_pair(path, wire));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto* cell : mod->cells()) {
|
||||||
|
std::string name = cell->name.str();
|
||||||
|
// TODO: really skip internal cells?
|
||||||
|
if (name[0] == '$')
|
||||||
|
continue;
|
||||||
|
name = name.substr(1);
|
||||||
|
std::string path = prefix;
|
||||||
|
if (path.length())
|
||||||
|
path += "/";
|
||||||
|
path += name;
|
||||||
|
design_cells.push_back(std::make_pair(path, cell));
|
||||||
|
for (auto pin : cell->connections()) {
|
||||||
|
IdString pin_name = pin.first;
|
||||||
|
std::string pin_name_sdc = path + "/" + pin.first.str().substr(1);
|
||||||
|
design_pins.push_back(std::make_pair(pin_name_sdc, std::make_pair(cell, pin_name)));
|
||||||
|
}
|
||||||
|
if (auto sub_mod = mod->design->module(cell->type)) {
|
||||||
|
hierarchy.push_back(name);
|
||||||
|
sniff_module(hierarchy, sub_mod);
|
||||||
|
hierarchy.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SdcObjects(Design* design) : design(design) {
|
||||||
|
Module* top = design->top_module();
|
||||||
|
if (!top)
|
||||||
|
log_error("Top module couldn't be determined. Check 'top' attribute usage");
|
||||||
|
for (auto port : top->ports) {
|
||||||
|
design_ports.push_back(std::make_pair(port.str().substr(1), top->wire(port)));
|
||||||
|
}
|
||||||
|
std::list<std::string> hierarchy{};
|
||||||
|
sniff_module(hierarchy, top);
|
||||||
|
}
|
||||||
|
~SdcObjects() = default;
|
||||||
|
|
||||||
|
template <typename T, typename U>
|
||||||
|
void build_normal_result(Tcl_Interp* interp, std::vector<std::tuple<std::string, T, BitSelection>>&& resolved, U& tgt, std::function<size_t(T&)> width, Tcl_Obj*& result) {
|
||||||
|
if (!result)
|
||||||
|
result = Tcl_NewListObj(resolved.size(), nullptr);
|
||||||
|
for (auto [name, obj, matching_bits] : resolved) {
|
||||||
|
for (size_t i = 0; i < width(obj); i++)
|
||||||
|
if (matching_bits.is_set(i)) {
|
||||||
|
Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(name.c_str(), name.size()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
size_t node_count = get_node_count(interp);
|
||||||
|
tgt.emplace_back(std::move(resolved));
|
||||||
|
log("%zu %zu\n", node_count, tgt.size());
|
||||||
|
log_assert(node_count == tgt.size());
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
void merge_as_constrained(std::vector<std::tuple<std::string, T, BitSelection>>&& resolved) {
|
||||||
|
for (auto [name, obj, matching_bits] : resolved) {
|
||||||
|
merge_or_init(std::make_pair(name, obj), constrained_pins, matching_bits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void dump() {
|
||||||
|
std::sort(design_ports.begin(), design_ports.end());
|
||||||
|
std::sort(design_cells.begin(), design_cells.end());
|
||||||
|
std::sort(design_pins.begin(), design_pins.end());
|
||||||
|
std::sort(design_nets.begin(), design_nets.end());
|
||||||
|
constrained_ports.sort();
|
||||||
|
constrained_cells.sort();
|
||||||
|
constrained_pins.sort();
|
||||||
|
constrained_nets.sort();
|
||||||
|
// log("Design ports:\n");
|
||||||
|
// for (auto name : design_ports) {
|
||||||
|
// log("\t%s\n", name.c_str());
|
||||||
|
// }
|
||||||
|
// log("Design cells:\n");
|
||||||
|
// for (auto [name, cell] : design_cells) {
|
||||||
|
// (void)cell;
|
||||||
|
// log("\t%s\n", name.c_str());
|
||||||
|
// }
|
||||||
|
// log("Design pins:\n");
|
||||||
|
// for (auto [name, pin] : design_pins) {
|
||||||
|
// (void)pin;
|
||||||
|
// log("\t%s\n", name.c_str());
|
||||||
|
// }
|
||||||
|
// log("Design nets:\n");
|
||||||
|
// for (auto [name, net] : design_nets) {
|
||||||
|
// (void)net;
|
||||||
|
// log("\t%s\n", name.c_str());
|
||||||
|
// }
|
||||||
|
// log("\n");
|
||||||
|
log("Constrained ports:\n");
|
||||||
|
for (auto [ref, bits] : constrained_ports) {
|
||||||
|
auto [name, port] = ref;
|
||||||
|
(void)port;
|
||||||
|
log("\t%s\n", name.c_str());
|
||||||
|
bits.dump();
|
||||||
|
}
|
||||||
|
log("Constrained cells:\n");
|
||||||
|
for (auto& [name, cell] : constrained_cells) {
|
||||||
|
(void)cell;
|
||||||
|
log("\t%s\n", name.c_str());
|
||||||
|
}
|
||||||
|
log("Constrained pins:\n");
|
||||||
|
for (auto& [ref, bits] : constrained_pins) {
|
||||||
|
auto [name, pin] = ref;
|
||||||
|
(void)pin;
|
||||||
|
log("\t%s\n", name.c_str());
|
||||||
|
bits.dump();
|
||||||
|
}
|
||||||
|
log("Constrained nets:\n");
|
||||||
|
for (auto& [ref, bits] : constrained_nets) {
|
||||||
|
auto [name, net] = ref;
|
||||||
|
(void)net;
|
||||||
|
log("\t%s\n", name.c_str());
|
||||||
|
bits.dump();
|
||||||
|
}
|
||||||
|
log("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
class KeepHierarchyWorker {
|
||||||
|
std::unordered_set<Module*> tracked_modules = {};
|
||||||
|
Design* design = nullptr;
|
||||||
|
bool mark(Module* mod) {
|
||||||
|
for (auto* cell : mod->cells()) {
|
||||||
|
if (auto* submod = design->module(cell->type))
|
||||||
|
if (mark(submod)) {
|
||||||
|
mod->set_bool_attribute(ID::keep_hierarchy);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tracked_modules.count(mod)) {
|
||||||
|
mod->set_bool_attribute(ID::keep_hierarchy);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
KeepHierarchyWorker(SdcObjects* objects, Design* d) : design(d) {
|
||||||
|
for (auto [ref, _] : objects->constrained_ports) {
|
||||||
|
tracked_modules.insert(ref.second->module);
|
||||||
|
}
|
||||||
|
for (auto& [_, cell] : objects->constrained_cells) {
|
||||||
|
tracked_modules.insert(cell->module);
|
||||||
|
}
|
||||||
|
for (auto& [ref, _] : objects->constrained_pins) {
|
||||||
|
tracked_modules.insert(ref.second.first->module);
|
||||||
|
}
|
||||||
|
for (auto& [ref, _] : objects->constrained_nets) {
|
||||||
|
tracked_modules.insert(ref.second->module);
|
||||||
|
}
|
||||||
|
log_debug("keep_hierarchy tracked modules:\n");
|
||||||
|
for (auto* mod : tracked_modules)
|
||||||
|
log_debug("\t%s\n", mod->name);
|
||||||
|
}
|
||||||
|
bool mark() {
|
||||||
|
return mark(design->top_module());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
void keep_hierarchy() {
|
||||||
|
(void)KeepHierarchyWorker(this, design).mark();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO vectors
|
||||||
|
// TODO cell arrays?
|
||||||
|
struct MatchConfig {
|
||||||
|
enum MatchMode {
|
||||||
|
WILDCARD,
|
||||||
|
REGEX,
|
||||||
|
} match;
|
||||||
|
bool match_case;
|
||||||
|
enum HierMode {
|
||||||
|
FLAT,
|
||||||
|
TREE,
|
||||||
|
} hier;
|
||||||
|
MatchConfig(bool regexp_flag, bool nocase_flag, bool hierarchical_flag) :
|
||||||
|
match(regexp_flag ? REGEX : WILDCARD),
|
||||||
|
match_case(!nocase_flag),
|
||||||
|
hier(hierarchical_flag ? FLAT : TREE) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::pair<bool, BitSelection> matches(std::string name, const std::string& pat, const MatchConfig& config) {
|
||||||
|
(void)config;
|
||||||
|
bool got_bit_index = false;;
|
||||||
|
int bit_idx;
|
||||||
|
std::string pat_base = pat;
|
||||||
|
size_t pos = pat.rfind('[');
|
||||||
|
if (pos != std::string::npos) {
|
||||||
|
got_bit_index = true;
|
||||||
|
pat_base = pat.substr(0, pos);
|
||||||
|
std::string bit_selector = pat.substr(pos + 1, pat.rfind(']') - pos - 1);
|
||||||
|
for (auto c : bit_selector)
|
||||||
|
if (!std::isdigit(c))
|
||||||
|
log_error("Unsupported bit selector %s in SDC pattern %s\n",
|
||||||
|
bit_selector.c_str(), pat.c_str());
|
||||||
|
bit_idx = std::stoi(bit_selector);
|
||||||
|
|
||||||
|
}
|
||||||
|
BitSelection bits = {};
|
||||||
|
if (name == pat_base) {
|
||||||
|
if (got_bit_index) {
|
||||||
|
bits.set(bit_idx);
|
||||||
|
return std::make_pair(true, bits);
|
||||||
|
} else {
|
||||||
|
bits.set_all();
|
||||||
|
return std::make_pair(true, bits);
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return std::make_pair(false, bits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int graph_node(TclCall call) {
|
||||||
|
// TODO is that it?
|
||||||
|
return redirect_unknown(call);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int redirect_unknown(TclCall call) {
|
||||||
|
// TODO redirect to different command
|
||||||
|
Tcl_Obj *newCmd = Tcl_NewStringObj("unknown", -1);
|
||||||
|
auto newObjc = call.objc + 1;
|
||||||
|
Tcl_Obj **newObjv = new Tcl_Obj*[newObjc];
|
||||||
|
newObjv[0] = newCmd;
|
||||||
|
for (int i = 1; i < newObjc; i++) {
|
||||||
|
newObjv[i] = call.objv[i - 1];
|
||||||
|
}
|
||||||
|
int result = Tcl_EvalObjv(call.interp, newObjc, newObjv, 0);
|
||||||
|
Tcl_DecrRefCount(newCmd);
|
||||||
|
delete[] newObjv;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct SdcGraphNode {
|
||||||
|
using Child = std::variant<SdcGraphNode*, std::string>;
|
||||||
|
std::vector<Child> children;
|
||||||
|
SdcGraphNode() = default;
|
||||||
|
void addChild(SdcGraphNode* child) {
|
||||||
|
children.push_back(child);
|
||||||
|
}
|
||||||
|
void addChild(std::string tcl_string) {
|
||||||
|
children.push_back(tcl_string);
|
||||||
|
}
|
||||||
|
void dump(std::ostream& os) const {
|
||||||
|
bool first = true;
|
||||||
|
for (auto child : children) {
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
os << " ";
|
||||||
|
}
|
||||||
|
if (auto* s = std::get_if<std::string>(&child))
|
||||||
|
os << *s;
|
||||||
|
else if (SdcGraphNode*& c = *std::get_if<SdcGraphNode*>(&child)) {
|
||||||
|
os << "[";
|
||||||
|
c->dump(os);
|
||||||
|
os << "]";
|
||||||
|
} else {
|
||||||
|
log_assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static size_t get_node_count(Tcl_Interp* interp) {
|
||||||
|
const char* idx_raw = Tcl_GetVar(interp, "sdc_call_index", TCL_GLOBAL_ONLY);
|
||||||
|
log_assert(idx_raw);
|
||||||
|
std::string idx(idx_raw);
|
||||||
|
for (auto c : idx)
|
||||||
|
if (!std::isdigit(c))
|
||||||
|
log_error("sdc_call_index non-numeric value %s\n", idx.c_str());
|
||||||
|
return std::stoi(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::vector<std::string>> gather_nested_calls(Tcl_Interp* interp) {
|
||||||
|
|
||||||
|
Tcl_Obj* listObj = Tcl_GetVar2Ex(interp, "sdc_calls", nullptr, TCL_GLOBAL_ONLY);
|
||||||
|
int listLength;
|
||||||
|
|
||||||
|
std::vector<std::vector<std::string>> sdc_calls;
|
||||||
|
if (Tcl_ListObjLength(interp, listObj, &listLength) == TCL_OK) {
|
||||||
|
for (int i = 0; i < listLength; i++) {
|
||||||
|
Tcl_Obj* subListObj;
|
||||||
|
std::vector<std::string> subList;
|
||||||
|
if (Tcl_ListObjIndex(interp, listObj, i, &subListObj) != TCL_OK) {
|
||||||
|
log_error("broken list of lists\n");
|
||||||
|
}
|
||||||
|
int subListLength;
|
||||||
|
if (Tcl_ListObjLength(interp, subListObj, &subListLength) == TCL_OK) {
|
||||||
|
// Valid list - extract elements
|
||||||
|
for (int j = 0; j < subListLength; j++) {
|
||||||
|
Tcl_Obj* elementObj;
|
||||||
|
if (Tcl_ListObjIndex(interp, subListObj, j, &elementObj) == TCL_OK) {
|
||||||
|
const char* elementStr = Tcl_GetString(elementObj);
|
||||||
|
subList.push_back(std::string(elementStr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Single element, not a list
|
||||||
|
const char* elementStr = Tcl_GetString(subListObj);
|
||||||
|
subList.push_back(std::string(elementStr));
|
||||||
|
}
|
||||||
|
sdc_calls.push_back(subList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log_assert(sdc_calls.size() == get_node_count(interp));
|
||||||
|
return sdc_calls;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<SdcGraphNode> build_graph(const std::vector<std::vector<std::string>>& sdc_calls) {
|
||||||
|
size_t node_count = sdc_calls.size();
|
||||||
|
std::vector<SdcGraphNode> graph(node_count);
|
||||||
|
for (size_t i = 0; i < node_count; i++) {
|
||||||
|
auto& new_node = graph[i];
|
||||||
|
for (size_t j = 0; j < sdc_calls[i].size(); j++) {
|
||||||
|
auto arg = sdc_calls[i][j];
|
||||||
|
const std::string prefix = "YOSYS_SDC_MAGIC_NODE_";
|
||||||
|
auto pos = arg.find(prefix);
|
||||||
|
if (pos != std::string::npos) {
|
||||||
|
std::string rest = arg.substr(pos + prefix.length());
|
||||||
|
for (auto c : rest)
|
||||||
|
if (!std::isdigit(c))
|
||||||
|
log_error("weird thing %s in %s\n", rest.c_str(), arg.c_str());
|
||||||
|
size_t arg_node_idx = std::stoi(rest);
|
||||||
|
log_assert(arg_node_idx < graph.size());
|
||||||
|
new_node.addChild(&graph[arg_node_idx]);
|
||||||
|
} else {
|
||||||
|
new_node.addChild(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<bool> node_ownership(const std::vector<SdcGraphNode>& graph) {
|
||||||
|
std::vector<bool> has_parent(graph.size());
|
||||||
|
for (auto node : graph) {
|
||||||
|
for (auto child : node.children) {
|
||||||
|
if (SdcGraphNode** pp = std::get_if<SdcGraphNode*>(&child)) {
|
||||||
|
size_t idx = std::distance(&graph.front(), (const SdcGraphNode*)*pp);
|
||||||
|
log_assert(idx < has_parent.size());
|
||||||
|
has_parent[idx] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return has_parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dump_sdc_graph(const std::vector<SdcGraphNode>& graph, const std::vector<bool>& has_parent) {
|
||||||
|
for (size_t i = 0; i < graph.size(); i++) {
|
||||||
|
if (!has_parent[i]) {
|
||||||
|
graph[i].dump(std::cout);
|
||||||
|
std::cout << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void inspect_globals(Tcl_Interp* interp, bool dump_mode) {
|
||||||
|
std::vector<std::vector<std::string>> sdc_calls = gather_nested_calls(interp);
|
||||||
|
std::vector<SdcGraphNode> graph = build_graph(sdc_calls);
|
||||||
|
if (dump_mode)
|
||||||
|
dump_sdc_graph(graph, node_ownership(graph));
|
||||||
|
}
|
||||||
|
|
||||||
|
// patterns -> (pattern-object-bit)s
|
||||||
|
template <typename T, typename U>
|
||||||
|
std::vector<std::tuple<std::string, T, BitSelection>>
|
||||||
|
find_matching(U objects, const MatchConfig& config, const std::vector<std::string> &patterns, const char* obj_type)
|
||||||
|
{
|
||||||
|
std::vector<std::tuple<std::string, T, BitSelection>> resolved;
|
||||||
|
for (auto pat : patterns) {
|
||||||
|
bool found = false;
|
||||||
|
for (auto [name, obj] : objects) {
|
||||||
|
auto [does_match, matching_bits] = matches(name, pat, config);
|
||||||
|
if (does_match) {
|
||||||
|
found = true;
|
||||||
|
resolved.push_back(std::make_tuple(name, obj, matching_bits));
|
||||||
|
// TODO add a continue statement, conditional on config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found)
|
||||||
|
log_warning("No matches in design for %s %s\n", obj_type, pat.c_str());
|
||||||
|
}
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TclOpts {
|
||||||
|
const char* name;
|
||||||
|
std::initializer_list<const char*> legals;
|
||||||
|
TclOpts(const char* name, std::initializer_list<const char*> legals) : name(name), legals(legals) {}
|
||||||
|
bool parse_opt(Tcl_Obj* obj, const char* opt_name) {
|
||||||
|
char* arg = Tcl_GetString(obj);
|
||||||
|
std::string expected = std::string("-") + opt_name;
|
||||||
|
if (expected == arg) {
|
||||||
|
if (!std::find_if(legals.begin(), legals.end(),
|
||||||
|
[&opt_name](const char* str) { return opt_name == str; }))
|
||||||
|
log_cmd_error("Illegal argument %s for %s.\n", expected.c_str(), name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GetterOpts : TclOpts {
|
||||||
|
bool hierarchical_flag = false;
|
||||||
|
bool regexp_flag = false;
|
||||||
|
bool nocase_flag = false;
|
||||||
|
std::string separator = "/";
|
||||||
|
Tcl_Obj* of_objects = nullptr;
|
||||||
|
std::vector<std::string> patterns = {};
|
||||||
|
GetterOpts(const char* name, std::initializer_list<const char*> legals) : TclOpts(name, legals) {}
|
||||||
|
template<typename T>
|
||||||
|
bool parse_flag(Tcl_Obj* obj, const char* flag_name, T& flag_var) {
|
||||||
|
bool ret = parse_opt(obj, flag_name);
|
||||||
|
if (ret)
|
||||||
|
flag_var = true;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
void parse(int objc, Tcl_Obj* const objv[]) {
|
||||||
|
int i = 1;
|
||||||
|
for (; i < objc; i++) {
|
||||||
|
if (parse_flag(objv[i], "hierarchical", hierarchical_flag)) continue;
|
||||||
|
if (parse_flag(objv[i], "hier", hierarchical_flag)) continue;
|
||||||
|
if (parse_flag(objv[i], "regexp", regexp_flag)) continue;
|
||||||
|
if (parse_flag(objv[i], "nocase", nocase_flag)) continue;
|
||||||
|
if (parse_opt(objv[i], "hsc")) {
|
||||||
|
log_assert(i + 1 < objc);
|
||||||
|
separator = Tcl_GetString(objv[++i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (parse_opt(objv[i], "of_objects")) {
|
||||||
|
log_assert(i + 1 < objc);
|
||||||
|
of_objects = objv[++i];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (; i < objc; i++) {
|
||||||
|
patterns.push_back(Tcl_GetString(objv[i]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
void check_simple() {
|
||||||
|
if (regexp_flag || hierarchical_flag || nocase_flag || separator != "/" || of_objects) {
|
||||||
|
log_error("%s got unexpected flags in simple mode\n", name);
|
||||||
|
}
|
||||||
|
if (patterns.size() != 1)
|
||||||
|
log_error("%s got unexpected number of patterns in simple mode: %zu\n", name, patterns.size());
|
||||||
|
}
|
||||||
|
void check_simple_sep() {
|
||||||
|
if (separator != "/")
|
||||||
|
log_error("Only '/' accepted as separator");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void merge_or_init(const T& key, dict<T, BitSelection>& dst, const BitSelection& src) {
|
||||||
|
if (dst.count(key) == 0) {
|
||||||
|
dst[key] = src;
|
||||||
|
} else {
|
||||||
|
dst[key].merge(src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[])
|
||||||
|
{
|
||||||
|
auto* objects = (SdcObjects*)data;
|
||||||
|
GetterOpts opts("get_pins", {"hierarchical", "hier", "regexp", "nocase", "hsc", "of_objects"});
|
||||||
|
opts.parse(objc, objv);
|
||||||
|
if (objects->collect_mode == SdcObjects::CollectMode::SimpleGetter)
|
||||||
|
opts.check_simple();
|
||||||
|
opts.check_simple_sep();
|
||||||
|
|
||||||
|
MatchConfig config(opts.regexp_flag, opts.nocase_flag, opts.hierarchical_flag);
|
||||||
|
std::vector<std::tuple<std::string, SdcObjects::CellPin, BitSelection>> resolved;
|
||||||
|
const auto& pins = objects->design_pins;
|
||||||
|
resolved = find_matching<SdcObjects::CellPin, decltype(pins)>(pins, config, opts.patterns, "pin");
|
||||||
|
|
||||||
|
return graph_node(TclCall{interp, objc, objv});
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[])
|
||||||
|
{
|
||||||
|
auto* objects = (SdcObjects*)data;
|
||||||
|
GetterOpts opts("get_ports", {"regexp", "nocase"});
|
||||||
|
opts.parse(objc, objv);
|
||||||
|
if (objects->collect_mode == SdcObjects::CollectMode::SimpleGetter)
|
||||||
|
opts.check_simple();
|
||||||
|
|
||||||
|
MatchConfig config(opts.regexp_flag, opts.nocase_flag, false);
|
||||||
|
std::vector<std::tuple<std::string, Wire*, BitSelection>> resolved;
|
||||||
|
const auto& ports = objects->design_ports;
|
||||||
|
resolved = find_matching<Wire*, decltype(ports)>(ports, config, opts.patterns, "port");
|
||||||
|
|
||||||
|
for (auto [name, wire, matching_bits] : resolved) {
|
||||||
|
if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint)
|
||||||
|
merge_or_init(std::make_pair(name, wire), objects->constrained_ports, matching_bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
return graph_node(TclCall{interp, objc, objv});
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sdc_get_nets_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[])
|
||||||
|
{
|
||||||
|
auto* objects = (SdcObjects*)data;
|
||||||
|
GetterOpts opts("get_nets", {"hierarchical", "hier", "regexp", "nocase", "hsc", "of_objects"});
|
||||||
|
opts.parse(objc, objv);
|
||||||
|
if (objects->collect_mode == SdcObjects::CollectMode::SimpleGetter)
|
||||||
|
opts.check_simple();
|
||||||
|
|
||||||
|
MatchConfig config(opts.regexp_flag, opts.nocase_flag, false);
|
||||||
|
std::vector<std::tuple<std::string, Wire*, BitSelection>> resolved;
|
||||||
|
const auto& ports = objects->design_nets;
|
||||||
|
resolved = find_matching<Wire*, decltype(ports)>(ports, config, opts.patterns, "net");
|
||||||
|
|
||||||
|
for (auto [name, wire, matching_bits] : resolved) {
|
||||||
|
if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint)
|
||||||
|
merge_or_init(std::make_pair(name, wire), objects->constrained_nets, matching_bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
return graph_node(TclCall{interp, objc, objv});
|
||||||
|
}
|
||||||
|
|
||||||
|
class SDCInterpreter
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
Tcl_Interp* interp = nullptr;
|
||||||
|
public:
|
||||||
|
std::unique_ptr<SdcObjects> objects;
|
||||||
|
~SDCInterpreter() {
|
||||||
|
if (interp)
|
||||||
|
Tcl_DeleteInterp(interp);
|
||||||
|
}
|
||||||
|
static SDCInterpreter& get() {
|
||||||
|
static SDCInterpreter instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
Tcl_Interp* fresh_interp(Design* design) {
|
||||||
|
if (interp)
|
||||||
|
Tcl_DeleteInterp(interp);
|
||||||
|
|
||||||
|
interp = Tcl_CreateInterp();
|
||||||
|
if (Tcl_Init(interp)!=TCL_OK)
|
||||||
|
log_error("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno()));
|
||||||
|
|
||||||
|
objects = std::make_unique<SdcObjects>(design);
|
||||||
|
objects->collect_mode = SdcObjects::CollectMode::SimpleGetter;
|
||||||
|
Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, (ClientData) objects.get(), NULL);
|
||||||
|
Tcl_CreateObjCommand(interp, "get_nets", sdc_get_nets_cmd, (ClientData) objects.get(), NULL);
|
||||||
|
Tcl_CreateObjCommand(interp, "get_ports", sdc_get_ports_cmd, (ClientData) objects.get(), NULL);
|
||||||
|
return interp;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Also see TclPass
|
||||||
|
struct SdcPass : public Pass {
|
||||||
|
void help() override
|
||||||
|
{
|
||||||
|
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
||||||
|
log("\n");
|
||||||
|
log(" sdc [options] file\n");
|
||||||
|
log("\n");
|
||||||
|
log("Read the SDC file for the current design.\n");
|
||||||
|
log("\n");
|
||||||
|
log(" -dump\n");
|
||||||
|
log(" Dump the referenced design objects.\n");
|
||||||
|
log("\n");
|
||||||
|
log(" -dump-graph\n");
|
||||||
|
log(" Dump the uninterpreted call graph.\n");
|
||||||
|
log("\n");
|
||||||
|
log(" -keep_hierarchy\n");
|
||||||
|
log(" Add keep_hierarchy attributes while retaining SDC validity.\n");
|
||||||
|
log("\n");
|
||||||
|
}
|
||||||
|
SdcPass() : Pass("sdc", "sniff at some SDC") { }
|
||||||
|
void execute(std::vector<std::string> args, RTLIL::Design *design) override {
|
||||||
|
log_header(design, "Executing SDC pass.\n");
|
||||||
|
log_experimental("sdc");
|
||||||
|
size_t argidx;
|
||||||
|
bool dump_mode = false;
|
||||||
|
bool dump_graph_mode = false;
|
||||||
|
bool keep_hierarchy_mode = false;
|
||||||
|
std::vector<std::string> stubs_paths;
|
||||||
|
for (argidx = 1; argidx < args.size(); argidx++) {
|
||||||
|
if (args[argidx] == "-dump") {
|
||||||
|
dump_mode = true;
|
||||||
|
continue;
|
||||||
|
} else if (args[argidx] == "-dump-graph") {
|
||||||
|
dump_graph_mode = true;
|
||||||
|
continue;
|
||||||
|
} else if (args[argidx] == "-keep_hierarchy") {
|
||||||
|
keep_hierarchy_mode = true;
|
||||||
|
continue;
|
||||||
|
} else if (args[argidx] == "-stubs" && argidx+1 < args.size()) {
|
||||||
|
stubs_paths.push_back(args[++argidx]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (argidx >= args.size())
|
||||||
|
log_cmd_error("Missing SDC file.\n");
|
||||||
|
|
||||||
|
std::string sdc_path = args[argidx++];
|
||||||
|
if (argidx < args.size())
|
||||||
|
log_cmd_error("Unexpected extra positional argument %s after SDC file %s.\n", args[argidx], sdc_path);
|
||||||
|
SDCInterpreter& sdc = SDCInterpreter::get();
|
||||||
|
Tcl_Interp *interp = sdc.fresh_interp(design);
|
||||||
|
Tcl_Preserve(interp);
|
||||||
|
std::string stub_path = "+/sdc/graph-stubs.sdc";
|
||||||
|
rewrite_filename(stub_path);
|
||||||
|
if (Tcl_EvalFile(interp, stub_path.c_str()) != TCL_OK)
|
||||||
|
log_cmd_error("SDC interpreter returned an error in stub preamble file: %s\n", Tcl_GetStringResult(interp));
|
||||||
|
for (auto path : stubs_paths)
|
||||||
|
if (Tcl_EvalFile(interp, path.c_str()) != TCL_OK)
|
||||||
|
log_cmd_error("SDC interpreter returned an error in OpenSTA stub file %s: %s\n", path.c_str(), Tcl_GetStringResult(interp));
|
||||||
|
if (Tcl_EvalFile(interp, sdc_path.c_str()) != TCL_OK)
|
||||||
|
log_cmd_error("SDC interpreter returned an error: %s\n", Tcl_GetStringResult(interp));
|
||||||
|
if (dump_mode)
|
||||||
|
sdc.objects->dump();
|
||||||
|
if (keep_hierarchy_mode)
|
||||||
|
sdc.objects->keep_hierarchy();
|
||||||
|
inspect_globals(interp, dump_graph_mode);
|
||||||
|
Tcl_Release(interp);
|
||||||
|
}
|
||||||
|
} SdcPass;
|
||||||
|
|
||||||
|
YOSYS_NAMESPACE_END
|
||||||
|
#endif
|
70
tests/sdc/alu_sub.sdc
Normal file
70
tests/sdc/alu_sub.sdc
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
###############################################################################
|
||||||
|
# Created by write_sdc
|
||||||
|
# Fri Oct 3 11:26:00 2025
|
||||||
|
###############################################################################
|
||||||
|
current_design wrapper
|
||||||
|
###############################################################################
|
||||||
|
# Timing Constraints
|
||||||
|
###############################################################################
|
||||||
|
create_clock -name this_clk -period 1.0000 [get_ports {clk}]
|
||||||
|
create_clock -name that_clk -period 2.0000
|
||||||
|
create_clock -name another_clk -period 2.0000 \
|
||||||
|
[list [get_ports {A[0]}]\
|
||||||
|
[get_ports {A[1]}]\
|
||||||
|
[get_ports {A[2]}]\
|
||||||
|
[get_ports {A[3]}]\
|
||||||
|
[get_ports {A[4]}]\
|
||||||
|
[get_ports {A[5]}]\
|
||||||
|
[get_ports {A[6]}]\
|
||||||
|
[get_ports {A[7]}]\
|
||||||
|
[get_ports {B[0]}]\
|
||||||
|
[get_ports {B[1]}]\
|
||||||
|
[get_ports {B[2]}]\
|
||||||
|
[get_ports {B[3]}]\
|
||||||
|
[get_ports {B[4]}]\
|
||||||
|
[get_ports {B[5]}]\
|
||||||
|
[get_ports {B[6]}]\
|
||||||
|
[get_ports {B[7]}]]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[0]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[0]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[1]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[1]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[2]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[2]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[3]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[3]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[4]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[4]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[5]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[5]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[6]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[6]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {A[7]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {A[7]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[0]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[0]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[1]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[1]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[2]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[2]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[3]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[3]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[4]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[4]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[5]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[5]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[6]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[6]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -rise -min -add_delay [get_ports {B[7]}]
|
||||||
|
set_input_delay 1.0000 -clock [get_clocks {this_clk}] -fall -min -add_delay [get_ports {B[7]}]
|
||||||
|
group_path -name operation_group\
|
||||||
|
-through [list [get_nets {alu/operation[0]}]\
|
||||||
|
[get_nets {alu/operation[1]}]\
|
||||||
|
[get_nets {alu/operation[2]}]\
|
||||||
|
[get_nets {alu/operation[3]}]]
|
||||||
|
###############################################################################
|
||||||
|
# Environment
|
||||||
|
###############################################################################
|
||||||
|
###############################################################################
|
||||||
|
# Design Rules
|
||||||
|
###############################################################################
|
62
tests/sdc/alu_sub.v
Normal file
62
tests/sdc/alu_sub.v
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
module adder(
|
||||||
|
input [7:0] a, input [7:0] b, output [7:0] y
|
||||||
|
);
|
||||||
|
assign y = a + b;
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
module wrapper(
|
||||||
|
input clk,
|
||||||
|
input [7:0] A,
|
||||||
|
input [7:0] B,
|
||||||
|
input [3:0] op,
|
||||||
|
output reg [7:0] result
|
||||||
|
);
|
||||||
|
wire CF, ZF, SF;
|
||||||
|
alu alu(
|
||||||
|
.clk(clk),
|
||||||
|
.A(A),
|
||||||
|
.B(B),
|
||||||
|
.operation(op),
|
||||||
|
.result(result),
|
||||||
|
.CF(CF),
|
||||||
|
.ZF(ZF),
|
||||||
|
.SF(SF)
|
||||||
|
);
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
module alu(
|
||||||
|
input clk,
|
||||||
|
input [7:0] A,
|
||||||
|
input [7:0] B,
|
||||||
|
input [3:0] operation,
|
||||||
|
output reg [7:0] result,
|
||||||
|
output reg CF,
|
||||||
|
output reg ZF,
|
||||||
|
output reg SF
|
||||||
|
);
|
||||||
|
|
||||||
|
localparam ALU_OP_ADD /* verilator public_flat */ = 4'b0000;
|
||||||
|
localparam ALU_OP_SUB /* verilator public_flat */ = 4'b0001;
|
||||||
|
|
||||||
|
reg [8:0] tmp;
|
||||||
|
reg [7:0] added;
|
||||||
|
|
||||||
|
adder adder(.a(A), .b(B), .y(added));
|
||||||
|
|
||||||
|
always @(posedge clk)
|
||||||
|
begin
|
||||||
|
case (operation)
|
||||||
|
ALU_OP_ADD :
|
||||||
|
tmp = added;
|
||||||
|
ALU_OP_SUB :
|
||||||
|
tmp = A - B;
|
||||||
|
endcase
|
||||||
|
|
||||||
|
CF <= tmp[8];
|
||||||
|
ZF <= tmp[7:0] == 0;
|
||||||
|
SF <= tmp[7];
|
||||||
|
|
||||||
|
result <= tmp[7:0];
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
|
|
14
tests/sdc/alu_sub.ys
Normal file
14
tests/sdc/alu_sub.ys
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
read_verilog alu_sub.v
|
||||||
|
proc
|
||||||
|
hierarchy -auto-top
|
||||||
|
|
||||||
|
select -assert-mod-count 1 adder
|
||||||
|
select -assert-mod-count 1 wrapper
|
||||||
|
select -assert-mod-count 1 alu
|
||||||
|
|
||||||
|
sdc -keep_hierarchy alu_sub.sdc
|
||||||
|
flatten
|
||||||
|
|
||||||
|
select -assert-mod-count 0 adder
|
||||||
|
select -assert-mod-count 1 wrapper
|
||||||
|
select -assert-mod-count 1 alu
|
4
tests/sdc/run-test.sh
Executable file
4
tests/sdc/run-test.sh
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -eu
|
||||||
|
source ../gen-tests-makefile.sh
|
||||||
|
generate_mk --yosys-scripts --bash
|
2
tests/sdc/side-effects.sdc
Normal file
2
tests/sdc/side-effects.sdc
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
puts "This should print something:"
|
||||||
|
puts [get_ports {A[0]}]
|
4
tests/sdc/side-effects.sh
Executable file
4
tests/sdc/side-effects.sh
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
../../yosys -p 'read_verilog alu_sub.v; proc; hierarchy -auto-top; sdc side-effects.sdc' | grep 'This should print something:
|
||||||
|
YOSYS_SDC_MAGIC_NODE_0'
|
Loading…
Add table
Add a link
Reference in a new issue