Merge branch 'YosysHQ:main' into main

This commit is contained in:
Akash Levy 2025-11-20 23:05:40 -05:00 committed by GitHub
commit a705042d2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 1865 additions and 275 deletions

View File

@ -177,7 +177,7 @@ ifeq ($(OS), Haiku)
CXXFLAGS += -D_DEFAULT_SOURCE
endif
YOSYS_VER := 0.59+62
YOSYS_VER := 0.59+110
YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1)
YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2)
YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'.' -f3)
@ -955,6 +955,7 @@ MK_TEST_DIRS += tests/arch/xilinx
MK_TEST_DIRS += tests/bugpoint
MK_TEST_DIRS += tests/opt
MK_TEST_DIRS += tests/sat
MK_TEST_DIRS += tests/sdc
MK_TEST_DIRS += tests/sim
MK_TEST_DIRS += tests/svtypes
MK_TEST_DIRS += tests/techmap

View File

@ -188,7 +188,7 @@ extern char yosys_path[PATH_MAX];
#endif
#ifdef YOSYS_ENABLE_TCL
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();
};
#endif
@ -610,7 +610,7 @@ int main(int argc, char **argv)
if (run_tcl_shell) {
#ifdef YOSYS_ENABLE_TCL
yosys_tcl_activate_repl();
Tcl_Main(argc, argv, yosys_tcl_iterp_init);
Tcl_Main(argc, argv, yosys_tcl_interp_init);
#else
log_error("Can't exectue TCL shell: this version of yosys is not built with TCL support enabled.\n");
#endif

View File

@ -21,245 +21,316 @@
USING_YOSYS_NAMESPACE
FfData::FfData(FfInitVals *initvals, Cell *cell_) : FfData(cell_->module, initvals, cell_->name)
{
cell = cell_;
sig_q = cell->getPort(ID::Q);
width = GetSize(sig_q);
attributes = cell->attributes;
// sorry
template<typename InputType, typename OutputType, typename = std::enable_if_t<std::is_base_of_v<FfTypeData, OutputType>>>
void manufacture_info(InputType flop, OutputType& info, FfInitVals *initvals) {
Cell* cell = nullptr;
IdString type;
constexpr bool have_cell = std::is_same_v<InputType, Cell*>;
if constexpr (std::is_same_v<InputType, IdString>) {
type = flop;
} else {
static_assert(std::is_same_v<InputType, Cell*>);
cell = flop;
type = flop->type;
}
if constexpr (have_cell) {
info.sig_q = cell->getPort(ID::Q);
info.width = GetSize(info.sig_q);
info.attributes = cell->attributes;
if (initvals)
info.val_init = (*initvals)(info.sig_q);
}
if (initvals)
val_init = (*initvals)(sig_q);
std::string type_str = cell->type.str();
std::string type_str = type.str();
if (cell->type.in(ID($anyinit), ID($ff), ID($dff), ID($dffe), ID($dffsr), ID($dffsre), ID($adff), ID($adffe), ID($aldff), ID($aldffe), ID($sdff), ID($sdffe), ID($sdffce), ID($dlatch), ID($adlatch), ID($dlatchsr), ID($sr))) {
if (cell->type.in(ID($anyinit), ID($ff))) {
has_gclk = true;
sig_d = cell->getPort(ID::D);
if (cell->type == ID($anyinit)) {
is_anyinit = true;
log_assert(val_init.is_fully_undef());
if (type.in(ID($anyinit), ID($ff), ID($dff), ID($dffe), ID($dffsr), ID($dffsre), ID($adff), ID($adffe), ID($aldff), ID($aldffe), ID($sdff), ID($sdffe), ID($sdffce), ID($dlatch), ID($adlatch), ID($dlatchsr), ID($sr))) {
if (type.in(ID($anyinit), ID($ff))) {
info.has_gclk = true;
if constexpr (have_cell)
info.sig_d = cell->getPort(ID::D);
if (type == ID($anyinit)) {
info.is_anyinit = true;
if constexpr (have_cell)
log_assert(info.val_init.is_fully_undef());
}
} else if (cell->type == ID($sr)) {
} else if (type == ID($sr)) {
// No data input at all.
} else if (cell->type.in(ID($dlatch), ID($adlatch), ID($dlatchsr))) {
has_aload = true;
sig_aload = cell->getPort(ID::EN);
pol_aload = cell->getParam(ID::EN_POLARITY).as_bool();
sig_ad = cell->getPort(ID::D);
} else if (type.in(ID($dlatch), ID($adlatch), ID($dlatchsr))) {
info.has_aload = true;
if constexpr (have_cell) {
info.sig_aload = cell->getPort(ID::EN);
info.pol_aload = cell->getParam(ID::EN_POLARITY).as_bool();
info.sig_ad = cell->getPort(ID::D);
}
} else {
has_clk = true;
sig_clk = cell->getPort(ID::CLK);
pol_clk = cell->getParam(ID::CLK_POLARITY).as_bool();
sig_d = cell->getPort(ID::D);
info.has_clk = true;
if constexpr (have_cell) {
info.sig_clk = cell->getPort(ID::CLK);
info.pol_clk = cell->getParam(ID::CLK_POLARITY).as_bool();
info.sig_d = cell->getPort(ID::D);
}
}
if (cell->type.in(ID($dffe), ID($dffsre), ID($adffe), ID($aldffe), ID($sdffe), ID($sdffce))) {
has_ce = true;
sig_ce = cell->getPort(ID::EN);
pol_ce = cell->getParam(ID::EN_POLARITY).as_bool();
if (type.in(ID($dffe), ID($dffsre), ID($adffe), ID($aldffe), ID($sdffe), ID($sdffce))) {
info.has_ce = true;
if constexpr (have_cell) {
info.sig_ce = cell->getPort(ID::EN);
info.pol_ce = cell->getParam(ID::EN_POLARITY).as_bool();
}
}
if (cell->type.in(ID($dffsr), ID($dffsre), ID($dlatchsr), ID($sr))) {
has_sr = true;
sig_clr = cell->getPort(ID::CLR);
sig_set = cell->getPort(ID::SET);
pol_clr = cell->getParam(ID::CLR_POLARITY).as_bool();
pol_set = cell->getParam(ID::SET_POLARITY).as_bool();
if (type.in(ID($dffsr), ID($dffsre), ID($dlatchsr), ID($sr))) {
info.has_sr = true;
if constexpr (have_cell) {
info.sig_clr = cell->getPort(ID::CLR);
info.sig_set = cell->getPort(ID::SET);
info.pol_clr = cell->getParam(ID::CLR_POLARITY).as_bool();
info.pol_set = cell->getParam(ID::SET_POLARITY).as_bool();
}
}
if (cell->type.in(ID($aldff), ID($aldffe))) {
has_aload = true;
sig_aload = cell->getPort(ID::ALOAD);
pol_aload = cell->getParam(ID::ALOAD_POLARITY).as_bool();
sig_ad = cell->getPort(ID::AD);
if (type.in(ID($aldff), ID($aldffe))) {
info.has_aload = true;
if constexpr (have_cell) {
info.sig_aload = cell->getPort(ID::ALOAD);
info.pol_aload = cell->getParam(ID::ALOAD_POLARITY).as_bool();
info.sig_ad = cell->getPort(ID::AD);
}
}
if (cell->type.in(ID($adff), ID($adffe), ID($adlatch))) {
has_arst = true;
sig_arst = cell->getPort(ID::ARST);
pol_arst = cell->getParam(ID::ARST_POLARITY).as_bool();
val_arst = cell->getParam(ID::ARST_VALUE);
if (type.in(ID($adff), ID($adffe), ID($adlatch))) {
info.has_arst = true;
if constexpr (have_cell) {
info.sig_arst = cell->getPort(ID::ARST);
info.pol_arst = cell->getParam(ID::ARST_POLARITY).as_bool();
info.val_arst = cell->getParam(ID::ARST_VALUE);
}
}
if (cell->type.in(ID($sdff), ID($sdffe), ID($sdffce))) {
has_srst = true;
sig_srst = cell->getPort(ID::SRST);
pol_srst = cell->getParam(ID::SRST_POLARITY).as_bool();
val_srst = cell->getParam(ID::SRST_VALUE);
ce_over_srst = cell->type == ID($sdffce);
if (type.in(ID($sdff), ID($sdffe), ID($sdffce))) {
info.has_srst = true;
if constexpr (have_cell) {
info.sig_srst = cell->getPort(ID::SRST);
info.pol_srst = cell->getParam(ID::SRST_POLARITY).as_bool();
info.val_srst = cell->getParam(ID::SRST_VALUE);
}
info.ce_over_srst = type == ID($sdffce);
}
} else if (cell->type == ID($_FF_)) {
is_fine = true;
has_gclk = true;
sig_d = cell->getPort(ID::D);
} else if (type == ID($_FF_)) {
info.is_fine = true;
info.has_gclk = true;
if constexpr (have_cell)
info.sig_d = cell->getPort(ID::D);
} else if (type_str.substr(0, 5) == "$_SR_") {
is_fine = true;
has_sr = true;
pol_set = type_str[5] == 'P';
pol_clr = type_str[6] == 'P';
sig_set = cell->getPort(ID::S);
sig_clr = cell->getPort(ID::R);
info.is_fine = true;
info.has_sr = true;
info.pol_set = type_str[5] == 'P';
info.pol_clr = type_str[6] == 'P';
if constexpr (have_cell) {
info.sig_set = cell->getPort(ID::S);
info.sig_clr = cell->getPort(ID::R);
}
} else if (type_str.substr(0, 6) == "$_DFF_" && type_str.size() == 8) {
is_fine = true;
sig_d = cell->getPort(ID::D);
has_clk = true;
pol_clk = type_str[6] == 'P';
sig_clk = cell->getPort(ID::C);
info.is_fine = true;
info.has_clk = true;
info.pol_clk = type_str[6] == 'P';
if constexpr (have_cell) {
info.sig_d = cell->getPort(ID::D);
info.sig_clk = cell->getPort(ID::C);
}
} else if (type_str.substr(0, 7) == "$_DFFE_" && type_str.size() == 10) {
is_fine = true;
sig_d = cell->getPort(ID::D);
has_clk = true;
pol_clk = type_str[7] == 'P';
sig_clk = cell->getPort(ID::C);
has_ce = true;
pol_ce = type_str[8] == 'P';
sig_ce = cell->getPort(ID::E);
info.is_fine = true;
info.has_clk = true;
info.pol_clk = type_str[7] == 'P';
info.has_ce = true;
info.pol_ce = type_str[8] == 'P';
if constexpr (have_cell) {
info.sig_d = cell->getPort(ID::D);
info.sig_clk = cell->getPort(ID::C);
info.sig_ce = cell->getPort(ID::E);
}
} else if (type_str.substr(0, 6) == "$_DFF_" && type_str.size() == 10) {
is_fine = true;
sig_d = cell->getPort(ID::D);
has_clk = true;
pol_clk = type_str[6] == 'P';
sig_clk = cell->getPort(ID::C);
has_arst = true;
pol_arst = type_str[7] == 'P';
sig_arst = cell->getPort(ID::R);
val_arst = type_str[8] == '1' ? State::S1 : State::S0;
info.is_fine = true;
info.has_clk = true;
info.pol_clk = type_str[6] == 'P';
info.has_arst = true;
info.pol_arst = type_str[7] == 'P';
info.val_arst = type_str[8] == '1' ? State::S1 : State::S0;
if constexpr (have_cell) {
info.sig_d = cell->getPort(ID::D);
info.sig_clk = cell->getPort(ID::C);
info.sig_arst = cell->getPort(ID::R);
}
} else if (type_str.substr(0, 7) == "$_DFFE_" && type_str.size() == 12) {
is_fine = true;
sig_d = cell->getPort(ID::D);
has_clk = true;
pol_clk = type_str[7] == 'P';
sig_clk = cell->getPort(ID::C);
has_arst = true;
pol_arst = type_str[8] == 'P';
sig_arst = cell->getPort(ID::R);
val_arst = type_str[9] == '1' ? State::S1 : State::S0;
has_ce = true;
pol_ce = type_str[10] == 'P';
sig_ce = cell->getPort(ID::E);
info.is_fine = true;
info.has_clk = true;
info.pol_clk = type_str[7] == 'P';
info.has_arst = true;
info.pol_arst = type_str[8] == 'P';
info.val_arst = type_str[9] == '1' ? State::S1 : State::S0;
info.has_ce = true;
info.pol_ce = type_str[10] == 'P';
if constexpr (have_cell) {
info.sig_d = cell->getPort(ID::D);
info.sig_clk = cell->getPort(ID::C);
info.sig_arst = cell->getPort(ID::R);
info.sig_ce = cell->getPort(ID::E);
}
} else if (type_str.substr(0, 8) == "$_ALDFF_" && type_str.size() == 11) {
is_fine = true;
sig_d = cell->getPort(ID::D);
has_clk = true;
pol_clk = type_str[8] == 'P';
sig_clk = cell->getPort(ID::C);
has_aload = true;
pol_aload = type_str[9] == 'P';
sig_aload = cell->getPort(ID::L);
sig_ad = cell->getPort(ID::AD);
info.is_fine = true;
info.has_clk = true;
info.pol_clk = type_str[8] == 'P';
info.has_aload = true;
info.pol_aload = type_str[9] == 'P';
if constexpr (have_cell) {
info.sig_d = cell->getPort(ID::D);
info.sig_clk = cell->getPort(ID::C);
info.sig_aload = cell->getPort(ID::L);
info.sig_ad = cell->getPort(ID::AD);
}
} else if (type_str.substr(0, 9) == "$_ALDFFE_" && type_str.size() == 13) {
is_fine = true;
sig_d = cell->getPort(ID::D);
has_clk = true;
pol_clk = type_str[9] == 'P';
sig_clk = cell->getPort(ID::C);
has_aload = true;
pol_aload = type_str[10] == 'P';
sig_aload = cell->getPort(ID::L);
sig_ad = cell->getPort(ID::AD);
has_ce = true;
pol_ce = type_str[11] == 'P';
sig_ce = cell->getPort(ID::E);
info.is_fine = true;
info.has_clk = true;
info.pol_clk = type_str[9] == 'P';
info.has_aload = true;
info.pol_aload = type_str[10] == 'P';
info.has_ce = true;
info.pol_ce = type_str[11] == 'P';
if constexpr (have_cell) {
info.sig_d = cell->getPort(ID::D);
info.sig_clk = cell->getPort(ID::C);
info.sig_aload = cell->getPort(ID::L);
info.sig_ad = cell->getPort(ID::AD);
info.sig_ce = cell->getPort(ID::E);
}
} else if (type_str.substr(0, 8) == "$_DFFSR_" && type_str.size() == 12) {
is_fine = true;
sig_d = cell->getPort(ID::D);
has_clk = true;
pol_clk = type_str[8] == 'P';
sig_clk = cell->getPort(ID::C);
has_sr = true;
pol_set = type_str[9] == 'P';
pol_clr = type_str[10] == 'P';
sig_set = cell->getPort(ID::S);
sig_clr = cell->getPort(ID::R);
info.is_fine = true;
info.has_clk = true;
info.pol_clk = type_str[8] == 'P';
info.has_sr = true;
info.pol_set = type_str[9] == 'P';
info.pol_clr = type_str[10] == 'P';
if constexpr (have_cell) {
info.sig_d = cell->getPort(ID::D);
info.sig_clk = cell->getPort(ID::C);
info.sig_set = cell->getPort(ID::S);
info.sig_clr = cell->getPort(ID::R);
}
} else if (type_str.substr(0, 9) == "$_DFFSRE_" && type_str.size() == 14) {
is_fine = true;
sig_d = cell->getPort(ID::D);
has_clk = true;
pol_clk = type_str[9] == 'P';
sig_clk = cell->getPort(ID::C);
has_sr = true;
pol_set = type_str[10] == 'P';
pol_clr = type_str[11] == 'P';
sig_set = cell->getPort(ID::S);
sig_clr = cell->getPort(ID::R);
has_ce = true;
pol_ce = type_str[12] == 'P';
sig_ce = cell->getPort(ID::E);
info.is_fine = true;
info.has_clk = true;
info.pol_clk = type_str[9] == 'P';
info.has_sr = true;
info.pol_set = type_str[10] == 'P';
info.pol_clr = type_str[11] == 'P';
info.has_ce = true;
info.pol_ce = type_str[12] == 'P';
if constexpr (have_cell) {
info.sig_d = cell->getPort(ID::D);
info.sig_clk = cell->getPort(ID::C);
info.sig_set = cell->getPort(ID::S);
info.sig_clr = cell->getPort(ID::R);
info.sig_ce = cell->getPort(ID::E);
}
} else if (type_str.substr(0, 7) == "$_SDFF_" && type_str.size() == 11) {
is_fine = true;
sig_d = cell->getPort(ID::D);
has_clk = true;
pol_clk = type_str[7] == 'P';
sig_clk = cell->getPort(ID::C);
has_srst = true;
pol_srst = type_str[8] == 'P';
sig_srst = cell->getPort(ID::R);
val_srst = type_str[9] == '1' ? State::S1 : State::S0;
info.is_fine = true;
info.has_clk = true;
info.pol_clk = type_str[7] == 'P';
info.has_srst = true;
info.pol_srst = type_str[8] == 'P';
info.val_srst = type_str[9] == '1' ? State::S1 : State::S0;
if constexpr (have_cell) {
info.sig_d = cell->getPort(ID::D);
info.sig_clk = cell->getPort(ID::C);
info.sig_srst = cell->getPort(ID::R);
}
} else if (type_str.substr(0, 8) == "$_SDFFE_" && type_str.size() == 13) {
is_fine = true;
sig_d = cell->getPort(ID::D);
has_clk = true;
pol_clk = type_str[8] == 'P';
sig_clk = cell->getPort(ID::C);
has_srst = true;
pol_srst = type_str[9] == 'P';
sig_srst = cell->getPort(ID::R);
val_srst = type_str[10] == '1' ? State::S1 : State::S0;
has_ce = true;
pol_ce = type_str[11] == 'P';
sig_ce = cell->getPort(ID::E);
info.is_fine = true;
info.has_clk = true;
info.pol_clk = type_str[8] == 'P';
info.has_srst = true;
info.pol_srst = type_str[9] == 'P';
info.val_srst = type_str[10] == '1' ? State::S1 : State::S0;
info.has_ce = true;
info.pol_ce = type_str[11] == 'P';
if constexpr (have_cell) {
info.sig_d = cell->getPort(ID::D);
info.sig_clk = cell->getPort(ID::C);
info.sig_srst = cell->getPort(ID::R);
info.sig_ce = cell->getPort(ID::E);
}
} else if (type_str.substr(0, 9) == "$_SDFFCE_" && type_str.size() == 14) {
is_fine = true;
sig_d = cell->getPort(ID::D);
has_clk = true;
pol_clk = type_str[9] == 'P';
sig_clk = cell->getPort(ID::C);
has_srst = true;
pol_srst = type_str[10] == 'P';
sig_srst = cell->getPort(ID::R);
val_srst = type_str[11] == '1' ? State::S1 : State::S0;
has_ce = true;
pol_ce = type_str[12] == 'P';
sig_ce = cell->getPort(ID::E);
ce_over_srst = true;
info.is_fine = true;
info.has_clk = true;
info.pol_clk = type_str[9] == 'P';
info.has_srst = true;
info.pol_srst = type_str[10] == 'P';
info.val_srst = type_str[11] == '1' ? State::S1 : State::S0;
info.has_ce = true;
info.pol_ce = type_str[12] == 'P';
info.ce_over_srst = true;
if constexpr (have_cell) {
info.sig_d = cell->getPort(ID::D);
info.sig_clk = cell->getPort(ID::C);
info.sig_srst = cell->getPort(ID::R);
info.sig_ce = cell->getPort(ID::E);
}
} else if (type_str.substr(0, 9) == "$_DLATCH_" && type_str.size() == 11) {
is_fine = true;
has_aload = true;
sig_ad = cell->getPort(ID::D);
has_aload = true;
pol_aload = type_str[9] == 'P';
sig_aload = cell->getPort(ID::E);
info.is_fine = true;
info.has_aload = true;
info.has_aload = true;
info.pol_aload = type_str[9] == 'P';
if constexpr (have_cell) {
info.sig_ad = cell->getPort(ID::D);
info.sig_aload = cell->getPort(ID::E);
}
} else if (type_str.substr(0, 9) == "$_DLATCH_" && type_str.size() == 13) {
is_fine = true;
has_aload = true;
sig_ad = cell->getPort(ID::D);
has_aload = true;
pol_aload = type_str[9] == 'P';
sig_aload = cell->getPort(ID::E);
has_arst = true;
pol_arst = type_str[10] == 'P';
sig_arst = cell->getPort(ID::R);
val_arst = type_str[11] == '1' ? State::S1 : State::S0;
info.is_fine = true;
info.has_aload = true;
info.has_aload = true;
info.pol_aload = type_str[9] == 'P';
info.has_arst = true;
info.pol_arst = type_str[10] == 'P';
info.val_arst = type_str[11] == '1' ? State::S1 : State::S0;
if constexpr (have_cell) {
info.sig_ad = cell->getPort(ID::D);
info.sig_aload = cell->getPort(ID::E);
info.sig_arst = cell->getPort(ID::R);
}
} else if (type_str.substr(0, 11) == "$_DLATCHSR_" && type_str.size() == 15) {
is_fine = true;
has_aload = true;
sig_ad = cell->getPort(ID::D);
has_aload = true;
pol_aload = type_str[11] == 'P';
sig_aload = cell->getPort(ID::E);
has_sr = true;
pol_set = type_str[12] == 'P';
pol_clr = type_str[13] == 'P';
sig_set = cell->getPort(ID::S);
sig_clr = cell->getPort(ID::R);
info.is_fine = true;
info.has_aload = true;
info.has_aload = true;
info.pol_aload = type_str[11] == 'P';
info.has_sr = true;
info.pol_set = type_str[12] == 'P';
info.pol_clr = type_str[13] == 'P';
if constexpr (have_cell) {
info.sig_ad = cell->getPort(ID::D);
info.sig_aload = cell->getPort(ID::E);
info.sig_set = cell->getPort(ID::S);
info.sig_clr = cell->getPort(ID::R);
}
} else {
log_assert(0);
}
if (has_aload && !has_clk && !has_sr && !has_arst && sig_ad.is_fully_const()) {
// Plain D latches with const D treated specially.
has_aload = false;
has_arst = true;
sig_arst = sig_aload;
pol_arst = pol_aload;
val_arst = sig_ad.as_const();
}
if constexpr (have_cell)
if (info.has_aload && !info.has_clk && !info.has_sr && !info.has_arst && info.sig_ad.is_fully_const()) {
// Plain D latches with const D treated specially.
info.has_aload = false;
info.has_arst = true;
info.sig_arst = info.sig_aload;
info.pol_arst = info.pol_aload;
info.val_arst = info.sig_ad.as_const();
}
}
FfTypeData::FfTypeData(IdString type) : FfTypeData()
{
manufacture_info(type, *this, nullptr);
}
FfData::FfData(FfInitVals *initvals, Cell *cell_) : FfData(cell_->module, initvals, cell_->name)
{
cell = cell_;
manufacture_info(cell, *this, initvals);
}
FfData FfData::slice(const std::vector<int> &bits) {

View File

@ -78,31 +78,20 @@ YOSYS_NAMESPACE_BEGIN
// - has_arst [does not correspond to a native cell, represented as dlatch with const D input]
// - empty set [not a cell — will be emitted as a simple direct connection]
struct FfData {
Module *module;
FfInitVals *initvals;
Cell *cell;
IdString name;
// The FF output.
SigSpec sig_q;
// The sync data input, present if has_clk or has_gclk.
SigSpec sig_d;
// The async data input, present if has_aload.
SigSpec sig_ad;
// The sync clock, present if has_clk.
SigSpec sig_clk;
// The clock enable, present if has_ce.
SigSpec sig_ce;
// The async load enable, present if has_aload.
SigSpec sig_aload;
// The async reset, preset if has_arst.
SigSpec sig_arst;
// The sync reset, preset if has_srst.
SigSpec sig_srst;
// The async clear (per-lane), present if has_sr.
SigSpec sig_clr;
// The async set (per-lane), present if has_sr.
SigSpec sig_set;
struct FfTypeData {
FfTypeData(IdString type);
FfTypeData() {
has_clk = false;
has_gclk = false;
has_ce = false;
has_aload = false;
has_srst = false;
has_arst = false;
has_sr = false;
ce_over_srst = false;
is_fine = false;
is_anyinit = false;
}
// True if this is a clocked (edge-sensitive) flip-flop.
bool has_clk;
// True if this is a $ff, exclusive with every other has_*.
@ -143,9 +132,38 @@ struct FfData {
bool pol_clr;
bool pol_set;
// The value loaded by sig_arst.
// Zero length if unknowable from just type
Const val_arst;
// The value loaded by sig_srst.
// Zero length if unknowable from just type
Const val_srst;
};
struct FfData : FfTypeData {
Module *module;
FfInitVals *initvals;
Cell *cell;
IdString name;
// The FF output.
SigSpec sig_q;
// The sync data input, present if has_clk or has_gclk.
SigSpec sig_d;
// The async data input, present if has_aload.
SigSpec sig_ad;
// The sync clock, present if has_clk.
SigSpec sig_clk;
// The clock enable, present if has_ce.
SigSpec sig_ce;
// The async load enable, present if has_aload.
SigSpec sig_aload;
// The async reset, preset if has_arst.
SigSpec sig_arst;
// The sync reset, preset if has_srst.
SigSpec sig_srst;
// The async clear (per-lane), present if has_sr.
SigSpec sig_clr;
// The async set (per-lane), present if has_sr.
SigSpec sig_set;
// The initial value at power-up.
Const val_init;
// The FF data width in bits.
@ -154,16 +172,6 @@ struct FfData {
FfData(Module *module = nullptr, FfInitVals *initvals = nullptr, IdString name = IdString()) : module(module), initvals(initvals), cell(nullptr), name(name) {
width = 0;
has_clk = false;
has_gclk = false;
has_ce = false;
has_aload = false;
has_srst = false;
has_arst = false;
has_sr = false;
ce_over_srst = false;
is_fine = false;
is_anyinit = false;
pol_clk = false;
pol_aload = false;
pol_ce = false;

View File

@ -728,7 +728,6 @@ template <> struct IDMacroHelper<-1> {
namespace RTLIL {
extern dict<std::string, std::string> constpad;
[[deprecated("Call cell->is_builtin_ff() instead")]]
const pool<IdString> &builtin_ff_cell_types();
static inline std::string escape_id(const std::string &str) {

View File

@ -533,7 +533,7 @@ static int tcl_set_param(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj *cons
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)
log_warning("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno()));

View File

@ -97,6 +97,7 @@ CellTypes yosys_celltypes;
#ifdef YOSYS_ENABLE_TCL
Tcl_Interp *yosys_tcl_interp = NULL;
Tcl_Interp *yosys_sdc_interp = NULL;
#endif
std::set<std::string> yosys_input_files, yosys_output_files;
@ -396,17 +397,18 @@ void rewrite_filename(std::string &filename)
#ifdef YOSYS_ENABLE_TCL
// 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()
{
if (yosys_tcl_interp == NULL) {
yosys_tcl_interp = Tcl_CreateInterp();
yosys_tcl_iterp_init(yosys_tcl_interp);
yosys_tcl_interp_init(yosys_tcl_interp);
}
return yosys_tcl_interp;
}
// Also see SdcPass
struct TclPass : public Pass {
TclPass() : Pass("tcl", "execute a TCL script file") { }
void help() override {
@ -449,6 +451,7 @@ struct TclPass : public Pass {
Tcl_Release(interp);
}
} TclPass;
#endif
#if defined(__linux__) || defined(__CYGWIN__)

View File

@ -58,3 +58,6 @@ OBJS += passes/cmds/test_select.o
OBJS += passes/cmds/timeest.o
OBJS += passes/cmds/linecoverage.o
OBJS += passes/cmds/sort.o
OBJS += passes/cmds/icell_liberty.o
include $(YOSYS_SRC)/passes/cmds/sdc/Makefile.inc

View File

@ -0,0 +1,207 @@
#include "kernel/yosys.h"
#include "kernel/celltypes.h"
#include "kernel/ff.h"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
struct LibertyStubber {
CellTypes ct;
LibertyStubber() {
ct.setup();
ct.setup_internals_ff();
}
void liberty_prefix(std::ostream& f)
{
f << "/*\n";
f << stringf("\tModels interfaces of select Yosys internal cell.\n");
f << stringf("\tLikely contains INCORRECT POLARITIES.\n");
f << stringf("\tImpractical for any simulation, synthesis, or timing.\n");
f << stringf("\tIntended purely for SDC expansion.\n");
f << stringf("\tDo not microwave or tumble dry.\n");
f << stringf("\tGenerated by %s\n", yosys_maybe_version());
f << "*/\n";
f << "library (yosys) {\n";
f << "\tinput_threshold_pct_fall : 50;\n";
f << "\tinput_threshold_pct_rise : 50;\n";
f << "\toutput_threshold_pct_fall : 50;\n";
f << "\toutput_threshold_pct_rise : 50;\n";
f << "\tslew_lower_threshold_pct_fall : 1;\n";
f << "\tslew_lower_threshold_pct_rise : 1;\n";
f << "\tslew_upper_threshold_pct_fall : 99;\n";
f << "\tslew_upper_threshold_pct_rise : 99;\n";
}
void liberty_suffix(std::ostream& f)
{
f << "}\n";
}
struct LibertyItemizer {
std::ostream& f;
int indent;
LibertyItemizer(std::ostream& f) : f(f), indent(0) {};
void item(std::string key, std::string val)
{
f << std::string(indent, '\t') << key << " : \"" << val << "\";\n";
}
};
void liberty_flop(Module* base, Module* derived, std::ostream& f)
{
auto base_name = base->name.str().substr(1);
auto derived_name = derived->name.str().substr(1);
FfTypeData ffType(base_name);
LibertyItemizer i(f);
if (ffType.has_gclk) {
log_warning("Formal flip flop %s can't be modeled\n", base_name.c_str());
return;
}
if (ffType.has_ce) {
log_warning("DFFE %s can't be modeled\n", base_name.c_str());
return;
}
f << "\tcell (\"" << derived_name << "\") {\n";
auto& base_type = ct.cell_types[base_name];
i.indent = 3;
auto sorted_ports = derived->ports;
// Hack for CLK and C coming before Q does
auto cmp = [](IdString l, IdString r) { return l.str() < r.str(); };
std::sort(sorted_ports.begin(), sorted_ports.end(), cmp);
std::string clock_pin_name = "";
for (auto x : sorted_ports) {
std::string port_name = RTLIL::unescape_id(x);
bool is_input = base_type.inputs.count(x);
bool is_output = base_type.outputs.count(x);
f << "\t\tpin (" << RTLIL::unescape_id(x.str()) << ") {\n";
if (is_input && !is_output) {
i.item("direction", "input");
} else if (!is_input && is_output) {
i.item("direction", "output");
} else {
i.item("direction", "inout");
}
if (port_name == "CLK" || port_name == "C") {
i.item("clock", "true");
clock_pin_name = port_name;
}
if (port_name == "Q") {
i.item("function", "IQ");
f << "\t\t\ttiming () {\n";
i.indent++;
log_assert(clock_pin_name.size());
i.item("related_pin", clock_pin_name);
i.indent--;
f << "\t\t\t}\n";
}
f << "\t\t}\n";
}
f << "\t\tff (\"IQ\",\"IQ_N\") {\n";
i.indent = 3;
// TODO polarities?
if (ffType.has_clk) {
auto pin = ffType.is_fine ? "C" : "CLK";
i.item("clocked_on", pin);
}
if (ffType.has_arst) {
auto meaning = (ffType.val_arst == State::S1) ? "preset" : "clear";
auto pin = ffType.is_fine ? "R" : "ARST";
i.item(meaning, pin);
}
auto next_state = ffType.has_ce ? "D & EN" : "D";
i.item("next_state", next_state);
f << "\t\t}\n";
f << "\t}\n";
}
void liberty_cell(Module* base, Module* derived, std::ostream& f)
{
auto base_name = base->name.str().substr(1);
auto derived_name = derived->name.str().substr(1);
if (!ct.cell_types.count(base_name)) {
log_debug("skip skeleton for %s\n", base_name.c_str());
return;
}
if (RTLIL::builtin_ff_cell_types().count(base_name))
return liberty_flop(base, derived, f);
auto& base_type = ct.cell_types[base_name];
f << "\tcell (\"" << derived_name << "\") {\n";
for (auto x : derived->ports) {
bool is_input = base_type.inputs.count(x);
bool is_output = base_type.outputs.count(x);
f << "\t\tpin (" << RTLIL::unescape_id(x.str()) << ") {\n";
if (is_input && !is_output) {
f << "\t\t\tdirection : input;\n";
} else if (!is_input && is_output) {
f << "\t\t\tdirection : output;\n";
} else {
f << "\t\t\tdirection : inout;\n";
}
f << "\t\t}\n";
}
f << "\t}\n";
}
};
struct IcellLiberty : Pass {
IcellLiberty() : Pass("icell_liberty", "write Liberty interfaces for used internal cells") {}
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" icell_liberty <liberty_file>\n");
log("\n");
log("Write Liberty files modeling the interfaces of used internal cells.\n");
log("\n");
log("Models are not guaranteed to be logically sound.\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *d) override
{
log_header(d, "Executing ICELL_LIBERTY pass.\n");
size_t argidx;
IdString naming_attr;
std::string liberty_filename;
auto liberty_file = std::make_unique<std::ofstream>();
for (argidx = 1; argidx < args.size(); argidx++) {
break;
}
if (argidx < args.size())
liberty_filename = args[argidx++];
else
log_cmd_error("no Liberty filename specified\n");
if (liberty_filename.size()) {
liberty_file->open(liberty_filename.c_str());
if (liberty_file->fail()) {
log_cmd_error("Can't open file `%s' for writing: %s\n", liberty_filename.c_str(), strerror(errno));
}
}
pool<RTLIL::IdString> done;
LibertyStubber stubber = {};
stubber.liberty_prefix(*liberty_file);
for (auto module : d->selected_modules()) {
for (auto cell : module->selected_cells()) {
Module *inst_module = d->module(cell->type);
if (!inst_module || !inst_module->get_blackbox_attribute())
continue;
Module *base = inst_module;
if (!done.count(base->name)) {
stubber.liberty_cell(base, base, *liberty_file);
done.insert(base->name);
}
}
}
stubber.liberty_suffix(*liberty_file);
}
} IcellLiberty;
PRIVATE_NAMESPACE_END

View File

@ -0,0 +1,3 @@
OBJS += passes/cmds/sdc/sdc.o
$(eval $(call add_share_file,share/sdc,passes/cmds/sdc/graph-stubs.sdc))

View File

@ -0,0 +1,42 @@
proc unknown {args} {
# Check if it's a getter
if {[llength $args] > 0} {
set first_arg [lindex $args 0]
if {[string match "get_*" $first_arg]} {
# It's a getter, has it been redirected from specialized C++ code?
if {[llength $args] > 1} {
set second_arg [lindex $args 1]
if {$second_arg ne "-getter-validated"} {
error "Unknown getter: $first_arg"
}
} else {
error "Unknown getter: $first_arg"
}
}
}
# TODO this safety feature could be optional via a global
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]
}
proc get_clocks {args} {
# get_clocks isn't a design object getter
# because clocks aren't design objects, just aliases
# so the referred to clock pin already are being tracked
# as arguments of uninterpreted create_clock command or similar
return [unknown "get_clocks" "-getter-validated" {*}$args]
}

785
passes/cmds/sdc/sdc.cc Normal file
View File

@ -0,0 +1,785 @@
#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 getter_graph_node(TclCall call) {
// Insert -getter-validated as first argument for passing to unknown
// to distinguish resolved and unknown getters.
// For example, if call is ["get_foo", "-bar"]
// then newCall is ["get_foo", "-getter-validated", "-bar"]
Tcl_Obj* validity_tag = Tcl_NewStringObj("-getter-validated", -1);
Tcl_IncrRefCount(validity_tag);
std::vector<Tcl_Obj*> newObjv(call.objc + 1);
log_assert(call.objc > 0);
newObjv[0] = call.objv[0];
newObjv[1] = validity_tag;
for (int i = 1; i < call.objc; ++i) {
newObjv[i + 1] = call.objv[i];
}
// Send the vector to the Tcl land
Tcl_Obj** allocatedObjv = (Tcl_Obj**)Tcl_Alloc((call.objc + 1) * sizeof(Tcl_Obj*));
for (int i = 0; i < call.objc + 1; ++i) {
allocatedObjv[i] = newObjv[i];
}
TclCall newCall {
.interp = call.interp,
.objc = call.objc + 1,
.objv = allocatedObjv
};
// Finally, redirect to unknown handler
return redirect_unknown(newCall);
}
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 = (Tcl_Obj**)Tcl_Alloc(newObjc * sizeof(Tcl_Obj*));
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);
Tcl_Free((char*) 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 getter_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 getter_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 getter_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", "Read an SDC file") { }
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

View File

@ -193,7 +193,7 @@ pyosys_headers = [
),
PyosysClass("SigChunk"),
PyosysClass("SigBit", hash_expr="s"),
PyosysClass("SigSpec", hash_expr="s", denylist={"chunks"}),
PyosysClass("SigSpec", hash_expr="s", denylist=frozenset({"chunks"})),
PyosysClass(
"Cell",
ref_only=True,
@ -539,6 +539,8 @@ class PyosysWrapperGenerator(object):
python_name_override = "__ne__"
elif function.operator == "<":
python_name_override = "__lt__"
elif function.operator == "[]" and function.const:
python_name_override = "__getitem__"
else:
return
@ -599,7 +601,10 @@ class PyosysWrapperGenerator(object):
# care
return
has_containers = self.register_containers(field)
self.register_containers(field)
rvp = "py::return_value_policy::copy"
if isinstance(field.type, Pointer):
rvp = "py::return_value_policy::reference_internal"
definition_fn = f"def_{'readonly' if field.type.const else 'readwrite'}"
if field.static:
@ -611,7 +616,7 @@ class PyosysWrapperGenerator(object):
f'"{field_python_basename}"',
f"&{metadata.name}::{field.name}",
]
def_args.append("py::return_value_policy::copy")
def_args.append(rvp)
print(
f"\t\t\t.{definition_fn}({', '.join(def_args)})",
file=self.f,

View File

@ -2,6 +2,8 @@
ifneq ($(SMALL),1)
OBJS += techlibs/common/synth.o
OBJS += techlibs/common/prep.o
OBJS += techlibs/common/opensta.o
OBJS += techlibs/common/sdc_expand.o
endif
GENFILES += techlibs/common/simlib_help.inc

128
techlibs/common/opensta.cc Normal file
View File

@ -0,0 +1,128 @@
#include "kernel/rtlil.h"
#include "kernel/log.h"
#include "techlibs/common/opensta.h"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
#if !defined(YOSYS_DISABLE_SPAWN)
struct OpenstaPass : public Pass
{
OpenstaPass() : Pass("opensta", "run OpenSTA") { }
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" opensta [options]\n");
log("\n");
log("Expand SDC file with OpenSTA.\n");
log("Internal command like abc. Requires a well-formed design.\n");
log("For general SDC expansion with OpenSTA, use the sdc_expand command.\n");
log("\n");
log(" -exe <command>\n");
log(" use <command> to run OpenSTA instead of \"%s\"\n", default_opensta_cmd);
log("\n");
log(" -sdc-in <filename>\n");
log(" expand SDC input file <filename>\n");
log("\n");
log(" -sdc-out <filename>\n");
log(" expand SDC file to output file <filename>\n");
log("\n");
log(" -nocleanup\n");
log("\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
string run_from, run_to;
string opensta_exe = design->scratchpad_get_string("opensta.exe", default_opensta_cmd);
string sdc_filename, sdc_expanded_filename;
string tempdir_name, script_filename;
string verilog_filename, liberty_filename;
bool cleanup = design->scratchpad_get_bool("opensta.cleanup", true);
log_header(design, "Executing OPENSTA pass.\n");
log_experimental("opensta");
log_push();
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++)
{
if (args[argidx] == "-exe" && argidx+1 < args.size()) {
opensta_exe = args[++argidx];
continue;
}
if (args[argidx] == "-sdc-in" && argidx+1 < args.size()) {
sdc_filename = args[++argidx];
continue;
}
if (args[argidx] == "-sdc-out" && argidx+1 < args.size()) {
sdc_expanded_filename = args[++argidx];
continue;
}
if (args[argidx] == "-verilog" && argidx+1 < args.size()) {
verilog_filename = args[++argidx];
continue;
}
if (args[argidx] == "-liberty" && argidx+1 < args.size()) {
liberty_filename = args[++argidx];
continue;
}
if (args[argidx] == "-nocleanup") {
cleanup = false;
continue;
}
break;
}
extra_args(args, argidx, design);
if (!design->full_selection())
log_cmd_error("This command only operates on fully selected designs!\n");
if (cleanup)
tempdir_name = get_base_tmpdir() + "/";
else
tempdir_name = "_tmp_";
tempdir_name += proc_program_prefix() + "yosys-opensta-XXXXXX";
tempdir_name = make_temp_dir(tempdir_name);
script_filename = tempdir_name + "/opensta.tcl";
// script_filename
auto top_mod = design->top_module();
if (!top_mod)
log_error("Can't find top module in current design!\n");
std::ofstream f_script;
f_script.open(script_filename);
f_script << "read_verilog " << verilog_filename << "\n";
f_script << "read_lib " << liberty_filename << "\n";
f_script << "link_design " << RTLIL::unescape_id(top_mod->name) << "\n";
f_script << "read_sdc " << sdc_filename << "\n";
f_script << "write_sdc " << sdc_expanded_filename << "\n";
f_script.close();
std::string command = opensta_exe + " -exit " + script_filename;
auto process_line = [](const std::string &line) {
if (line.find("Creating black box") != std::string::npos)
return;
if (line.find("does not match net size") != std::string::npos)
return;
log("OpenSTA: %s", line.c_str());
};
int ret = run_command(command, process_line);
if (ret)
log_error("OpenSTA returned %d (error)\n", ret);
else
log("sdc_expanded_filename: %s\n", sdc_expanded_filename.c_str());
if (cleanup) {
log("Removing temp directory.\n");
remove_directory(tempdir_name);
}
log_pop();
}
} OpenstaPass;
#endif
PRIVATE_NAMESPACE_END

View File

@ -0,0 +1,8 @@
#include "kernel/yosys_common.h"
#ifndef OPENSTA_H
YOSYS_NAMESPACE_BEGIN
static const char* default_opensta_cmd = "sta";
YOSYS_NAMESPACE_END
#endif /* OPENSTA_H */

View File

@ -0,0 +1,156 @@
#include "kernel/rtlil.h"
#include "kernel/log.h"
#include "techlibs/common/opensta.h"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
struct SdcExpandPass : public ScriptPass
{
SdcExpandPass() : ScriptPass("sdc_expand", "expand SDC design with opensta") { }
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" sdc_expand [options]\n");
log("\n");
log("Expand SDC file with opensta based on arbitrary current design.\n");
log("Changes the design but only temporarily.\n");
log("\n");
log(" -exe <command>\n");
log(" use <command> to run OpenSTA instead of \"%s\"\n", default_opensta_cmd);
log("\n");
log(" -sdc-in <filename>\n");
log(" expand SDC file <filename>\n");
log("\n");
log(" -nocleanup\n");
log("\n");
log("\n");
log("The following commands are executed by this synthesis command:\n");
help_script();
log("\n");
}
string opensta_exe, sdc_filename, sdc_expanded_filename;
bool cleanup;
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
log_header(design, "Executing SDC_EXPAND pass.\n");
log_experimental("sdc_expand");
string run_from, run_to;
opensta_exe = "";
cleanup = design->scratchpad_get_bool("sdc_expand.cleanup", true);
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++)
{
if (args[argidx] == "-exe" && argidx+1 < args.size()) {
opensta_exe = args[++argidx];
continue;
}
if (args[argidx] == "-sdc-in" && argidx+1 < args.size()) {
sdc_filename = args[++argidx];
continue;
}
if (args[argidx] == "-sdc-out" && argidx+1 < args.size()) {
sdc_expanded_filename = args[++argidx];
continue;
}
if (args[argidx] == "-nocleanup") {
cleanup = false;
continue;
}
if (args[argidx] == "-run" && argidx+1 < args.size()) {
size_t pos = args[argidx+1].find(':');
if (pos == std::string::npos) {
run_from = args[++argidx];
run_to = args[argidx];
} else {
run_from = args[++argidx].substr(0, pos);
run_to = args[argidx].substr(pos+1);
}
continue;
}
break;
}
if (sdc_filename.empty())
log_cmd_error("Missing -sdc-in argument\n");
if (sdc_expanded_filename.empty())
log_cmd_error("Missing -sdc-out argument\n");
extra_args(args, argidx, design);
if (!design->full_selection())
log_cmd_error("This command only operates on fully selected designs!\n");
log_header(design, "Executing OPENSTA pass.\n");
log_push();
run_script(design, run_from, run_to);
log_pop();
}
void script() override
{
std::string tempdir_name;
run("design -save pre_expand");
run("proc");
run("memory");
// run("dfflegalize -cell $dff");
if (help_mode) {
tempdir_name = "<tmp_dir>";
} else {
if (cleanup)
tempdir_name = get_base_tmpdir() + "/";
else
tempdir_name = "_tmp_";
tempdir_name += proc_program_prefix() + "yosys-sdc_expand-XXXXXX";
tempdir_name = make_temp_dir(tempdir_name);
}
std::string verilog_path = tempdir_name + "/elaborated.v";
std::string write_verilog_cmd = "write_verilog -norename -noexpr -attr2comment -defparam ";
run(write_verilog_cmd + verilog_path);
run("read_verilog -setattr whitebox -defer -DSIMLIB_NOCHECKS +/simlib.v");
run("proc");
run("hierarchy -auto-top");
run("chtype -publish_icells");
std::string liberty_path = tempdir_name + "/yosys.lib";
run("icell_liberty " + liberty_path);
std::string opensta_pass_call = "opensta ";
if (opensta_exe.length()) {
opensta_pass_call += "-exe ";
opensta_pass_call += help_mode ? "<exe>" : opensta_exe;
}
opensta_pass_call += " -sdc-in ";
opensta_pass_call += help_mode ? "<sdc-in>" : sdc_filename;
opensta_pass_call += " -sdc-out ";
opensta_pass_call += help_mode ? "<sdc-out>" : sdc_expanded_filename;
opensta_pass_call += " -verilog ";
opensta_pass_call += help_mode ? "<verilog>" : verilog_path;
opensta_pass_call += " -liberty ";
opensta_pass_call += help_mode ? "<tmp_dir>/yosys.lib" : liberty_path;
if (!cleanup)
opensta_pass_call += " -nocleanup";
run(opensta_pass_call);
if (!help_mode) {
if (cleanup) {
log("Removing temp directory.\n");
remove_directory(tempdir_name);
} else {
log("Keeping temp directory %s\n", tempdir_name.c_str());
}
}
run("design -load pre_expand");
}
} SdcExpandPass;
PRIVATE_NAMESPACE_END

View File

@ -1,20 +0,0 @@
import os
from pyosys import libyosys as ys
__dir__ = os.path.dirname(os.path.abspath(__file__))
add_sub = os.path.join(__dir__, "..", "common", "add_sub.v")
base = ys.Design()
ys.run_pass(f"read_verilog {add_sub}", base)
ys.run_pass("hierarchy -top top", base)
ys.run_pass("proc", base)
ys.run_pass("equiv_opt -assert -map +/ecp5/cells_sim.v synth_ecp5", base)
postopt = ys.Design()
ys.run_pass("design -load postopt", postopt)
ys.run_pass("cd top", postopt)
ys.run_pass("select -assert-min 25 t:LUT4", postopt)
ys.run_pass("select -assert-max 26 t:LUT4", postopt)
ys.run_pass("select -assert-count 10 t:PFUMX", postopt)
ys.run_pass("select -assert-count 6 t:L6MUX21", postopt)
ys.run_pass("select -assert-none t:LUT4 t:PFUMX t:L6MUX21 %% t:* %D", postopt)

View File

@ -1,7 +1,6 @@
from pathlib import Path
from pyosys import libyosys as ys
__file_dir__ = Path(__file__).absolute().parent
add_sub = __file_dir__.parent / "arch" / "common" / "add_sub.v"

View File

@ -0,0 +1,28 @@
from pyosys import libyosys as ys
from pathlib import Path
__file_dir__ = Path(__file__).absolute().parent
def _dump_sigbit(bit):
if bit.is_wire():
if bit.wire.width == 1:
return bit.wire.name.str()
else:
return f"{bit.wire.name} [{bit.offset}]"
else:
if bit.data == ys.State.S1:
return 1
elif bit.data == ys.State.S0:
return 0
else:
assert "unknown constants not supported"
d = ys.Design()
ys.run_pass(f"read_verilog {__file_dir__ / 'spm.cut.v.gz'}", d)
ys.run_pass(f"hierarchy -top spm", d)
module = d.module(r"\spm")
for conn_from, conn_to in module.connections_:
for bit_from, bit_to in zip(conn_from, conn_to):
print(f"assign {_dump_sigbit(bit_from)} = {_dump_sigbit(bit_to)};")

70
tests/sdc/alu_sub.sdc Normal file
View 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
View 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
View 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

1
tests/sdc/get_foo.sdc Normal file
View File

@ -0,0 +1 @@
get_foo -bar 1

4
tests/sdc/run-test.sh Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -eu
source ../gen-tests-makefile.sh
generate_mk --yosys-scripts --bash

View File

@ -0,0 +1,2 @@
puts "This should print something:"
puts [get_ports {A[0]}]

4
tests/sdc/side-effects.sh Executable file
View 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'

5
tests/sdc/unknown-getter.sh Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
! ../../yosys -p 'read_verilog alu_sub.v; proc; hierarchy -auto-top; sdc get_foo.sdc' 2>&1 | grep 'Unknown getter'