From fd5918c811c701e82ba56d1040cd2acf327193f4 Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sat, 17 May 2025 14:10:23 -0700 Subject: [PATCH 001/153] get_field_names for structs --- backends/functional/smtlib_rosette.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc index c9e737d19..786f7b176 100644 --- a/backends/functional/smtlib_rosette.cc +++ b/backends/functional/smtlib_rosette.cc @@ -114,6 +114,13 @@ public: size_t i = field_names.at(name); return list(fields[i].accessor, std::move(record)); } + std::vector get_field_names() + { + std::vector names; + for (auto field : fields) + names.push_back(field.name); + return names; + } }; std::string smt_const(RTLIL::Const const &c) { From 7b4c9c5dcdbbda993751909a653c080f05688d02 Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sat, 17 May 2025 14:12:09 -0700 Subject: [PATCH 002/153] Add optional keyword-based constructor --- backends/functional/smtlib_rosette.cc | 37 ++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc index 786f7b176..316878a76 100644 --- a/backends/functional/smtlib_rosette.cc +++ b/backends/functional/smtlib_rosette.cc @@ -195,12 +195,13 @@ struct SmtrModule { Functional::IR ir; SmtrScope scope; std::string name; - + std::optional input_kw_name; + SmtrStruct input_struct; SmtrStruct output_struct; SmtrStruct state_struct; - SmtrModule(Module *module) + SmtrModule(Module *module, bool keyword_constructor) : ir(Functional::IR::from_module(module)) , scope() , name(scope.unique_name(module->name)) @@ -209,6 +210,10 @@ struct SmtrModule { , state_struct(scope.unique_name(module->name.str() + "_State"), scope) { scope.reserve(name + "_initial"); + if (keyword_constructor) { + input_kw_name = scope.unique_name(module->name.str() + "_inputs_kw"); + scope.reserve(*input_kw_name); + } for (auto input : ir.inputs()) input_struct.insert(input->name, input->sort); for (auto output : ir.outputs()) @@ -264,6 +269,22 @@ struct SmtrModule { w.pop(); } + void write_input_kw(SExprWriter &w) + { + w.push(); + w.open(list("define")); + w.open(list(name + "_inputs_kw")); + for (auto name : input_struct.get_field_names()) { + w << "#:" + name << name; + } + w.close(); + w.open(list(input_struct.name)); + for (auto name : input_struct.get_field_names()) { + w << name; + } + w.pop(); + } + void write(std::ostream &out) { SExprWriter w(out); @@ -272,6 +293,10 @@ struct SmtrModule { output_struct.write_definition(w); state_struct.write_definition(w); + if (input_kw_name) { + write_input_kw(w); + } + write_eval(w); write_initial(w); } @@ -289,12 +314,16 @@ struct FunctionalSmtrBackend : public Backend { log("\n"); log(" -provides\n"); log(" include 'provide' statement(s) for loading output as a module\n"); + log(" -keyword-constructor\n"); + log(" provide a function which can construct inputs using keywords\n"); + log(" \n"); log("\n"); } void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override { auto provides = false; + auto keyword_constructor = false; log_header(design, "Executing Functional Rosette Backend.\n"); @@ -303,6 +332,8 @@ struct FunctionalSmtrBackend : public Backend { { if (args[argidx] == "-provides") provides = true; + else if (args[argidx] == "-keyword-constructor") + keyword_constructor = true; else break; } @@ -315,7 +346,7 @@ struct FunctionalSmtrBackend : public Backend { for (auto module : design->selected_modules()) { log("Processing module `%s`.\n", module->name.c_str()); - SmtrModule smtr(module); + SmtrModule smtr(module, keyword_constructor); smtr.write(*f); } } From 10b8fdddb40eb0c7f1a273347428bacdf1b2a9ef Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sat, 17 May 2025 14:39:11 -0700 Subject: [PATCH 003/153] Rename argument --- backends/functional/smtlib_rosette.cc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc index 316878a76..6769d7384 100644 --- a/backends/functional/smtlib_rosette.cc +++ b/backends/functional/smtlib_rosette.cc @@ -201,7 +201,7 @@ struct SmtrModule { SmtrStruct output_struct; SmtrStruct state_struct; - SmtrModule(Module *module, bool keyword_constructor) + SmtrModule(Module *module, bool keyword_helpers) : ir(Functional::IR::from_module(module)) , scope() , name(scope.unique_name(module->name)) @@ -210,7 +210,7 @@ struct SmtrModule { , state_struct(scope.unique_name(module->name.str() + "_State"), scope) { scope.reserve(name + "_initial"); - if (keyword_constructor) { + if (keyword_helpers) { input_kw_name = scope.unique_name(module->name.str() + "_inputs_kw"); scope.reserve(*input_kw_name); } @@ -314,8 +314,8 @@ struct FunctionalSmtrBackend : public Backend { log("\n"); log(" -provides\n"); log(" include 'provide' statement(s) for loading output as a module\n"); - log(" -keyword-constructor\n"); - log(" provide a function which can construct inputs using keywords\n"); + log(" -keyword-helpers\n"); + log(" provide helper functions which can construct/destruct inputs/outputs using keywords\n"); log(" \n"); log("\n"); } @@ -323,7 +323,7 @@ struct FunctionalSmtrBackend : public Backend { void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override { auto provides = false; - auto keyword_constructor = false; + auto keyword_helpers = false; log_header(design, "Executing Functional Rosette Backend.\n"); @@ -332,8 +332,8 @@ struct FunctionalSmtrBackend : public Backend { { if (args[argidx] == "-provides") provides = true; - else if (args[argidx] == "-keyword-constructor") - keyword_constructor = true; + else if (args[argidx] == "-keyword-helpers") + keyword_helpers = true; else break; } @@ -346,7 +346,7 @@ struct FunctionalSmtrBackend : public Backend { for (auto module : design->selected_modules()) { log("Processing module `%s`.\n", module->name.c_str()); - SmtrModule smtr(module, keyword_constructor); + SmtrModule smtr(module, keyword_helpers); smtr.write(*f); } } From 1fdfba2a1afd9a7811c314914c0ee53cc9abff8e Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sat, 17 May 2025 15:17:29 -0700 Subject: [PATCH 004/153] Add helper for accessing by base name The existing access function isn't useful if we don't have access to the original names of the input/output/state signals. There may be a better way to do this, but it might require restructuring the SmtrStruct. --- backends/functional/smtlib_rosette.cc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc index 6769d7384..82bfd662a 100644 --- a/backends/functional/smtlib_rosette.cc +++ b/backends/functional/smtlib_rosette.cc @@ -114,6 +114,15 @@ public: size_t i = field_names.at(name); return list(fields[i].accessor, std::move(record)); } + SExpr access_by_base_name(SExpr record, std::string base_name) + { + // Find the field by its base name + auto it = std::find_if(fields.begin(), fields.end(), [&](const Field &field) { return field.name == base_name; }); + if (it == fields.end()) { + log_error("Field with base name '%s' not found in struct '%s'.\n", base_name.c_str(), name.c_str()); + } + return list(it->accessor, std::move(record)); + } std::vector get_field_names() { std::vector names; From c1111f125c391bae17fde32c22f2c926395f31cf Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sat, 17 May 2025 15:19:09 -0700 Subject: [PATCH 005/153] Add output helper as well --- backends/functional/smtlib_rosette.cc | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc index 82bfd662a..a59574fcf 100644 --- a/backends/functional/smtlib_rosette.cc +++ b/backends/functional/smtlib_rosette.cc @@ -205,6 +205,7 @@ struct SmtrModule { SmtrScope scope; std::string name; std::optional input_kw_name; + std::optional output_kw_name; SmtrStruct input_struct; SmtrStruct output_struct; @@ -222,6 +223,8 @@ struct SmtrModule { if (keyword_helpers) { input_kw_name = scope.unique_name(module->name.str() + "_inputs_kw"); scope.reserve(*input_kw_name); + output_kw_name = scope.unique_name(module->name.str() + "_outputs_kw"); + scope.reserve(*output_kw_name); } for (auto input : ir.inputs()) input_struct.insert(input->name, input->sort); @@ -278,8 +281,9 @@ struct SmtrModule { w.pop(); } - void write_input_kw(SExprWriter &w) + void write_keyword_helpers(SExprWriter &w) { + // Input struct keyword-based constructor. w.push(); w.open(list("define")); w.open(list(name + "_inputs_kw")); @@ -292,6 +296,18 @@ struct SmtrModule { w << name; } w.pop(); + // Output struct keyword-based destructor. + w.push(); + w.open(list("define")); + w.open(list(name + "_outputs_kw")); + const auto outputs_name = "outputs"; + w << outputs_name; + w.close(); + w.open(list("list")); + for (auto name : output_struct.get_field_names()) { + w << list("cons", "\"" + name + "\"", output_struct.access_by_base_name(outputs_name, name)); + } + w.pop(); } void write(std::ostream &out) @@ -303,7 +319,9 @@ struct SmtrModule { state_struct.write_definition(w); if (input_kw_name) { - write_input_kw(w); + if (!output_kw_name) + log_error("if keyword helpers are enabled, both input and output helper names are expected"); + write_keyword_helpers(w); } write_eval(w); From a55dc801758126378b8b318519996b947a003b70 Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sat, 17 May 2025 16:04:17 -0700 Subject: [PATCH 006/153] Rename parameter --- backends/functional/smtlib_rosette.cc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc index a59574fcf..4dd0fb855 100644 --- a/backends/functional/smtlib_rosette.cc +++ b/backends/functional/smtlib_rosette.cc @@ -211,7 +211,7 @@ struct SmtrModule { SmtrStruct output_struct; SmtrStruct state_struct; - SmtrModule(Module *module, bool keyword_helpers) + SmtrModule(Module *module, bool assoc_list_helpers) : ir(Functional::IR::from_module(module)) , scope() , name(scope.unique_name(module->name)) @@ -220,7 +220,7 @@ struct SmtrModule { , state_struct(scope.unique_name(module->name.str() + "_State"), scope) { scope.reserve(name + "_initial"); - if (keyword_helpers) { + if (assoc_list_helpers) { input_kw_name = scope.unique_name(module->name.str() + "_inputs_kw"); scope.reserve(*input_kw_name); output_kw_name = scope.unique_name(module->name.str() + "_outputs_kw"); @@ -341,8 +341,8 @@ struct FunctionalSmtrBackend : public Backend { log("\n"); log(" -provides\n"); log(" include 'provide' statement(s) for loading output as a module\n"); - log(" -keyword-helpers\n"); - log(" provide helper functions which can construct/destruct inputs/outputs using keywords\n"); + log(" -assoc-list-helpers\n"); + log(" provide helper functions which convert inputs/outputs from/to association lists\n"); log(" \n"); log("\n"); } @@ -350,7 +350,7 @@ struct FunctionalSmtrBackend : public Backend { void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override { auto provides = false; - auto keyword_helpers = false; + auto assoc_list_helpers = false; log_header(design, "Executing Functional Rosette Backend.\n"); @@ -359,8 +359,8 @@ struct FunctionalSmtrBackend : public Backend { { if (args[argidx] == "-provides") provides = true; - else if (args[argidx] == "-keyword-helpers") - keyword_helpers = true; + else if (args[argidx] == "-assoc-list-helpers") + assoc_list_helpers = true; else break; } @@ -373,7 +373,7 @@ struct FunctionalSmtrBackend : public Backend { for (auto module : design->selected_modules()) { log("Processing module `%s`.\n", module->name.c_str()); - SmtrModule smtr(module, keyword_helpers); + SmtrModule smtr(module, assoc_list_helpers); smtr.write(*f); } } From af51097af77375cc0f46facb449e1472a2514aca Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sun, 18 May 2025 18:01:43 -0700 Subject: [PATCH 007/153] Convert to 'assoc list helpers' --- backends/functional/smtlib_rosette.cc | 44 ++++++++++++++++----------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc index 4dd0fb855..ec116f1a2 100644 --- a/backends/functional/smtlib_rosette.cc +++ b/backends/functional/smtlib_rosette.cc @@ -204,8 +204,8 @@ struct SmtrModule { Functional::IR ir; SmtrScope scope; std::string name; - std::optional input_kw_name; - std::optional output_kw_name; + std::optional input_helper_name; + std::optional output_helper_name; SmtrStruct input_struct; SmtrStruct output_struct; @@ -221,10 +221,10 @@ struct SmtrModule { { scope.reserve(name + "_initial"); if (assoc_list_helpers) { - input_kw_name = scope.unique_name(module->name.str() + "_inputs_kw"); - scope.reserve(*input_kw_name); - output_kw_name = scope.unique_name(module->name.str() + "_outputs_kw"); - scope.reserve(*output_kw_name); + input_helper_name = scope.unique_name(module->name.str() + "_inputs_helper"); + scope.reserve(*input_helper_name); + output_helper_name = scope.unique_name(module->name.str() + "_outputs_helper"); + scope.reserve(*output_helper_name); } for (auto input : ir.inputs()) input_struct.insert(input->name, input->sort); @@ -281,28 +281,36 @@ struct SmtrModule { w.pop(); } - void write_keyword_helpers(SExprWriter &w) + void write_assoc_list_helpers(SExprWriter &w) { // Input struct keyword-based constructor. w.push(); w.open(list("define")); - w.open(list(name + "_inputs_kw")); - for (auto name : input_struct.get_field_names()) { - w << "#:" + name << name; - } + const auto inputs_name = "inputs"; + w.open(list(*input_helper_name, inputs_name)); w.close(); w.open(list(input_struct.name)); for (auto name : input_struct.get_field_names()) { - w << name; + w.push(); + w.open(list("let")); + w.push(); + w.open(list()); + w.open(list("assoc-result")); + w << list("assoc", "\"" + name + "\"", inputs_name); + w.pop(); + w.open(list("if", "assoc-result")); + w << list("cdr", "assoc-result"); + w.open(list("begin")); + w << list("fprintf", list("current-error-port"), "\"%s not found in inputs\""); + w << "'not-found"; + w.pop(); } w.pop(); // Output struct keyword-based destructor. w.push(); w.open(list("define")); - w.open(list(name + "_outputs_kw")); const auto outputs_name = "outputs"; - w << outputs_name; - w.close(); + w << list(*output_helper_name, outputs_name); w.open(list("list")); for (auto name : output_struct.get_field_names()) { w << list("cons", "\"" + name + "\"", output_struct.access_by_base_name(outputs_name, name)); @@ -318,10 +326,10 @@ struct SmtrModule { output_struct.write_definition(w); state_struct.write_definition(w); - if (input_kw_name) { - if (!output_kw_name) + if (input_helper_name) { + if (!output_helper_name) log_error("if keyword helpers are enabled, both input and output helper names are expected"); - write_keyword_helpers(w); + write_assoc_list_helpers(w); } write_eval(w); From 8ec9de00ec2b39e268cd5b8557a457cf0dca74af Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Tue, 20 May 2025 17:45:23 -0700 Subject: [PATCH 008/153] Use ir.inputs()/ir.outputs() --- backends/functional/smtlib_rosette.cc | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc index ec116f1a2..f34cc7440 100644 --- a/backends/functional/smtlib_rosette.cc +++ b/backends/functional/smtlib_rosette.cc @@ -114,22 +114,6 @@ public: size_t i = field_names.at(name); return list(fields[i].accessor, std::move(record)); } - SExpr access_by_base_name(SExpr record, std::string base_name) - { - // Find the field by its base name - auto it = std::find_if(fields.begin(), fields.end(), [&](const Field &field) { return field.name == base_name; }); - if (it == fields.end()) { - log_error("Field with base name '%s' not found in struct '%s'.\n", base_name.c_str(), name.c_str()); - } - return list(it->accessor, std::move(record)); - } - std::vector get_field_names() - { - std::vector names; - for (auto field : fields) - names.push_back(field.name); - return names; - } }; std::string smt_const(RTLIL::Const const &c) { @@ -290,13 +274,13 @@ struct SmtrModule { w.open(list(*input_helper_name, inputs_name)); w.close(); w.open(list(input_struct.name)); - for (auto name : input_struct.get_field_names()) { + for (auto input : ir.inputs()) { w.push(); w.open(list("let")); w.push(); w.open(list()); w.open(list("assoc-result")); - w << list("assoc", "\"" + name + "\"", inputs_name); + w << list("assoc", "\"" + RTLIL::unescape_id(input->name) + "\"", inputs_name); w.pop(); w.open(list("if", "assoc-result")); w << list("cdr", "assoc-result"); @@ -312,8 +296,8 @@ struct SmtrModule { const auto outputs_name = "outputs"; w << list(*output_helper_name, outputs_name); w.open(list("list")); - for (auto name : output_struct.get_field_names()) { - w << list("cons", "\"" + name + "\"", output_struct.access_by_base_name(outputs_name, name)); + for (auto output : ir.outputs()) { + w << list("cons", "\"" + RTLIL::unescape_id(name) + "\"", output_struct.access("outputs", output->name)); } w.pop(); } From d8b27d41c03f9a5932c2364e519bc59d95067150 Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Wed, 21 May 2025 21:31:07 -0700 Subject: [PATCH 009/153] Bugfix --- backends/functional/smtlib_rosette.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc index f34cc7440..639efb46e 100644 --- a/backends/functional/smtlib_rosette.cc +++ b/backends/functional/smtlib_rosette.cc @@ -297,7 +297,7 @@ struct SmtrModule { w << list(*output_helper_name, outputs_name); w.open(list("list")); for (auto output : ir.outputs()) { - w << list("cons", "\"" + RTLIL::unescape_id(name) + "\"", output_struct.access("outputs", output->name)); + w << list("cons", "\"" + RTLIL::unescape_id(output->name) + "\"", output_struct.access("outputs", output->name)); } w.pop(); } From 9faa61dfc66a695565e48d634758aa11b1b58dee Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Mon, 26 May 2025 20:43:32 -0700 Subject: [PATCH 010/153] Remove gate on smt and rkt tests as per https://github.com/YosysHQ/yosys/pull/5128#issuecomment-2896280647 --- tests/functional/run-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/run-test.sh b/tests/functional/run-test.sh index 9f70462ee..b9a0595f6 100755 --- a/tests/functional/run-test.sh +++ b/tests/functional/run-test.sh @@ -1,2 +1,2 @@ #!/usr/bin/env bash -pytest -v -m "not smt and not rkt" "$@" +pytest -v "$@" From 51560b0bf62c3f18317aa9513d189273cb48ee77 Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Mon, 26 May 2025 21:46:53 -0700 Subject: [PATCH 011/153] Start adding Rosette simulation facilties --- tests/functional/simulate_rosette.py | 1 + tests/functional/simulate_rosette.rkt | 104 ++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 tests/functional/simulate_rosette.py create mode 100644 tests/functional/simulate_rosette.rkt diff --git a/tests/functional/simulate_rosette.py b/tests/functional/simulate_rosette.py new file mode 100644 index 000000000..9400a575a --- /dev/null +++ b/tests/functional/simulate_rosette.py @@ -0,0 +1 @@ +"""Python utilities for simulating Rosette code.""" \ No newline at end of file diff --git a/tests/functional/simulate_rosette.rkt b/tests/functional/simulate_rosette.rkt new file mode 100644 index 000000000..39038d0cd --- /dev/null +++ b/tests/functional/simulate_rosette.rkt @@ -0,0 +1,104 @@ +; Utilities for simulating Rosette programs. +#lang racket/base + +(provide simulate-rosette) + +(require (only-in rosette bv) + racket/match + racket/list) + +; Inputs: +; - function: The function for the module to simulate. This should be a Rosette function generated by +; Yosys's `write_fuctional_rosette` backend. +; - input-helper, output-helper: association-list-based helpers for input and output struct, generated +; by Yosys's `write_fuctional_rosette` backend with `-assoc-list-helpers` enabled. +; - initial-state: The initial state of the module, as generated by Yosys's `write_fuctional_rosette` +; backend. +(define (simulate-rosette #:function function + #:input-helper input-helper + #:output-helper output-helper + #:initial-state initial-state + #:inputs inputs + #:outputs outputs) + (error "TODO: Implement simulate-rosette function")) + +; Inputs: +; - config: Association list mapping input name (string) to a configuration value, which is one of the +; following: +; - 'exhaustive: The input should be exhaustively tested. +; - : The input should be tested with this many random inputs. When the input is not +; present in the config, it defaults to 'exhaustive. +; - +(define (generate-inputs #:input-helper input-helper + #:num-inputs num-inputs + #:config config + #:inputs inputs) + ; Fill out missing vallues in the config with 'exhaustive. + (define config + (map (λ (input) + (let ([found (assoc (car input) config)]) (or found (cons (car input) 'exhaustive)))) + inputs)) + + ; ; Generate the inputs. + ; (define generated-inputs + ; (map (λ (input) + ; (let ([input-name (car input)] [input-bitwidth (cdr input)]) + ; (cond + ; [(equal? 'exhaustive (cdr (assoc input-name config))) (list input-name 'exhaustive)] + ; [(number? (cdr (assoc input-name config))) + ; (list input-name (make-random-input input-type (cdr (assoc input-name config))))] + ; [else (error "Invalid configuration for input" input-name)]))) + ; inputs)) + + (error "TODO")) + +; Helper function: for a given input name, bitwidth, and configuration, generate a list of inputs. +; Output: List of Rosette bitvector values for the input. +(define (generate-inputs-for-one input-name bitwidth config) + (cond + ; If the configuration is 'exhaustive, or if they request a number of inputs that is greater than + ; or equal to the number of possible values for the bitwidth, generate all possible inputs. + [(or (equal? config 'exhaustive) (and (number? config) (>= config (expt 2 bitwidth)))) + (for/list ([n (range (expt 2 bitwidth))]) + (bv n bitwidth))] + [(and (number? config) (positive? config)) + (map (λ (_) (bv (random (expt 2 bitwidth)) bitwidth)) (range config))] + [else (error (format "Invalid configuration ~a for input ~a" config input-name))])) + +; This is what gets executed when the script is run. +(module main racket/base + (require racket/cmdline)) + +(module+ test + (require rackunit) + (test-case "generate-inputs-for-one" + (check-equal? (generate-inputs-for-one "input1" 4 'exhaustive) + (list (bv 0 4) + (bv 1 4) + (bv 2 4) + (bv 3 4) + (bv 4 4) + (bv 5 4) + (bv 6 4) + (bv 7 4) + (bv 8 4) + (bv 9 4) + (bv 10 4) + (bv 11 4) + (bv 12 4) + (bv 13 4) + (bv 14 4) + (bv 15 4))) + + ; Requesting fewer inputs than the number of possible values for the bitwidth. + (check-equal? (length (generate-inputs-for-one "input2" 3 5)) 5) + + ; Requesting more inputs than the number of possible values for the bitwidth. + (check-equal? (generate-inputs-for-one "input3" 2 5) (list (bv 0 2) (bv 1 2) (bv 2 2) (bv 3 2))) + + ; Requesting equal number of inputs as the number of possible values for the bitwidth. + (check-equal? (generate-inputs-for-one "input4" 2 4) (list (bv 0 2) (bv 1 2) (bv 2 2) (bv 3 2))) + + ; Requesting invalid configuration should raise an error. + (check-exn exn:fail? (λ () (generate-inputs-for-one "input5" 2 'invalid-config))) + (check-exn exn:fail? (λ () (generate-inputs-for-one "input5" 2 bytes->string/latin-1))))) From 8a9d724873b74f6d0660407e1c47131f83717200 Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Mon, 23 Jun 2025 19:20:06 -0700 Subject: [PATCH 012/153] Finish up functions and tests, TODO: CLI --- tests/functional/simulate_rosette.rkt | 211 ++++++++++++++++---------- 1 file changed, 132 insertions(+), 79 deletions(-) diff --git a/tests/functional/simulate_rosette.rkt b/tests/functional/simulate_rosette.rkt index 39038d0cd..aa838a5a0 100644 --- a/tests/functional/simulate_rosette.rkt +++ b/tests/functional/simulate_rosette.rkt @@ -1,104 +1,157 @@ ; Utilities for simulating Rosette programs. +; +; Tests can be run with `raco test `. #lang racket/base (provide simulate-rosette) (require (only-in rosette bv) - racket/match racket/list) ; Inputs: ; - function: The function for the module to simulate. This should be a Rosette function generated by ; Yosys's `write_fuctional_rosette` backend. -; - input-helper, output-helper: association-list-based helpers for input and output struct, generated -; by Yosys's `write_fuctional_rosette` backend with `-assoc-list-helpers` enabled. ; - initial-state: The initial state of the module, as generated by Yosys's `write_fuctional_rosette` ; backend. -(define (simulate-rosette #:function function - #:input-helper input-helper - #:output-helper output-helper - #:initial-state initial-state - #:inputs inputs - #:outputs outputs) - (error "TODO: Implement simulate-rosette function")) +; - inputs: A list of association lists. The function will be called with each association list as +; inputs, and the state will be threaded through each call. +; +; Outputs: +; - A list of outputs, one for each cycle. The outputs are a list of the output objects generated by +; `function`. +(define (simulate-rosette #:function function #:initial-state initial-state #:inputs inputs) + + (define outputs-and-states + (drop (reverse (foldl (lambda (input acc) + (let* ([outputs (function input (cdr (car acc)))]) (cons outputs acc))) + (list (cons 'unused initial-state)) + inputs)) + 1)) + + (define outputs (map car outputs-and-states)) + + outputs) ; Inputs: -; - config: Association list mapping input name (string) to a configuration value, which is one of the -; following: -; - 'exhaustive: The input should be exhaustively tested. -; - : The input should be tested with this many random inputs. When the input is not -; present in the config, it defaults to 'exhaustive. -; - -(define (generate-inputs #:input-helper input-helper - #:num-inputs num-inputs - #:config config - #:inputs inputs) - ; Fill out missing vallues in the config with 'exhaustive. - (define config - (map (λ (input) - (let ([found (assoc (car input) config)]) (or found (cons (car input) 'exhaustive)))) - inputs)) +; - inputs: association list mapping string name to bitwidth. +; - num-inputs: number of inputs to generate. +; TODO(@gussmith23): If `num-inputs` is more than the number of possible values, just enumerate. +(define (generate-inputs #:inputs inputs #:num-inputs num-inputs) + (define (generate-random-input inputs) + (map (lambda (pair) (cons (car pair) (bv (random (expt 2 (cdr pair))) (cdr pair)))) inputs)) + (for/list ([_ (range num-inputs)]) + (generate-random-input inputs))) - ; ; Generate the inputs. - ; (define generated-inputs - ; (map (λ (input) - ; (let ([input-name (car input)] [input-bitwidth (cdr input)]) - ; (cond - ; [(equal? 'exhaustive (cdr (assoc input-name config))) (list input-name 'exhaustive)] - ; [(number? (cdr (assoc input-name config))) - ; (list input-name (make-random-input input-type (cdr (assoc input-name config))))] - ; [else (error "Invalid configuration for input" input-name)]))) - ; inputs)) - - (error "TODO")) - -; Helper function: for a given input name, bitwidth, and configuration, generate a list of inputs. -; Output: List of Rosette bitvector values for the input. -(define (generate-inputs-for-one input-name bitwidth config) - (cond - ; If the configuration is 'exhaustive, or if they request a number of inputs that is greater than - ; or equal to the number of possible values for the bitwidth, generate all possible inputs. - [(or (equal? config 'exhaustive) (and (number? config) (>= config (expt 2 bitwidth)))) - (for/list ([n (range (expt 2 bitwidth))]) - (bv n bitwidth))] - [(and (number? config) (positive? config)) - (map (λ (_) (bv (random (expt 2 bitwidth)) bitwidth)) (range config))] - [else (error (format "Invalid configuration ~a for input ~a" config input-name))])) +; Generates a clock signal for the given inputs. +; +; Given a string of inputs, one per clock cycle, this function generates a clock signal alongside the +; inputs. It does so by alternating the clock signal between 0 and 1 for each cycle, starting with 0. +; For example, if the inputs are (list inputs1 inputs2 inputs3), the output will be (list (cons (cons +; "clk" (bv 0 1)) inputs1) (cons (cons "clk" (bv 1 1)) inputs1) (cons (cons "clk" (bv 0 1)) inputs2) +; (cons (cons "clk" (bv 1 1)) inputs2) ... ). +; +; Inputs: +; - clock-name: The name of the clock signal. +; - inputs: A list of inputs in association list form, as output by `generate-inputs`. +; +; Outputs: +; - A list of association lists, each containing a new clock signal. Will be twice the length of the +; inputs list. +(define (generate-clock #:clock-name clock-name #:inputs inputs) + (apply append + (for/list ([this-cycle-inputs inputs]) + (list (cons (cons clock-name (bv 0 1)) this-cycle-inputs) + (cons (cons clock-name (bv 1 1)) this-cycle-inputs))))) ; This is what gets executed when the script is run. (module main racket/base - (require racket/cmdline)) + (require racket/cmdline) + + ; - input-helper, output-helper: association-list-based helpers for input and output struct, generated + ; by Yosys's `write_fuctional_rosette` backend with `-assoc-list-helpers` enabled. + ) (module+ test - (require rackunit) - (test-case "generate-inputs-for-one" - (check-equal? (generate-inputs-for-one "input1" 4 'exhaustive) - (list (bv 0 4) - (bv 1 4) - (bv 2 4) - (bv 3 4) - (bv 4 4) - (bv 5 4) - (bv 6 4) - (bv 7 4) - (bv 8 4) - (bv 9 4) - (bv 10 4) - (bv 11 4) - (bv 12 4) - (bv 13 4) - (bv 14 4) - (bv 15 4))) + (require rackunit + (only-in rosette bv bvadd)) + (test-case "generate-inputs" + (check-equal? (length (generate-inputs #:inputs (list (cons "input1" 4)) #:num-inputs 10)) 10) + ; Check that this call generates a list of one-length lists, each containing a single association + ; list with the key "input1" and a random value. + (check-true (foldl (lambda (input acc) + (and acc (equal? (length input) 1) (equal? (car (first input)) "input1"))) + #t + (generate-inputs #:inputs (list (cons "input1" 4)) #:num-inputs 10)))) - ; Requesting fewer inputs than the number of possible values for the bitwidth. - (check-equal? (length (generate-inputs-for-one "input2" 3 5)) 5) + (test-case "generate-clock" + (define inputs + (list (list (cons "input1" (bv 4 4)) (cons "input2" (bv 3 3)) (cons "input3" (bv 2 2))) + (list (cons "input1" (bv 3 4)) (cons "input2" (bv 4 3)) (cons "input3" (bv 1 2))) + (list (cons "input1" (bv 2 4)) (cons "input2" (bv 5 3)) (cons "input3" (bv 0 2))))) - ; Requesting more inputs than the number of possible values for the bitwidth. - (check-equal? (generate-inputs-for-one "input3" 2 5) (list (bv 0 2) (bv 1 2) (bv 2 2) (bv 3 2))) + (check-equal? (length (generate-clock #:clock-name "clk" #:inputs inputs)) 6) - ; Requesting equal number of inputs as the number of possible values for the bitwidth. - (check-equal? (generate-inputs-for-one "input4" 2 4) (list (bv 0 2) (bv 1 2) (bv 2 2) (bv 3 2))) + (check-equal? + (generate-clock #:clock-name "clk" #:inputs inputs) + (list (cons (cons "clk" (bv 0 1)) + (list (cons "input1" (bv 4 4)) (cons "input2" (bv 3 3)) (cons "input3" (bv 2 2)))) + (cons (cons "clk" (bv 1 1)) + (list (cons "input1" (bv 4 4)) (cons "input2" (bv 3 3)) (cons "input3" (bv 2 2)))) + (cons (cons "clk" (bv 0 1)) + (list (cons "input1" (bv 3 4)) (cons "input2" (bv 4 3)) (cons "input3" (bv 1 2)))) + (cons (cons "clk" (bv 1 1)) + (list (cons "input1" (bv 3 4)) (cons "input2" (bv 4 3)) (cons "input3" (bv 1 2)))) + (cons (cons "clk" (bv 0 1)) + (list (cons "input1" (bv 2 4)) (cons "input2" (bv 5 3)) (cons "input3" (bv 0 2)))) + (cons (cons "clk" (bv 1 1)) + (list (cons "input1" (bv 2 4)) (cons "input2" (bv 5 3)) (cons "input3" (bv 0 2))))))) + (test-case "simulate-rosette" - ; Requesting invalid configuration should raise an error. - (check-exn exn:fail? (λ () (generate-inputs-for-one "input5" 2 'invalid-config))) - (check-exn exn:fail? (λ () (generate-inputs-for-one "input5" 2 bytes->string/latin-1))))) + ; This function will take association lists as inputs, so the helper function is simply identity. + ; This is not generally true of Yosys-generated code. Similarly, this function uses an association + ; list for state, which is not what Yosys generates, but it's easier for testing. + ; + ; A one-stage adder. Inputs are registered in one clock cycle, and the output is the sum of the + ; two registered inputs. + (define (module-function inputs state) + (let* ([a (cdr (assoc "a" inputs))] + [b (cdr (assoc "b" inputs))] + [clk (cdr (assoc "clk" inputs))] + [old-clk (cdr (assoc "clk" state))] + [prev-a (cdr (assoc "prev-a" state))] + [prev-b (cdr (assoc "prev-b" state))] + [a-reg (cdr (assoc "a-reg" state))] + [b-reg (cdr (assoc "b-reg" state))] + [clk-ticked (and (equal? clk (bv 1 1)) (equal? old-clk (bv 0 1)))] + [new-a-reg (if clk-ticked prev-a a-reg)] + [new-b-reg (if clk-ticked prev-b b-reg)] + [out (list (cons "o" (bvadd new-a-reg new-b-reg)))] + [new-state (list (cons "prev-a" a) + (cons "a-reg" new-a-reg) + (cons "prev-b" b) + (cons "b-reg" new-b-reg) + (cons "clk" clk))]) + (cons out new-state))) + + (define outputs + (simulate-rosette #:function module-function + #:initial-state (list (cons "a-reg" (bv 0 4)) + (cons "b-reg" (bv 0 4)) + (cons "prev-a" (bv 0 4)) + (cons "prev-b" (bv 0 4)) + (cons "clk" (bv 0 1))) + #:inputs + (list (list (cons "clk" (bv 0 1)) (cons "a" (bv 4 4)) (cons "b" (bv 4 4))) + (list (cons "clk" (bv 1 1)) (cons "a" (bv 3 4)) (cons "b" (bv 0 4))) + (list (cons "clk" (bv 0 1)) (cons "a" (bv 10 4)) (cons "b" (bv 9 4))) + (list (cons "clk" (bv 1 1)) (cons "a" (bv 2 4)) (cons "b" (bv -1 4))) + (list (cons "clk" (bv 0 1)) (cons "a" (bv 4 4)) (cons "b" (bv -15 4))) + (list (cons "clk" (bv 1 1)) (cons "a" (bv 0 4)) (cons "b" (bv 0 4)))))) + + (check-equal? outputs + (list (list (cons "o" (bv 0 4))) + (list (cons "o" (bv 8 4))) + (list (cons "o" (bv 8 4))) + (list (cons "o" (bv 3 4))) + (list (cons "o" (bv 3 4))) + (list (cons "o" (bv -11 4))))))) From a1d68fe3bcddbf452832613822fce0235980b078 Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Thu, 26 Jun 2025 17:44:12 -0700 Subject: [PATCH 013/153] Add option for using assoc list helpers in tests --- tests/functional/rkt_vcd.py | 113 +++++++++++++----- tests/functional/simulate_rosette.rkt | 157 -------------------------- tests/functional/test_functional.py | 8 +- 3 files changed, 91 insertions(+), 187 deletions(-) delete mode 100644 tests/functional/simulate_rosette.rkt diff --git a/tests/functional/rkt_vcd.py b/tests/functional/rkt_vcd.py index 1b2cf31e3..f06c2dc27 100644 --- a/tests/functional/rkt_vcd.py +++ b/tests/functional/rkt_vcd.py @@ -43,21 +43,37 @@ def write_vcd(filename: Path, signals: SignalStepMap, timescale='1 ns', date='to if change_time == time: f.write(f"{value} {signal_name}\n") -def simulate_rosette(rkt_file_path: Path, vcd_path: Path, num_steps: int, rnd: Random): + +def simulate_rosette( + rkt_file_path: Path, + vcd_path: Path, + num_steps: int, + rnd: Random, + use_assoc_list_helpers: bool = False, +): + """ + Args: + - use_assoc_list_helpers: If True, will use the association list helpers + in the Racket file. The file should have been generated with the + -assoc-list-helpers flag in the yosys command. + """ signals: dict[str, list[str]] = {} inputs: SignalWidthMap = {} outputs: SignalWidthMap = {} current_struct_name: str = "" - with open(rkt_file_path, 'r') as rkt_file: + with open(rkt_file_path, "r") as rkt_file: for line in rkt_file: - m = re.search(r'gold_(Inputs|Outputs|State)', line) + m = re.search(r"gold_(Inputs|Outputs|State)", line) if m: current_struct_name = m.group(1) - if current_struct_name == "State": break - elif not current_struct_name: continue # skip lines before structs - m = re.search(r'; (.+?)\b \(bitvector (\d+)\)', line) - if not m: continue # skip non matching lines (probably closing the struct) + if current_struct_name == "State": + break + elif not current_struct_name: + continue # skip lines before structs + m = re.search(r"; (.+?)\b \(bitvector (\d+)\)", line) + if not m: + continue # skip non matching lines (probably closing the struct) signal = m.group(1) width = int(m.group(2)) if current_struct_name == "Inputs": @@ -69,43 +85,86 @@ def simulate_rosette(rkt_file_path: Path, vcd_path: Path, num_steps: int, rnd: R step_list: list[int] = [] for step in range(num_steps): value = rnd.getrandbits(width) - binary_string = format(value, '0{}b'.format(width)) + binary_string = format(value, "0{}b".format(width)) step_list.append(binary_string) signals[signal] = step_list - test_rkt_file_path = rkt_file_path.with_suffix('.tst.rkt') - with open(test_rkt_file_path, 'w') as test_rkt_file: - test_rkt_file.writelines([ - '#lang rosette\n', - f'(require "{rkt_file_path.name}")\n', - ]) + test_rkt_file_path = rkt_file_path.with_suffix(".tst.rkt") + with open(test_rkt_file_path, "w") as test_rkt_file: + test_rkt_file.writelines( + [ + "#lang rosette\n", + f'(require "{rkt_file_path.name}")\n', + ] + ) for step in range(num_steps): this_step = f"step_{step}" value_list: list[str] = [] - for signal, width in inputs.items(): - value = signals[signal][step] - value_list.append(f"(bv #b{value} {width})") - gold_Inputs = f"(gold_Inputs {' '.join(value_list)})" + if use_assoc_list_helpers: + # Generate inputs as a list of cons pairs making up the + # association list. + for signal, width in inputs.items(): + value = signals[signal][step] + value_list.append(f'(cons "{signal}" (bv #b{value} {width}))') + else: + # Otherwise, we generate the inputs as a list of bitvectors. + for signal, width in inputs.items(): + value = signals[signal][step] + value_list.append(f"(bv #b{value} {width})") + gold_Inputs = ( + f"(gold_inputs_helper (list {' '.join(value_list)}))" + if use_assoc_list_helpers + else f"(gold_Inputs {' '.join(value_list)})" + ) gold_State = f"(cdr step_{step-1})" if step else "gold_initial" - test_rkt_file.write(f"(define {this_step} (gold {gold_Inputs} {gold_State})) (car {this_step})\n") + get_value_expr = ( + f"(gold_outputs_helper (car {this_step}))" + if use_assoc_list_helpers + else f"(car {this_step})" + ) + test_rkt_file.write( + f"(define {this_step} (gold {gold_Inputs} {gold_State})) {get_value_expr}\n" + ) + cmd = ["racket", test_rkt_file_path] - status = subprocess.run(cmd, capture_output=True) - assert status.returncode == 0, f"{cmd[0]} failed" + try: + status = subprocess.run(cmd, capture_output=True, check=True) + except subprocess.CalledProcessError as e: + raise RuntimeError( + f"Racket simulation failed with command: {cmd}\n" + f"Error: {e.stderr.decode()}" + ) from e for signal in outputs.keys(): signals[signal] = [] for line in status.stdout.decode().splitlines(): - m = re.match(r'\(gold_Outputs( \(bv \S+ \d+\))+\)', line) + m = ( + re.match(r"\(list( \(cons \"\S+\" \(bv \S+ \d+\)\))+\)", line) + if use_assoc_list_helpers + else re.match(r"\(gold_Outputs( \(bv \S+ \d+\))+\)", line) + ) assert m, f"Incomplete output definition {line!r}" - for output, (value, width) in zip(outputs.keys(), re.findall(r'\(bv (\S+) (\d+)\)', line)): + outputs_values_and_widths = ( + { + output: re.findall( + r"\(cons \"" + output + r"\" \(bv (\S+) (\d+)\)\)", line + )[0] + for output in outputs.keys() + }.items() + if use_assoc_list_helpers + else zip(outputs.keys(), re.findall(r"\(bv (\S+) (\d+)\)", line)) + ) + for output, (value, width) in outputs_values_and_widths: assert isinstance(value, str), f"Bad value {value!r}" - assert value.startswith(('#b', '#x')), f"Non-binary value {value!r}" - assert int(width) == outputs[output], f"Width mismatch for output {output!r} (got {width}, expected {outputs[output]})" - int_value = int(value[2:], 16 if value.startswith('#x') else 2) - binary_string = format(int_value, '0{}b'.format(width)) + assert value.startswith(("#b", "#x")), f"Non-binary value {value!r}" + assert ( + int(width) == outputs[output] + ), f"Width mismatch for output {output!r} (got {width}, expected {outputs[output]})" + int_value = int(value[2:], 16 if value.startswith("#x") else 2) + binary_string = format(int_value, "0{}b".format(width)) signals[output].append(binary_string) vcd_signals: SignalStepMap = {} diff --git a/tests/functional/simulate_rosette.rkt b/tests/functional/simulate_rosette.rkt deleted file mode 100644 index aa838a5a0..000000000 --- a/tests/functional/simulate_rosette.rkt +++ /dev/null @@ -1,157 +0,0 @@ -; Utilities for simulating Rosette programs. -; -; Tests can be run with `raco test `. -#lang racket/base - -(provide simulate-rosette) - -(require (only-in rosette bv) - racket/list) - -; Inputs: -; - function: The function for the module to simulate. This should be a Rosette function generated by -; Yosys's `write_fuctional_rosette` backend. -; - initial-state: The initial state of the module, as generated by Yosys's `write_fuctional_rosette` -; backend. -; - inputs: A list of association lists. The function will be called with each association list as -; inputs, and the state will be threaded through each call. -; -; Outputs: -; - A list of outputs, one for each cycle. The outputs are a list of the output objects generated by -; `function`. -(define (simulate-rosette #:function function #:initial-state initial-state #:inputs inputs) - - (define outputs-and-states - (drop (reverse (foldl (lambda (input acc) - (let* ([outputs (function input (cdr (car acc)))]) (cons outputs acc))) - (list (cons 'unused initial-state)) - inputs)) - 1)) - - (define outputs (map car outputs-and-states)) - - outputs) - -; Inputs: -; - inputs: association list mapping string name to bitwidth. -; - num-inputs: number of inputs to generate. -; TODO(@gussmith23): If `num-inputs` is more than the number of possible values, just enumerate. -(define (generate-inputs #:inputs inputs #:num-inputs num-inputs) - (define (generate-random-input inputs) - (map (lambda (pair) (cons (car pair) (bv (random (expt 2 (cdr pair))) (cdr pair)))) inputs)) - (for/list ([_ (range num-inputs)]) - (generate-random-input inputs))) - -; Generates a clock signal for the given inputs. -; -; Given a string of inputs, one per clock cycle, this function generates a clock signal alongside the -; inputs. It does so by alternating the clock signal between 0 and 1 for each cycle, starting with 0. -; For example, if the inputs are (list inputs1 inputs2 inputs3), the output will be (list (cons (cons -; "clk" (bv 0 1)) inputs1) (cons (cons "clk" (bv 1 1)) inputs1) (cons (cons "clk" (bv 0 1)) inputs2) -; (cons (cons "clk" (bv 1 1)) inputs2) ... ). -; -; Inputs: -; - clock-name: The name of the clock signal. -; - inputs: A list of inputs in association list form, as output by `generate-inputs`. -; -; Outputs: -; - A list of association lists, each containing a new clock signal. Will be twice the length of the -; inputs list. -(define (generate-clock #:clock-name clock-name #:inputs inputs) - (apply append - (for/list ([this-cycle-inputs inputs]) - (list (cons (cons clock-name (bv 0 1)) this-cycle-inputs) - (cons (cons clock-name (bv 1 1)) this-cycle-inputs))))) - -; This is what gets executed when the script is run. -(module main racket/base - (require racket/cmdline) - - ; - input-helper, output-helper: association-list-based helpers for input and output struct, generated - ; by Yosys's `write_fuctional_rosette` backend with `-assoc-list-helpers` enabled. - ) - -(module+ test - (require rackunit - (only-in rosette bv bvadd)) - (test-case "generate-inputs" - (check-equal? (length (generate-inputs #:inputs (list (cons "input1" 4)) #:num-inputs 10)) 10) - ; Check that this call generates a list of one-length lists, each containing a single association - ; list with the key "input1" and a random value. - (check-true (foldl (lambda (input acc) - (and acc (equal? (length input) 1) (equal? (car (first input)) "input1"))) - #t - (generate-inputs #:inputs (list (cons "input1" 4)) #:num-inputs 10)))) - - (test-case "generate-clock" - (define inputs - (list (list (cons "input1" (bv 4 4)) (cons "input2" (bv 3 3)) (cons "input3" (bv 2 2))) - (list (cons "input1" (bv 3 4)) (cons "input2" (bv 4 3)) (cons "input3" (bv 1 2))) - (list (cons "input1" (bv 2 4)) (cons "input2" (bv 5 3)) (cons "input3" (bv 0 2))))) - - (check-equal? (length (generate-clock #:clock-name "clk" #:inputs inputs)) 6) - - (check-equal? - (generate-clock #:clock-name "clk" #:inputs inputs) - (list (cons (cons "clk" (bv 0 1)) - (list (cons "input1" (bv 4 4)) (cons "input2" (bv 3 3)) (cons "input3" (bv 2 2)))) - (cons (cons "clk" (bv 1 1)) - (list (cons "input1" (bv 4 4)) (cons "input2" (bv 3 3)) (cons "input3" (bv 2 2)))) - (cons (cons "clk" (bv 0 1)) - (list (cons "input1" (bv 3 4)) (cons "input2" (bv 4 3)) (cons "input3" (bv 1 2)))) - (cons (cons "clk" (bv 1 1)) - (list (cons "input1" (bv 3 4)) (cons "input2" (bv 4 3)) (cons "input3" (bv 1 2)))) - (cons (cons "clk" (bv 0 1)) - (list (cons "input1" (bv 2 4)) (cons "input2" (bv 5 3)) (cons "input3" (bv 0 2)))) - (cons (cons "clk" (bv 1 1)) - (list (cons "input1" (bv 2 4)) (cons "input2" (bv 5 3)) (cons "input3" (bv 0 2))))))) - (test-case "simulate-rosette" - - ; This function will take association lists as inputs, so the helper function is simply identity. - ; This is not generally true of Yosys-generated code. Similarly, this function uses an association - ; list for state, which is not what Yosys generates, but it's easier for testing. - ; - ; A one-stage adder. Inputs are registered in one clock cycle, and the output is the sum of the - ; two registered inputs. - (define (module-function inputs state) - (let* ([a (cdr (assoc "a" inputs))] - [b (cdr (assoc "b" inputs))] - [clk (cdr (assoc "clk" inputs))] - [old-clk (cdr (assoc "clk" state))] - [prev-a (cdr (assoc "prev-a" state))] - [prev-b (cdr (assoc "prev-b" state))] - [a-reg (cdr (assoc "a-reg" state))] - [b-reg (cdr (assoc "b-reg" state))] - [clk-ticked (and (equal? clk (bv 1 1)) (equal? old-clk (bv 0 1)))] - [new-a-reg (if clk-ticked prev-a a-reg)] - [new-b-reg (if clk-ticked prev-b b-reg)] - [out (list (cons "o" (bvadd new-a-reg new-b-reg)))] - [new-state (list (cons "prev-a" a) - (cons "a-reg" new-a-reg) - (cons "prev-b" b) - (cons "b-reg" new-b-reg) - (cons "clk" clk))]) - (cons out new-state))) - - (define outputs - (simulate-rosette #:function module-function - #:initial-state (list (cons "a-reg" (bv 0 4)) - (cons "b-reg" (bv 0 4)) - (cons "prev-a" (bv 0 4)) - (cons "prev-b" (bv 0 4)) - (cons "clk" (bv 0 1))) - #:inputs - (list (list (cons "clk" (bv 0 1)) (cons "a" (bv 4 4)) (cons "b" (bv 4 4))) - (list (cons "clk" (bv 1 1)) (cons "a" (bv 3 4)) (cons "b" (bv 0 4))) - (list (cons "clk" (bv 0 1)) (cons "a" (bv 10 4)) (cons "b" (bv 9 4))) - (list (cons "clk" (bv 1 1)) (cons "a" (bv 2 4)) (cons "b" (bv -1 4))) - (list (cons "clk" (bv 0 1)) (cons "a" (bv 4 4)) (cons "b" (bv -15 4))) - (list (cons "clk" (bv 1 1)) (cons "a" (bv 0 4)) (cons "b" (bv 0 4)))))) - - (check-equal? outputs - (list (list (cons "o" (bv 0 4))) - (list (cons "o" (bv 8 4))) - (list (cons "o" (bv 8 4))) - (list (cons "o" (bv 3 4))) - (list (cons "o" (bv 3 4))) - (list (cons "o" (bv -11 4))))))) diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 7a09966d8..86eabef1e 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -74,7 +74,8 @@ def test_smt(cell, parameters, tmp_path, num_steps, rnd): yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', '')) @pytest.mark.rkt -def test_rkt(cell, parameters, tmp_path, num_steps, rnd): +@pytest.mark.parametrize("use_assoc_list_helpers", [True, False]) +def test_rkt(cell, parameters, tmp_path, num_steps, rnd, use_assoc_list_helpers): import rkt_vcd rtlil_file = tmp_path / 'rtlil.il' @@ -83,8 +84,9 @@ def test_rkt(cell, parameters, tmp_path, num_steps, rnd): vcd_yosys_sim_file = tmp_path / 'yosys.vcd' cell.write_rtlil_file(rtlil_file, parameters) - yosys(f"read_rtlil {quote(rtlil_file)} ; clk2fflogic ; write_functional_rosette -provides {quote(rkt_file)}") - rkt_vcd.simulate_rosette(rkt_file, vcd_functional_file, num_steps, rnd(cell.name + "-rkt")) + use_assoc_helpers_flag = '-assoc-list-helpers' if use_assoc_list_helpers else '' + yosys(f"read_rtlil {quote(rtlil_file)} ; clk2fflogic ; write_functional_rosette -provides {use_assoc_helpers_flag} {quote(rkt_file)}") + rkt_vcd.simulate_rosette(rkt_file, vcd_functional_file, num_steps, rnd(cell.name + "-rkt"), use_assoc_list_helpers=use_assoc_list_helpers) yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', '')) def test_print_graph(tmp_path): From 3c54d8aef791fbe000630ceb0e8af1c1f9f964ae Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Mon, 7 Jul 2025 10:38:32 +1200 Subject: [PATCH 014/153] tests/functional: Auto parallelize Use the unique cell name (cell type + parameters) for the vcd filename to avoid collisions when converting to fst. --- tests/functional/conftest.py | 2 +- tests/functional/rtlil_cells.py | 5 +++-- tests/functional/run-test.sh | 2 +- tests/functional/test_functional.py | 12 ++++++------ 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py index a9fbb3c59..fb00d4f22 100644 --- a/tests/functional/conftest.py +++ b/tests/functional/conftest.py @@ -31,4 +31,4 @@ def pytest_generate_tests(metafunc): seed1 = metafunc.config.getoption("seed") rnd = lambda seed2: random.Random('{}-{}'.format(seed1, seed2)) names, cases = generate_test_cases(per_cell, rnd) - metafunc.parametrize("cell,parameters", cases, ids=names) + metafunc.parametrize("name,cell,parameters", cases, ids=names) diff --git a/tests/functional/rtlil_cells.py b/tests/functional/rtlil_cells.py index 964d81ddf..ab7cd4e0c 100644 --- a/tests/functional/rtlil_cells.py +++ b/tests/functional/rtlil_cells.py @@ -374,8 +374,9 @@ def generate_test_cases(per_cell, rnd): for (name, parameters) in cell.generate_tests(rnd): if not name in seen_names: seen_names.add(name) - tests.append((cell, parameters)) - names.append(f'{cell.name}-{name}' if name != '' else cell.name) + full_name = f'{cell.name}-{name}' if name != '' else cell.name + tests.append((full_name, cell, parameters)) + names.append(full_name) if per_cell is not None and len(seen_names) >= per_cell: break return (names, tests) \ No newline at end of file diff --git a/tests/functional/run-test.sh b/tests/functional/run-test.sh index b9a0595f6..6786e93f1 100755 --- a/tests/functional/run-test.sh +++ b/tests/functional/run-test.sh @@ -1,2 +1,2 @@ #!/usr/bin/env bash -pytest -v "$@" +pytest -v -n auto "$@" diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 86eabef1e..0553d6ecc 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -40,12 +40,12 @@ def yosys_sim(rtlil_file, vcd_reference_file, vcd_out_file, preprocessing = ""): capture_output=True, check=False) raise -def test_cxx(cell, parameters, tmp_path, num_steps, rnd): +def test_cxx(name, cell, parameters, tmp_path, num_steps, rnd): rtlil_file = tmp_path / 'rtlil.il' vcdharness_cc_file = base_path / 'tests/functional/vcd_harness.cc' cc_file = tmp_path / 'my_module_functional_cxx.cc' vcdharness_exe_file = tmp_path / 'a.out' - vcd_functional_file = tmp_path / 'functional.vcd' + vcd_functional_file = tmp_path / f'{name}.vcd' vcd_yosys_sim_file = tmp_path / 'yosys.vcd' cell.write_rtlil_file(rtlil_file, parameters) @@ -56,12 +56,12 @@ def test_cxx(cell, parameters, tmp_path, num_steps, rnd): yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', '')) @pytest.mark.smt -def test_smt(cell, parameters, tmp_path, num_steps, rnd): +def test_smt(name, cell, parameters, tmp_path, num_steps, rnd): import smt_vcd rtlil_file = tmp_path / 'rtlil.il' smt_file = tmp_path / 'smtlib.smt' - vcd_functional_file = tmp_path / 'functional.vcd' + vcd_functional_file = tmp_path / f'{name}.vcd' vcd_yosys_sim_file = tmp_path / 'yosys.vcd' if hasattr(cell, 'smt_max_steps'): @@ -75,12 +75,12 @@ def test_smt(cell, parameters, tmp_path, num_steps, rnd): @pytest.mark.rkt @pytest.mark.parametrize("use_assoc_list_helpers", [True, False]) -def test_rkt(cell, parameters, tmp_path, num_steps, rnd, use_assoc_list_helpers): +def test_rkt(name, cell, parameters, tmp_path, num_steps, rnd, use_assoc_list_helpers): import rkt_vcd rtlil_file = tmp_path / 'rtlil.il' rkt_file = tmp_path / 'smtlib.rkt' - vcd_functional_file = tmp_path / 'functional.vcd' + vcd_functional_file = tmp_path / f'{name}.vcd' vcd_yosys_sim_file = tmp_path / 'yosys.vcd' cell.write_rtlil_file(rtlil_file, parameters) From 108a4ed4964cb3a70f34c44d232f23fb73897728 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Mon, 7 Jul 2025 10:45:51 +1200 Subject: [PATCH 015/153] tests/functional: Reduce CI to 100 steps Takes approx half the time, at least when testing locally. --- tests/functional/run-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/run-test.sh b/tests/functional/run-test.sh index 6786e93f1..03e3b60f8 100755 --- a/tests/functional/run-test.sh +++ b/tests/functional/run-test.sh @@ -1,2 +1,2 @@ #!/usr/bin/env bash -pytest -v -n auto "$@" +pytest -v -n auto "$@" --steps 100 From dcf72ff8e2aa724b82ada89ad068b9308ed97a54 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Mon, 7 Jul 2025 11:27:37 +1200 Subject: [PATCH 016/153] Document tests/functional prereqs --- .../extending_yosys/test_suites.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/source/yosys_internals/extending_yosys/test_suites.rst b/docs/source/yosys_internals/extending_yosys/test_suites.rst index 3e5f45b94..6627fdbdd 100644 --- a/docs/source/yosys_internals/extending_yosys/test_suites.rst +++ b/docs/source/yosys_internals/extending_yosys/test_suites.rst @@ -14,6 +14,24 @@ compiler versions. For up to date information, including OS versions, refer to .. _Yosys Git repo: https://github.com/YosysHQ/yosys .. _the git actions page: https://github.com/YosysHQ/yosys/actions +Functional backend testing +-------------------------- + +Testing of the functional backend is controlled by the +``ENABLE_FUNCTIONAL_TESTS`` make variable. Setting it to a value of ``1``, +either when calling ``make test`` or in your ``Makefile.conf`` file, will enable +these additional tests. + +.. note:: + + The functional backend tests requires additional prerequisites to be + installed: + + - racket and z3, available via ``apt-get`` or similar. + - pytest and pytest-xdist, available via ``pip``; pytest-xdist-gnumake is + also recommended. + - rosette, available via ``raco`` (after installing racket). + .. todo:: are unit tests currently working .. From 7d10a724903749001d6ecb3e4b723d7f2bbd4a2e Mon Sep 17 00:00:00 2001 From: Anhijkt Date: Thu, 6 Nov 2025 23:29:47 +0200 Subject: [PATCH 017/153] fsm_detect: add adff detection --- passes/fsm/fsm_detect.cc | 11 +++- tests/various/fsm-arst.ys | 133 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 tests/various/fsm-arst.ys diff --git a/passes/fsm/fsm_detect.cc b/passes/fsm/fsm_detect.cc index 9cffbf95a..5f491a16c 100644 --- a/passes/fsm/fsm_detect.cc +++ b/passes/fsm/fsm_detect.cc @@ -199,8 +199,15 @@ static void detect_fsm(RTLIL::Wire *wire, bool ignore_self_reset=false) } SigSpec sig_y = sig_d, sig_undef; - if (!ignore_self_reset && ce.eval(sig_y, sig_undef)) - is_self_resetting = true; + if (!ignore_self_reset) { + if (cellport.first->type == ID($adff)) { + SigSpec sig_arst = assign_map(cellport.first->getPort(ID::ARST)); + if (ce.eval(sig_arst, sig_undef)) + is_self_resetting = true; + } + else if (ce.eval(sig_y, sig_undef)) + is_self_resetting = true; + } } if (has_fsm_encoding_attr) diff --git a/tests/various/fsm-arst.ys b/tests/various/fsm-arst.ys new file mode 100644 index 000000000..2cfca1d57 --- /dev/null +++ b/tests/various/fsm-arst.ys @@ -0,0 +1,133 @@ +read_verilog << EOT +module non_self_rs_fsm ( + input wire clk, + input wire reset, + output wire s1 +); + localparam [7:0] RST = 8'b10010010; + localparam [7:0] S1 = 8'b01001000; + localparam [7:0] S2 = 8'b11000111; + + reg [7:0] current_state, next_state; + always @(posedge clk or posedge reset) begin + if (reset) begin + current_state <= RST; + end else begin + current_state <= next_state; + end + end + + always @(*) begin + next_state = current_state; + + case (current_state) + RST: next_state = S1; + S1: next_state = S2; + S2: next_state = S1; + default: next_state = RST; + endcase + end + + assign s1 = next_state == S1; +endmodule + +module semi_self_rs_fsm ( + input wire clk, + inout wire reset, + input wire test, + output wire s1 +); + localparam [7:0] RST = 8'b10010010; + localparam [7:0] S1 = 8'b01001000; + localparam [7:0] S2 = 8'b11000111; + + reg [7:0] current_state, next_state; + reg [1:0] reset_test; + + assign reset = (test || (reset_test == 2)); + + always @(posedge clk or posedge reset) begin + if (reset) begin + current_state <= RST; + reset_test <= 0; + end else begin + current_state <= next_state; + if (current_state == S2) + reset_test = reset_test + 1; + end + end + + + always @(*) begin + next_state = current_state; + + case (current_state) + RST: next_state = S1; + S2: next_state = S1; + S1: next_state = S2; + + default: next_state = RST; + endcase + end + + assign s1 = next_state == S1; +endmodule + +module self_rs_fsm ( + input wire clk, + inout wire reset, + output wire s1 +); + localparam [7:0] RST = 8'b10010010; + localparam [7:0] S1 = 8'b01001000; + localparam [7:0] S2 = 8'b11000111; + + reg [7:0] current_state, next_state; + reg reset_reg; + + wire reset = (reset_reg || next_state == S1); + always @(posedge clk or posedge reset) begin + if (reset) begin + current_state <= RST; + reset_reg = 0; + end else begin + current_state <= next_state; + end + end + + always @(*) begin + next_state = current_state; + + case (current_state) + RST: next_state = S1; + S1: next_state = S2; + S2: next_state = S1; + default: begin + reset_reg = 1; + next_state = RST; + end + endcase + end + + assign s1 = next_state == S1; +endmodule + +EOT + +proc +opt_expr +opt_clean +check +opt -nodffe -nosdff + +fsm_detect +fsm_extract + +cd non_self_rs_fsm +select -assert-count 1 t:$fsm + +cd semi_self_rs_fsm +select -assert-count 1 t:$fsm + +cd self_rs_fsm +select -assert-none t:$fsm From a5cc905184f3a8112063742a13653a8be148a51b Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 7 Nov 2025 15:52:24 +1300 Subject: [PATCH 018/153] simplify.cc: Fix unsized const in params --- frontends/ast/simplify.cc | 10 +++++++--- tests/verilog/unbased_unsized.sv | 9 +++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index 97abf7452..330e9ef12 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -2231,9 +2231,13 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin } if (children[0]->type == AST_CONSTANT) { if (width != int(children[0]->bits.size())) { - RTLIL::SigSpec sig(children[0]->bits); - sig.extend_u0(width, children[0]->is_signed); - children[0] = mkconst_bits(location, sig.as_const().to_bits(), is_signed); + RTLIL::Const val; + if (children[0]->is_unsized) { + val = children[0]->bitsAsUnsizedConst(width); + } else { + val = children[0]->bitsAsConst(width); + } + children[0] = mkconst_bits(location, val.to_bits(), is_signed); fixup_hierarchy_flags(); } children[0]->is_signed = is_signed; diff --git a/tests/verilog/unbased_unsized.sv b/tests/verilog/unbased_unsized.sv index 1d0c5a72c..af932cb74 100644 --- a/tests/verilog/unbased_unsized.sv +++ b/tests/verilog/unbased_unsized.sv @@ -6,6 +6,11 @@ module pass_through( endmodule module top; + localparam logic [63:0] + l01 = '0, + l02 = '1, + l03 = 'x, + l04 = 'z; logic [63:0] o01, o02, o03, o04, o05, o06, o07, o08, @@ -36,5 +41,9 @@ module top; assert (o10 === {64 {1'b1}}); assert (o11 === {64 {1'bx}}); assert (o12 === {64 {1'bz}}); + assert (l01 === {64 {1'b0}}); + assert (l02 === {64 {1'b1}}); + assert (l03 === {64 {1'bx}}); + assert (l04 === {64 {1'bz}}); end endmodule From e4c5900acd5caeaaea6f4e3983fe3f2ad6ba0495 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 7 Nov 2025 17:13:12 +1300 Subject: [PATCH 019/153] tests/verilog: Unsized params in cell Non-zero case fails with `read_verilog`, but passes with `verific` and `read_slang`. --- tests/verilog/unbased_unsized.sv | 16 ++++++++++++++++ tests/verilog/unbased_unsized.ys | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/verilog/unbased_unsized.sv b/tests/verilog/unbased_unsized.sv index af932cb74..62f7d41ff 100644 --- a/tests/verilog/unbased_unsized.sv +++ b/tests/verilog/unbased_unsized.sv @@ -5,6 +5,14 @@ module pass_through( assign out = inp; endmodule +module set_param #( + parameter logic [63:0] VALUE +) ( + output logic [63:0] out +); + assign out = VALUE; +endmodule + module top; localparam logic [63:0] l01 = '0, @@ -28,6 +36,10 @@ module top; pass_through pt10('1, o10); pass_through pt11('x, o11); pass_through pt12('z, o12); + set_param #('0) sp13(o13); + set_param #('1) sp14(o14); + set_param #('x) sp15(o15); + set_param #('z) sp16(o16); always @* begin assert (o01 === {64 {1'b0}}); assert (o02 === {64 {1'b1}}); @@ -45,5 +57,9 @@ module top; assert (l02 === {64 {1'b1}}); assert (l03 === {64 {1'bx}}); assert (l04 === {64 {1'bz}}); + assert (o13 === {64 {1'b0}}); + assert (o14 === {64 {1'b1}}); + assert (o15 === {64 {1'bx}}); + assert (o16 === {64 {1'bz}}); end endmodule diff --git a/tests/verilog/unbased_unsized.ys b/tests/verilog/unbased_unsized.ys index 3290650d5..851866140 100644 --- a/tests/verilog/unbased_unsized.ys +++ b/tests/verilog/unbased_unsized.ys @@ -1,5 +1,5 @@ read_verilog -sv unbased_unsized.sv -hierarchy +hierarchy -top top proc flatten opt -full From 7302bf9a66b5b7635403cc4f59575b0e2899d414 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 7 Nov 2025 17:45:07 +1300 Subject: [PATCH 020/153] Add CONST_FLAG_UNSIZED In order to support unsized constants being used as parameters, the `const` struct needs to know if it is unsized (so that the parameter can be used to set the size). Add unsized flag to param value serialization and rtlil back-/front-end. Add cell params to `tests/rtlil/everything.v`. --- backends/rtlil/rtlil_backend.cc | 7 +++++-- frontends/ast/ast.cc | 9 +++++++-- frontends/rtlil/rtlil_frontend.cc | 5 +++++ kernel/rtlil.h | 9 +++++---- tests/rtlil/everything.v | 17 +++++++++++++++++ 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/backends/rtlil/rtlil_backend.cc b/backends/rtlil/rtlil_backend.cc index d607be837..63f64d12d 100644 --- a/backends/rtlil/rtlil_backend.cc +++ b/backends/rtlil/rtlil_backend.cc @@ -61,7 +61,9 @@ void RTLIL_BACKEND::dump_const(std::ostream &f, const RTLIL::Const &data, int wi return; } } - f << stringf("%d'", width); + if ((data.flags & RTLIL::CONST_FLAG_UNSIZED) == 0) { + f << stringf("%d'", width); + } if (data.flags & RTLIL::CONST_FLAG_SIGNED) { f << stringf("s"); } @@ -172,9 +174,10 @@ void RTLIL_BACKEND::dump_cell(std::ostream &f, std::string indent, const RTLIL:: dump_attributes(f, indent, cell); f << stringf("%s" "cell %s %s\n", indent, cell->type, cell->name); for (const auto& [name, param] : reversed(cell->parameters)) { - f << stringf("%s parameter%s%s %s ", indent, + f << stringf("%s parameter%s%s%s %s ", indent, (param.flags & RTLIL::CONST_FLAG_SIGNED) != 0 ? " signed" : "", (param.flags & RTLIL::CONST_FLAG_REAL) != 0 ? " real" : "", + (param.flags & RTLIL::CONST_FLAG_UNSIZED) != 0 ? " unsized" : "", name); dump_const(f, param); f << stringf("\n"); diff --git a/frontends/ast/ast.cc b/frontends/ast/ast.cc index 4a16abee9..984c4294c 100644 --- a/frontends/ast/ast.cc +++ b/frontends/ast/ast.cc @@ -993,6 +993,8 @@ RTLIL::Const AstNode::asParaConst() const RTLIL::Const val = asAttrConst(); if (is_signed) val.flags |= RTLIL::CONST_FLAG_SIGNED; + if (is_unsized) + val.flags |= RTLIL::CONST_FLAG_UNSIZED; return val; } @@ -1766,7 +1768,10 @@ static std::string serialize_param_value(const RTLIL::Const &val) { res.push_back('s'); if (val.flags & RTLIL::ConstFlags::CONST_FLAG_REAL) res.push_back('r'); - res += stringf("%d", GetSize(val)); + if (val.flags & RTLIL::ConstFlags::CONST_FLAG_UNSIZED) + res.push_back('u'); + else + res += stringf("%d", GetSize(val)); res.push_back('\''); res.append(val.as_string("?")); return res; @@ -1860,7 +1865,7 @@ std::string AstModule::derive_common(RTLIL::Design *design, const dictsecond.flags & RTLIL::CONST_FLAG_STRING) != 0) child->children[0] = AstNode::mkconst_str(loc, it->second.decode_string()); else - child->children[0] = AstNode::mkconst_bits(loc, it->second.to_bits(), (it->second.flags & RTLIL::CONST_FLAG_SIGNED) != 0); + child->children[0] = AstNode::mkconst_bits(loc, it->second.to_bits(), (it->second.flags & RTLIL::CONST_FLAG_SIGNED) != 0, (it->second.flags & RTLIL::CONST_FLAG_UNSIZED) != 0); rewritten.insert(it->first); } diff --git a/frontends/rtlil/rtlil_frontend.cc b/frontends/rtlil/rtlil_frontend.cc index 04d01fc93..271962725 100644 --- a/frontends/rtlil/rtlil_frontend.cc +++ b/frontends/rtlil/rtlil_frontend.cc @@ -567,10 +567,13 @@ struct RTLILFrontendWorker { if (try_parse_keyword("parameter")) { bool is_signed = false; bool is_real = false; + bool is_unsized = false; if (try_parse_keyword("signed")) { is_signed = true; } else if (try_parse_keyword("real")) { is_real = true; + } else if (try_parse_keyword("unsized")) { + is_unsized = true; } RTLIL::IdString param_name = parse_id(); RTLIL::Const val = parse_const(); @@ -578,6 +581,8 @@ struct RTLILFrontendWorker { val.flags |= RTLIL::CONST_FLAG_SIGNED; if (is_real) val.flags |= RTLIL::CONST_FLAG_REAL; + if (is_unsized) + val.flags |= RTLIL::CONST_FLAG_UNSIZED; cell->parameters.insert({std::move(param_name), std::move(val)}); expect_eol(); } else if (try_parse_keyword("connect")) { diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 7e699b365..85ae70ef4 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -53,10 +53,11 @@ namespace RTLIL // Semantic metadata - how can this constant be interpreted? // Values may be generally non-exclusive enum ConstFlags : unsigned char { - CONST_FLAG_NONE = 0, - CONST_FLAG_STRING = 1, - CONST_FLAG_SIGNED = 2, // only used for parameters - CONST_FLAG_REAL = 4 // only used for parameters + CONST_FLAG_NONE = 0, + CONST_FLAG_STRING = 1, + CONST_FLAG_SIGNED = 2, // only used for parameters + CONST_FLAG_REAL = 4, // only used for parameters + CONST_FLAG_UNSIZED = 8, // only used for parameters }; enum SelectPartials : unsigned char { diff --git a/tests/rtlil/everything.v b/tests/rtlil/everything.v index 666d630c2..c4f8c348a 100644 --- a/tests/rtlil/everything.v +++ b/tests/rtlil/everything.v @@ -38,3 +38,20 @@ module foo( assign b = bb; assign y = a + bb; endmodule + +module set_param #( + parameter [3:0] VALUE = 1'bx +) ( + output logic [3:0] out +); + assign out = VALUE; +endmodule + +module use_param ( + output logic [3:0] a, b, c, d +); + set_param #($signed(1)) spa (a); + set_param #('1) spb (b); + set_param #(1.1) spc (c); + set_param #(1'b1) spd (d); +endmodule From 5f76729cbbcc6fd98f1cacbcf4e598cd74f30857 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 12 Nov 2025 00:24:49 +0000 Subject: [PATCH 021/153] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 09dcd0639..f29c15484 100644 --- a/Makefile +++ b/Makefile @@ -161,7 +161,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.59+0 +YOSYS_VER := 0.59+9 YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2 | cut -d'+' -f1) YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'+' -f2) From 2319d82efbec1f9f1f06fd3d7a338aafa4a0904a Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Thu, 16 Oct 2025 03:37:49 +0000 Subject: [PATCH 022/153] Make IdString::begins_width/ends_with take std::string_view so we avoid a strlen when the parameter is a string constant --- kernel/rtlil.h | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 27f610f85..85e89686e 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -405,16 +405,15 @@ struct RTLIL::IdString return strncmp(c_str()+pos, s, len); } - bool begins_with(const char* prefix) const { - size_t len = strlen(prefix); - if (size() < len) return false; - return compare(0, len, prefix) == 0; + bool begins_with(std::string_view prefix) const { + if (size() < prefix.size()) return false; + return compare(0, prefix.size(), prefix.data()) == 0; } - bool ends_with(const char* suffix) const { - size_t len = strlen(suffix); - if (size() < len) return false; - return compare(size()-len, len, suffix) == 0; + bool ends_with(std::string_view suffix) const { + size_t sz = size(); + if (sz < suffix.size()) return false; + return compare(sz - suffix.size(), suffix.size(), suffix.data()) == 0; } bool contains(const char* str) const { From 32641bbf935a44597c50d285beacb812f18d9075 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Thu, 16 Oct 2025 04:05:56 +0000 Subject: [PATCH 023/153] Make IdString::contains take std::string_view so we avoid a strlen when the parameter is a string constant --- kernel/rtlil.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 85e89686e..b6104e0e5 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -416,8 +416,8 @@ struct RTLIL::IdString return compare(sz - suffix.size(), suffix.size(), suffix.data()) == 0; } - bool contains(const char* str) const { - return strstr(c_str(), str); + bool contains(std::string_view s) const { + return std::string_view(c_str()).find(s) != std::string::npos; } size_t size() const { From 20639906e3c645a9ae73076169e4ead98bdd4ab1 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Wed, 20 Aug 2025 03:47:03 +0000 Subject: [PATCH 024/153] Store IdString lengths and use them --- kernel/io.cc | 2 +- kernel/rtlil.cc | 6 ++--- kernel/rtlil.h | 58 +++++++++++++++++++++++++++++-------------------- 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/kernel/io.cc b/kernel/io.cc index e9801f63e..e713cff85 100644 --- a/kernel/io.cc +++ b/kernel/io.cc @@ -606,7 +606,7 @@ void format_emit_idstring(std::string &result, std::string_view spec, int *dynam { if (spec == "%s") { // Format checking will have guaranteed num_dynamic_ints == 0. - result += arg.c_str(); + result += arg.str_view(); return; } format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg.c_str()); diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index a58487216..5dac6016c 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -35,7 +35,7 @@ YOSYS_NAMESPACE_BEGIN bool RTLIL::IdString::destruct_guard_ok = false; RTLIL::IdString::destruct_guard_t RTLIL::IdString::destruct_guard; -std::vector RTLIL::IdString::global_id_storage_; +std::vector RTLIL::IdString::global_id_storage_; std::unordered_map RTLIL::IdString::global_id_index_; #ifndef YOSYS_NO_IDS_REFCNT std::vector RTLIL::IdString::global_refcount_storage_; @@ -57,14 +57,14 @@ static void populate(std::string_view name) name = name.substr(1); } RTLIL::IdString::global_id_index_.insert({name, GetSize(RTLIL::IdString::global_id_storage_)}); - RTLIL::IdString::global_id_storage_.push_back(const_cast(name.data())); + RTLIL::IdString::global_id_storage_.push_back({const_cast(name.data()), GetSize(name)}); } void RTLIL::IdString::prepopulate() { int size = static_cast(RTLIL::StaticId::STATIC_ID_END); global_id_storage_.reserve(size); - RTLIL::IdString::global_id_storage_.push_back(const_cast("")); + RTLIL::IdString::global_id_storage_.push_back({const_cast(""), 0}); global_id_index_.reserve(size); global_refcount_storage_.resize(size, 1); #define X(N) populate("\\" #N); diff --git a/kernel/rtlil.h b/kernel/rtlil.h index b6104e0e5..45d696321 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -138,6 +138,11 @@ namespace RTLIL struct RTLIL::IdString { + struct Storage { + char *buf; + int size; + }; + #undef YOSYS_XTRACE_GET_PUT #undef YOSYS_SORT_ID_FREE_LIST #undef YOSYS_USE_STICKY_IDS @@ -151,7 +156,7 @@ struct RTLIL::IdString ~destruct_guard_t() { destruct_guard_ok = false; } } destruct_guard; - static std::vector global_id_storage_; + static std::vector global_id_storage_; static std::unordered_map global_id_index_; #ifndef YOSYS_NO_IDS_REFCNT // For prepopulated IdStrings, the refcount is meaningless since they @@ -174,10 +179,10 @@ struct RTLIL::IdString #ifdef YOSYS_XTRACE_GET_PUT for (int idx = 0; idx < GetSize(global_id_storage_); idx++) { - if (global_id_storage_.at(idx) == nullptr) + if (global_id_storage_.at(idx).buf == nullptr) log("#X# DB-DUMP index %d: FREE\n", idx); else - log("#X# DB-DUMP index %d: '%s' (ref %u)\n", idx, global_id_storage_.at(idx), global_refcount_storage_.at(idx)); + log("#X# DB-DUMP index %d: '%s' (ref %u)\n", idx, global_id_storage_.at(idx).buf, global_refcount_storage_.at(idx)); } #endif } @@ -204,7 +209,7 @@ struct RTLIL::IdString #endif #ifdef YOSYS_XTRACE_GET_PUT if (yosys_xtrace && idx >= static_cast(StaticId::STATIC_ID_END)) - log("#X# GET-BY-INDEX '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx), idx, global_refcount_storage_.at(idx)); + log("#X# GET-BY-INDEX '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx).buf, idx, global_refcount_storage_.at(idx)); #endif return idx; } @@ -225,7 +230,7 @@ struct RTLIL::IdString #endif #ifdef YOSYS_XTRACE_GET_PUT if (yosys_xtrace) - log("#X# GET-BY-NAME '%s' (index %d, refcount %u)\n", global_id_storage_.at(it->second), it->second, global_refcount_storage_.at(it->second)); + log("#X# GET-BY-NAME '%s' (index %d, refcount %u)\n", global_id_storage_.at(it->second).buf, it->second, global_refcount_storage_.at(it->second)); #endif return it->second; } @@ -244,32 +249,31 @@ struct RTLIL::IdString if (global_free_idx_list_.empty()) { log_assert(global_id_storage_.size() < 0x40000000); global_free_idx_list_.push_back(global_id_storage_.size()); - global_id_storage_.push_back(nullptr); + global_id_storage_.push_back({nullptr, 0}); global_refcount_storage_.push_back(0); } int idx = global_free_idx_list_.back(); global_free_idx_list_.pop_back(); - char* buf = static_cast(malloc(p.size() + 1)); - memcpy(buf, p.data(), p.size()); - buf[p.size()] = 0; - global_id_storage_.at(idx) = buf; - global_id_index_.insert(it, {std::string_view(buf, p.size()), idx}); global_refcount_storage_.at(idx)++; #else int idx = global_id_storage_.size(); - global_id_storage_.push_back(strdup(p)); - global_id_index_[global_id_storage_.back()] = idx; + global_id_storage_.push_back({nullptr, 0}); #endif + char* buf = static_cast(malloc(p.size() + 1)); + memcpy(buf, p.data(), p.size()); + buf[p.size()] = 0; + global_id_storage_.at(idx) = {buf, GetSize(p)}; + global_id_index_.insert(it, {std::string_view(buf, p.size()), idx}); if (yosys_xtrace) { - log("#X# New IdString '%s' with index %d.\n", global_id_storage_.at(idx), idx); + log("#X# New IdString '%s' with index %d.\n", global_id_storage_.at(idx).buf, idx); log_backtrace("-X- ", yosys_xtrace-1); } #ifdef YOSYS_XTRACE_GET_PUT if (yosys_xtrace) - log("#X# GET-BY-NAME '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx), idx, global_refcount_storage_.at(idx)); + log("#X# GET-BY-NAME '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx).buf, idx, global_refcount_storage_.at(idx)); #endif #ifdef YOSYS_USE_STICKY_IDS @@ -294,7 +298,7 @@ struct RTLIL::IdString #ifdef YOSYS_XTRACE_GET_PUT if (yosys_xtrace) { - log("#X# PUT '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx), idx, global_refcount_storage_.at(idx)); + log("#X# PUT '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx).buf, idx, global_refcount_storage_.at(idx)); } #endif @@ -308,14 +312,14 @@ struct RTLIL::IdString static inline void free_reference(int idx) { if (yosys_xtrace) { - log("#X# Removed IdString '%s' with index %d.\n", global_id_storage_.at(idx), idx); + log("#X# Removed IdString '%s' with index %d.\n", global_id_storage_.at(idx).buf, idx); log_backtrace("-X- ", yosys_xtrace-1); } log_assert(idx >= static_cast(StaticId::STATIC_ID_END)); - global_id_index_.erase(global_id_storage_.at(idx)); - free(global_id_storage_.at(idx)); - global_id_storage_.at(idx) = nullptr; + global_id_index_.erase(global_id_storage_.at(idx).buf); + free(global_id_storage_.at(idx).buf); + global_id_storage_.at(idx) = {nullptr, 0}; global_free_idx_list_.push_back(idx); } #else @@ -359,11 +363,17 @@ struct RTLIL::IdString constexpr inline const IdString &id_string() const { return *this; } inline const char *c_str() const { - return global_id_storage_.at(index_); + return global_id_storage_.at(index_).buf; } inline std::string str() const { - return std::string(global_id_storage_.at(index_)); + const Storage &storage = global_id_storage_.at(index_); + return std::string(storage.buf, storage.size); + } + + inline std::string_view str_view() const { + const Storage &storage = global_id_storage_.at(index_); + return std::string_view(storage.buf, storage.size); } inline bool operator<(const IdString &rhs) const { @@ -395,7 +405,7 @@ struct RTLIL::IdString } std::string substr(size_t pos = 0, size_t len = std::string::npos) const { - if (len == std::string::npos || len >= strlen(c_str() + pos)) + if (len == std::string::npos || len + pos >= size()) return std::string(c_str() + pos); else return std::string(c_str() + pos, len); @@ -421,7 +431,7 @@ struct RTLIL::IdString } size_t size() const { - return strlen(c_str()); + return global_id_storage_.at(index_).size; } bool empty() const { From e84bc3c6c542f18bbc53ee21ecbbd0b1e1f9bb0d Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Thu, 16 Oct 2025 02:13:33 +0000 Subject: [PATCH 025/153] Remove explicit empty-string check when looking up IdStrings --- kernel/rtlil.cc | 3 ++- kernel/rtlil.h | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 5dac6016c..76744836d 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -64,9 +64,10 @@ void RTLIL::IdString::prepopulate() { int size = static_cast(RTLIL::StaticId::STATIC_ID_END); global_id_storage_.reserve(size); - RTLIL::IdString::global_id_storage_.push_back({const_cast(""), 0}); global_id_index_.reserve(size); global_refcount_storage_.resize(size, 1); + RTLIL::IdString::global_id_index_.insert({"", 0}); + RTLIL::IdString::global_id_storage_.push_back({const_cast(""), 0}); #define X(N) populate("\\" #N); #include "kernel/constids.inc" #undef X diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 45d696321..e005ac65e 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -237,9 +237,6 @@ struct RTLIL::IdString ensure_prepopulated(); - if (p.empty()) - return 0; - log_assert(p[0] == '$' || p[0] == '\\'); for (char ch : p) if ((unsigned)ch <= (unsigned)' ') From 0fe79ce01bc5df9cc23793875a0c9e5be05e7995 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Thu, 9 Oct 2025 22:54:26 +0000 Subject: [PATCH 026/153] Make RTLIL::Design::get_all_designs() unconditionally defined --- kernel/rtlil.cc | 6 ------ kernel/rtlil.h | 2 -- 2 files changed, 8 deletions(-) diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 76744836d..670b61afd 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -1084,9 +1084,7 @@ RTLIL::Design::Design() refcount_modules_ = 0; push_full_selection(); -#ifdef YOSYS_ENABLE_PYTHON RTLIL::Design::get_all_designs()->insert(std::pair(hashidx_, this)); -#endif } RTLIL::Design::~Design() @@ -1095,18 +1093,14 @@ RTLIL::Design::~Design() delete pr.second; for (auto n : bindings_) delete n; -#ifdef YOSYS_ENABLE_PYTHON RTLIL::Design::get_all_designs()->erase(hashidx_); -#endif } -#ifdef YOSYS_ENABLE_PYTHON static std::map all_designs; std::map *RTLIL::Design::get_all_designs(void) { return &all_designs; } -#endif RTLIL::ObjRange RTLIL::Design::modules() { diff --git a/kernel/rtlil.h b/kernel/rtlil.h index e005ac65e..fbf9292b3 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -1883,9 +1883,7 @@ struct RTLIL::Design // returns all selected unboxed whole modules, warning the user if any // partially selected or boxed modules have been ignored std::vector selected_unboxed_whole_modules_warn() const { return selected_modules(SELECT_WHOLE_WARN, SB_UNBOXED_WARN); } -#ifdef YOSYS_ENABLE_PYTHON static std::map *get_all_designs(void); -#endif }; struct RTLIL::Module : public RTLIL::NamedObject From d28f97e9da2d168406a2758a4b7cfd3d65f94159 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Thu, 9 Oct 2025 23:40:47 +0000 Subject: [PATCH 027/153] Remove YOSYS_USE_STICKY_IDS --- kernel/rtlil.cc | 4 ---- kernel/rtlil.h | 23 ----------------------- 2 files changed, 27 deletions(-) diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 670b61afd..c6358729a 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -41,10 +41,6 @@ std::unordered_map RTLIL::IdString::global_id_index_; std::vector RTLIL::IdString::global_refcount_storage_; std::vector RTLIL::IdString::global_free_idx_list_; #endif -#ifdef YOSYS_USE_STICKY_IDS -int RTLIL::IdString::last_created_idx_[8]; -int RTLIL::IdString::last_created_idx_ptr_; -#endif #define X(_id) const RTLIL::IdString RTLIL::IDInternal::_id(RTLIL::StaticId::_id); #include "kernel/constids.inc" diff --git a/kernel/rtlil.h b/kernel/rtlil.h index fbf9292b3..556f936e9 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -145,7 +145,6 @@ struct RTLIL::IdString #undef YOSYS_XTRACE_GET_PUT #undef YOSYS_SORT_ID_FREE_LIST - #undef YOSYS_USE_STICKY_IDS #undef YOSYS_NO_IDS_REFCNT // the global id string cache @@ -169,11 +168,6 @@ struct RTLIL::IdString static std::vector global_free_idx_list_; #endif -#ifdef YOSYS_USE_STICKY_IDS - static int last_created_idx_ptr_; - static int last_created_idx_[8]; -#endif - static inline void xtrace_db_dump() { #ifdef YOSYS_XTRACE_GET_PUT @@ -189,14 +183,6 @@ struct RTLIL::IdString static inline void checkpoint() { - #ifdef YOSYS_USE_STICKY_IDS - last_created_idx_ptr_ = 0; - for (int i = 0; i < 8; i++) { - if (last_created_idx_[i]) - put_reference(last_created_idx_[i]); - last_created_idx_[i] = 0; - } - #endif #ifdef YOSYS_SORT_ID_FREE_LIST std::sort(global_free_idx_list_.begin(), global_free_idx_list_.end(), std::greater()); #endif @@ -273,15 +259,6 @@ struct RTLIL::IdString log("#X# GET-BY-NAME '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx).buf, idx, global_refcount_storage_.at(idx)); #endif - #ifdef YOSYS_USE_STICKY_IDS - // Avoid Create->Delete->Create pattern - if (last_created_idx_[last_created_idx_ptr_]) - put_reference(last_created_idx_[last_created_idx_ptr_]); - last_created_idx_[last_created_idx_ptr_] = idx; - get_reference(last_created_idx_[last_created_idx_ptr_]); - last_created_idx_ptr_ = (last_created_idx_ptr_ + 1) & 7; - #endif - return idx; } From 5133b4bdea85a075842bb735b51712d16bac905e Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Thu, 9 Oct 2025 23:28:10 +0000 Subject: [PATCH 028/153] Create RTLIL::OwningIdString and use it in a few places --- kernel/rtlil.cc | 2 +- kernel/rtlil.h | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index c6358729a..6889343cd 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -42,7 +42,7 @@ std::vector RTLIL::IdString::global_refcount_storage_; std::vector RTLIL::IdString::global_free_idx_list_; #endif -#define X(_id) const RTLIL::IdString RTLIL::IDInternal::_id(RTLIL::StaticId::_id); +#define X(_id) const RTLIL::OwningIdString RTLIL::IDInternal::_id(RTLIL::StaticId::_id); #include "kernel/constids.inc" #undef X diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 556f936e9..df1559f8c 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -122,6 +122,7 @@ namespace RTLIL struct Binding; struct IdString; struct StaticIdString; + struct OwningIdString; typedef std::pair SigSig; @@ -460,6 +461,16 @@ public: } }; +struct RTLIL::OwningIdString : public RTLIL::IdString { + inline OwningIdString() { } + inline OwningIdString(const char *str) : IdString(str) { } + inline OwningIdString(const IdString &str) : IdString(str) { } + inline OwningIdString(IdString &&str) : IdString(str) { } + inline OwningIdString(const std::string &str) : IdString(str) { } + inline OwningIdString(std::string_view str) : IdString(str) { } + inline OwningIdString(StaticId id) : IdString(id) { } +}; + namespace hashlib { template <> struct hash_ops { @@ -494,7 +505,7 @@ inline bool RTLIL::IdString::operator!=(const RTLIL::StaticIdString &rhs) const namespace RTLIL { namespace IDInternal { -#define X(_id) extern const IdString _id; +#define X(_id) extern const OwningIdString _id; #include "kernel/constids.inc" #undef X } From 54bde15329c961c29b53585dbbcf0fab35120935 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Fri, 10 Oct 2025 01:10:33 +0000 Subject: [PATCH 029/153] Implement IdString garbage collection instead of refcounting. --- kernel/register.cc | 30 +++++- kernel/register.h | 30 ++++++ kernel/rtlil.cc | 122 +++++++++++++++++++++++ kernel/rtlil.h | 170 ++++++++++++++++----------------- passes/opt/opt_clean.cc | 4 + tests/unit/kernel/rtlilTest.cc | 6 ++ 6 files changed, 272 insertions(+), 90 deletions(-) diff --git a/kernel/register.cc b/kernel/register.cc index bd12dcc38..1f8e4a9a5 100644 --- a/kernel/register.cc +++ b/kernel/register.cc @@ -42,6 +42,23 @@ std::map backend_register; std::vector Frontend::next_args; +bool GarbageCollectionGuard::is_enabled_ = true; + +static bool garbage_collection_requested = false; + +void request_garbage_collection() +{ + garbage_collection_requested = true; +} + +void try_collect_garbage() +{ + if (!GarbageCollectionGuard::is_enabled() || !garbage_collection_requested) + return; + garbage_collection_requested = false; + RTLIL::OwningIdString::collect_garbage(); +} + Pass::Pass(std::string name, std::string short_help, source_location location) : pass_name(name), short_help(short_help), location(location) { @@ -263,14 +280,19 @@ void Pass::call(RTLIL::Design *design, std::vector args) if (pass_register.count(args[0]) == 0) log_cmd_error("No such command: %s (type 'help' for a command overview)\n", args[0]); + Pass *pass = pass_register[args[0]]; - if (pass_register[args[0]]->experimental_flag) + // Collect garbage before the next pass if requested. No need to collect garbage after the last pass. + try_collect_garbage(); + GarbageCollectionGuard gc_guard(pass->allow_garbage_collection_during_pass()); + + if (pass->experimental_flag) log_experimental(args[0]); size_t orig_sel_stack_pos = design->selection_stack.size(); - auto state = pass_register[args[0]]->pre_execute(); - pass_register[args[0]]->execute(args, design); - pass_register[args[0]]->post_execute(state); + auto state = pass->pre_execute(); + pass->execute(args, design); + pass->post_execute(state); while (design->selection_stack.size() > orig_sel_stack_pos) design->pop_selection(); } diff --git a/kernel/register.h b/kernel/register.h index 534cfbc28..b9c709dc1 100644 --- a/kernel/register.h +++ b/kernel/register.h @@ -50,6 +50,30 @@ struct source_location { // dummy placeholder YOSYS_NAMESPACE_BEGIN +// Track whether garbage collection is enabled. Garbage collection must be disabled +// while any RTLIL objects (e.g. non-owning non-immortal IdStrings) exist outside Designs. +// Garbage collection is disabled whenever any GarbageCollectionGuard(false) is on the +// stack. These objects must be stack-allocated on the main thread. +class GarbageCollectionGuard +{ + bool was_enabled; + static bool is_enabled_; +public: + GarbageCollectionGuard(bool allow) : was_enabled(is_enabled_) { + is_enabled_ &= allow; + } + ~GarbageCollectionGuard() { + is_enabled_ = was_enabled; + } + static bool is_enabled() { return is_enabled_; } +}; + +// Call from anywhere to request GC at the next safe point. +void request_garbage_collection(); + +// GC if GarbageCollectionGuard::is_enabled() and GC was requested. +void try_collect_garbage(); + struct Pass { std::string pass_name, short_help; @@ -108,6 +132,10 @@ struct Pass virtual void on_register(); virtual void on_shutdown(); virtual bool replace_existing_pass() const { return false; } + + // This should return false if the pass holds onto RTLIL objects outside a Design while it + // calls nested passes. For safety, we default to assuming the worst. + virtual bool allow_garbage_collection_during_pass() const { return false; } }; struct ScriptPass : Pass @@ -126,6 +154,8 @@ struct ScriptPass : Pass void run_nocheck(std::string command, std::string info = std::string()); void run_script(RTLIL::Design *design, std::string run_from = std::string(), std::string run_to = std::string()); void help_script(); + + bool allow_garbage_collection_during_pass() const override { return true; } }; struct Frontend : Pass diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 6889343cd..0e974b141 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -82,6 +82,128 @@ static constexpr bool check_well_known_id_order() // and in sorted ascii order, as required by the ID macro. static_assert(check_well_known_id_order()); +struct IdStringCollector { + IdStringCollector(int size) : live(size, false) {} + + void trace(IdString id) { live[id.index_] = true; } + template void trace(const T* v) { + trace(*v); + } + template void trace(const std::vector &v) { + for (const auto &element : v) + trace(element); + } + template void trace(const pool &p) { + for (const auto &element : p) + trace(element); + } + template void trace(const dict &d) { + for (const auto &[key, value] : d) { + trace(key); + trace(value); + } + } + template void trace_keys(const dict &d) { + for (const auto &[key, value] : d) { + trace(key); + } + } + template void trace_values(const dict &d) { + for (const auto &[key, value] : d) { + trace(value); + } + } + template void trace(const idict &d) { + for (const auto &element : d) + trace(element); + } + + void trace(const RTLIL::Design &design) { + trace_values(design.modules_); + trace(design.selection_vars); + } + void trace(const RTLIL::Selection &selection_var) { + trace(selection_var.selected_modules); + trace(selection_var.selected_members); + } + void trace_named(const RTLIL::NamedObject named) { + trace_keys(named.attributes); + trace(named.name); + } + void trace(const RTLIL::Module &module) { + trace_named(module); + trace_values(module.wires_); + trace_values(module.cells_); + trace(module.avail_parameters); + trace_keys(module.parameter_default_values); + trace_values(module.memories); + trace_values(module.processes); + } + void trace(const RTLIL::Wire &wire) { + trace_named(wire); + if (wire.known_driver()) + trace(wire.driverPort()); + } + void trace(const RTLIL::Cell &cell) { + trace_named(cell); + trace(cell.type); + trace_keys(cell.connections_); + trace_keys(cell.parameters); + } + void trace(const RTLIL::Memory &mem) { + trace_named(mem); + } + void trace(const RTLIL::Process &proc) { + trace_named(proc); + trace(proc.root_case); + trace(proc.syncs); + } + void trace(const RTLIL::CaseRule &rule) { + trace_keys(rule.attributes); + trace(rule.switches); + } + void trace(const RTLIL::SwitchRule &rule) { + trace_keys(rule.attributes); + trace(rule.cases); + } + void trace(const RTLIL::SyncRule &rule) { + trace(rule.mem_write_actions); + } + void trace(const RTLIL::MemWriteAction &action) { + trace_keys(action.attributes); + trace(action.memid); + } + + std::vector live; +}; + +void RTLIL::OwningIdString::collect_garbage() +{ +#ifndef YOSYS_NO_IDS_REFCNT + int size = GetSize(global_refcount_storage_); + IdStringCollector collector(size); + for (auto &[idx, design] : *RTLIL::Design::get_all_designs()) { + collector.trace(*design); + } + for (int i = static_cast(StaticId::STATIC_ID_END); i < size; ++i) { + if (collector.live[i] || global_refcount_storage_[i] > 0) + continue; + RTLIL::IdString::Storage &storage = global_id_storage_.at(i); + if (storage.buf == nullptr) + continue; + if (yosys_xtrace) { + log("#X# Removed IdString '%s' with index %d.\n", storage.buf, i); + log_backtrace("-X- ", yosys_xtrace-1); + } + + global_id_index_.erase(std::string_view(storage.buf, storage.size)); + free(storage.buf); + storage = {nullptr, 0}; + global_free_idx_list_.push_back(i); + } +#endif +} + dict RTLIL::constpad; static const pool &builtin_ff_cell_types_internal() { diff --git a/kernel/rtlil.h b/kernel/rtlil.h index df1559f8c..9d3919162 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -127,12 +127,12 @@ namespace RTLIL typedef std::pair SigSig; struct StaticIdString { - constexpr StaticIdString(StaticId id, const IdString &id_str) : id_str(id_str), id(id) {} - constexpr inline operator const IdString &() const { return id_str; } + constexpr StaticIdString(StaticId id, const OwningIdString &id_str) : id_str(id_str), id(id) {} + constexpr inline operator const OwningIdString &() const { return id_str; } constexpr inline int index() const { return static_cast(id); } - constexpr inline const IdString &id_string() const { return id_str; } + constexpr inline const OwningIdString &id_string() const { return id_str; } - const IdString &id_str; + const OwningIdString &id_str; const StaticId id; }; }; @@ -189,32 +189,12 @@ struct RTLIL::IdString #endif } - static inline int get_reference(int idx) - { - #ifndef YOSYS_NO_IDS_REFCNT - global_refcount_storage_[idx]++; - #endif - #ifdef YOSYS_XTRACE_GET_PUT - if (yosys_xtrace && idx >= static_cast(StaticId::STATIC_ID_END)) - log("#X# GET-BY-INDEX '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx).buf, idx, global_refcount_storage_.at(idx)); - #endif - return idx; - } - - static int get_reference(const char *p) - { - return get_reference(std::string_view(p)); - } - - static int get_reference(std::string_view p) + static int insert(std::string_view p) { log_assert(destruct_guard_ok); auto it = global_id_index_.find(p); if (it != global_id_index_.end()) { - #ifndef YOSYS_NO_IDS_REFCNT - global_refcount_storage_.at(it->second)++; - #endif #ifdef YOSYS_XTRACE_GET_PUT if (yosys_xtrace) log("#X# GET-BY-NAME '%s' (index %d, refcount %u)\n", global_id_storage_.at(it->second).buf, it->second, global_refcount_storage_.at(it->second)); @@ -239,7 +219,6 @@ struct RTLIL::IdString int idx = global_free_idx_list_.back(); global_free_idx_list_.pop_back(); - global_refcount_storage_.at(idx)++; #else int idx = global_id_storage_.size(); global_id_storage_.push_back({nullptr, 0}); @@ -263,67 +242,19 @@ struct RTLIL::IdString return idx; } -#ifndef YOSYS_NO_IDS_REFCNT - static inline void put_reference(int idx) - { - // put_reference() may be called from destructors after the destructor of - // global_refcount_storage_ has been run. in this case we simply do nothing. - if (idx < static_cast(StaticId::STATIC_ID_END) || !destruct_guard_ok) - return; - - #ifdef YOSYS_XTRACE_GET_PUT - if (yosys_xtrace) { - log("#X# PUT '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx).buf, idx, global_refcount_storage_.at(idx)); - } - #endif - - uint32_t &refcount = global_refcount_storage_[idx]; - - if (--refcount > 0) - return; - - free_reference(idx); - } - static inline void free_reference(int idx) - { - if (yosys_xtrace) { - log("#X# Removed IdString '%s' with index %d.\n", global_id_storage_.at(idx).buf, idx); - log_backtrace("-X- ", yosys_xtrace-1); - } - log_assert(idx >= static_cast(StaticId::STATIC_ID_END)); - - global_id_index_.erase(global_id_storage_.at(idx).buf); - free(global_id_storage_.at(idx).buf); - global_id_storage_.at(idx) = {nullptr, 0}; - global_free_idx_list_.push_back(idx); - } -#else - static inline void put_reference(int) { } -#endif - // the actual IdString object is just is a single int int index_; inline IdString() : index_(0) { } - inline IdString(const char *str) : index_(get_reference(str)) { } - inline IdString(const IdString &str) : index_(get_reference(str.index_)) { } + inline IdString(const char *str) : index_(insert(std::string_view(str))) { } + inline IdString(const IdString &str) : index_(str.index_) { } inline IdString(IdString &&str) : index_(str.index_) { str.index_ = 0; } - inline IdString(const std::string &str) : index_(get_reference(std::string_view(str))) { } - inline IdString(std::string_view str) : index_(get_reference(str)) { } + inline IdString(const std::string &str) : index_(insert(std::string_view(str))) { } + inline IdString(std::string_view str) : index_(insert(str)) { } inline IdString(StaticId id) : index_(static_cast(id)) {} - inline ~IdString() { put_reference(index_); } - inline void operator=(const IdString &rhs) { - put_reference(index_); - index_ = get_reference(rhs.index_); - } - - inline void operator=(IdString &&rhs) { - put_reference(index_); - index_ = rhs.index_; - rhs.index_ = 0; - } + IdString &operator=(const IdString &rhs) = default; inline void operator=(const char *rhs) { IdString id(rhs); @@ -463,12 +394,78 @@ public: struct RTLIL::OwningIdString : public RTLIL::IdString { inline OwningIdString() { } - inline OwningIdString(const char *str) : IdString(str) { } - inline OwningIdString(const IdString &str) : IdString(str) { } - inline OwningIdString(IdString &&str) : IdString(str) { } - inline OwningIdString(const std::string &str) : IdString(str) { } - inline OwningIdString(std::string_view str) : IdString(str) { } - inline OwningIdString(StaticId id) : IdString(id) { } + inline OwningIdString(const StaticIdString &str) : IdString(str) { } + inline OwningIdString(const OwningIdString &str) : IdString(str) { get_reference(); } + inline OwningIdString(const char *str) : IdString(str) { get_reference(); } + inline OwningIdString(const IdString &str) : IdString(str) { get_reference(); } + inline OwningIdString(IdString &&str) : IdString(str) { get_reference(); } + inline OwningIdString(const std::string &str) : IdString(str) { get_reference(); } + inline OwningIdString(std::string_view str) : IdString(str) { get_reference(); } + inline OwningIdString(StaticId id) : IdString(id) {} + inline ~OwningIdString() { + put_reference(); + } + + inline OwningIdString &operator=(const OwningIdString &rhs) { + put_reference(); + index_ = rhs.index_; + get_reference(); + return *this; + } + inline OwningIdString &operator=(const IdString &rhs) { + put_reference(); + index_ = rhs.index_; + get_reference(); + return *this; + } + inline OwningIdString &operator=(OwningIdString &&rhs) { + std::swap(index_, rhs.index_); + return *this; + } + + // Collect all non-owning references. + static void collect_garbage(); + + // Used by the ID() macro to create an IdString with no destructor whose string will + // never be released. If ID() creates a closure-static `OwningIdString` then + // initialization of the static registers its destructor to run at exit, which is + // wasteful. + static IdString immortal(const char* str) { + IdString result(str); + get_reference(result.index_); + return result; + } +private: + void get_reference() + { + get_reference(index_); + } + static void get_reference(int idx) + { + #ifndef YOSYS_NO_IDS_REFCNT + global_refcount_storage_[idx]++; + #endif + #ifdef YOSYS_XTRACE_GET_PUT + if (yosys_xtrace && idx >= static_cast(StaticId::STATIC_ID_END)) + log("#X# GET-BY-INDEX '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx), idx, global_refcount_storage_.at(idx)); + #endif + } + + void put_reference() + { + #ifndef YOSYS_NO_IDS_REFCNT + // put_reference() may be called from destructors after the destructor of + // global_refcount_storage_ has been run. in this case we simply do nothing. + if (index_ < static_cast(StaticId::STATIC_ID_END) || !destruct_guard_ok) + return; + #ifdef YOSYS_XTRACE_GET_PUT + if (yosys_xtrace) { + log("#X# PUT '%s' (index %d, refcount %u)\n", global_id_storage_.at(index_), index_, global_refcount_storage_.at(index_)); + } + #endif + --global_refcount_storage_[index_]; + #endif + } }; namespace hashlib { @@ -571,7 +568,8 @@ template <> struct IDMacroHelper<-1> { >::eval([]() \ -> const YOSYS_NAMESPACE_PREFIX RTLIL::IdString & { \ const char *p = "\\" #_id, *q = p[1] == '$' ? p+1 : p; \ - static const YOSYS_NAMESPACE_PREFIX RTLIL::IdString id(q); \ + static const YOSYS_NAMESPACE_PREFIX RTLIL::IdString id = \ + YOSYS_NAMESPACE_PREFIX RTLIL::OwningIdString::immortal(q); \ return id; \ }) diff --git a/passes/opt/opt_clean.cc b/passes/opt/opt_clean.cc index 996a9b3c9..b3e3cd33a 100644 --- a/passes/opt/opt_clean.cc +++ b/passes/opt/opt_clean.cc @@ -722,6 +722,8 @@ struct OptCleanPass : public Pass { ct_reg.clear(); ct_all.clear(); log_pop(); + + request_garbage_collection(); } } OptCleanPass; @@ -784,6 +786,8 @@ struct CleanPass : public Pass { keep_cache.reset(); ct_reg.clear(); ct_all.clear(); + + request_garbage_collection(); } } CleanPass; diff --git a/tests/unit/kernel/rtlilTest.cc b/tests/unit/kernel/rtlilTest.cc index 557355ed9..8bba9ac28 100644 --- a/tests/unit/kernel/rtlilTest.cc +++ b/tests/unit/kernel/rtlilTest.cc @@ -361,6 +361,12 @@ namespace RTLIL { EXPECT_FALSE(Const().is_onehot(&pos)); } + TEST_F(KernelRtlilTest, OwningIdString) { + OwningIdString own("\\figblortle"); + OwningIdString::collect_garbage(); + EXPECT_EQ(own.str(), "\\figblortle"); + } + class WireRtlVsHdlIndexConversionTest : public KernelRtlilTest, public testing::WithParamInterface> From b0e2d75dbe26b98b2bf8a0d9702c422aa23f21f6 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Mon, 13 Oct 2025 00:12:51 +0000 Subject: [PATCH 030/153] Make IdString refcounts a hashtable containing only the nonzero refcounts This saves space and doesn't cost very much since we hardly ever have nonzero refcounts any more. It also allows for IdStrings with negative indexes, which we're going to add. --- kernel/rtlil.cc | 10 ++++++---- kernel/rtlil.h | 44 +++++++++++++++++++++++++++----------------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 0e974b141..466ed0a74 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -38,7 +38,7 @@ RTLIL::IdString::destruct_guard_t RTLIL::IdString::destruct_guard; std::vector RTLIL::IdString::global_id_storage_; std::unordered_map RTLIL::IdString::global_id_index_; #ifndef YOSYS_NO_IDS_REFCNT -std::vector RTLIL::IdString::global_refcount_storage_; +std::unordered_map RTLIL::IdString::global_refcount_storage_; std::vector RTLIL::IdString::global_free_idx_list_; #endif @@ -61,7 +61,6 @@ void RTLIL::IdString::prepopulate() int size = static_cast(RTLIL::StaticId::STATIC_ID_END); global_id_storage_.reserve(size); global_id_index_.reserve(size); - global_refcount_storage_.resize(size, 1); RTLIL::IdString::global_id_index_.insert({"", 0}); RTLIL::IdString::global_id_storage_.push_back({const_cast(""), 0}); #define X(N) populate("\\" #N); @@ -180,17 +179,20 @@ struct IdStringCollector { void RTLIL::OwningIdString::collect_garbage() { #ifndef YOSYS_NO_IDS_REFCNT - int size = GetSize(global_refcount_storage_); + int size = GetSize(global_id_storage_); IdStringCollector collector(size); for (auto &[idx, design] : *RTLIL::Design::get_all_designs()) { collector.trace(*design); } for (int i = static_cast(StaticId::STATIC_ID_END); i < size; ++i) { - if (collector.live[i] || global_refcount_storage_[i] > 0) + if (collector.live[i]) continue; RTLIL::IdString::Storage &storage = global_id_storage_.at(i); if (storage.buf == nullptr) continue; + if (global_refcount_storage_.find(i) != global_refcount_storage_.end()) + continue; + if (yosys_xtrace) { log("#X# Removed IdString '%s' with index %d.\n", storage.buf, i); log_backtrace("-X- ", yosys_xtrace-1); diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 9d3919162..a081762c9 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -159,16 +159,18 @@ struct RTLIL::IdString static std::vector global_id_storage_; static std::unordered_map global_id_index_; #ifndef YOSYS_NO_IDS_REFCNT - // For prepopulated IdStrings, the refcount is meaningless since they - // are never freed even if the refcount is zero. For code efficiency - // we increment the refcount of prepopulated IdStrings like any other string, - // but we never decrement the refcount or check whether it's zero. - // So, make this unsigned because refcounts of preopulated IdStrings may overflow - // and overflow of signed integers is undefined behavior. - static std::vector global_refcount_storage_; + // All (index, refcount) pairs in this map have refcount > 0. + static std::unordered_map global_refcount_storage_; static std::vector global_free_idx_list_; #endif + static int refcount(int idx) { + auto it = global_refcount_storage_.find(idx); + if (it == global_refcount_storage_.end()) + return 0; + return it->second; + } + static inline void xtrace_db_dump() { #ifdef YOSYS_XTRACE_GET_PUT @@ -177,7 +179,7 @@ struct RTLIL::IdString if (global_id_storage_.at(idx).buf == nullptr) log("#X# DB-DUMP index %d: FREE\n", idx); else - log("#X# DB-DUMP index %d: '%s' (ref %u)\n", idx, global_id_storage_.at(idx).buf, global_refcount_storage_.at(idx)); + log("#X# DB-DUMP index %d: '%s' (ref %u)\n", idx, refcount(idx).buf, refcount); } #endif } @@ -197,7 +199,7 @@ struct RTLIL::IdString if (it != global_id_index_.end()) { #ifdef YOSYS_XTRACE_GET_PUT if (yosys_xtrace) - log("#X# GET-BY-NAME '%s' (index %d, refcount %u)\n", global_id_storage_.at(it->second).buf, it->second, global_refcount_storage_.at(it->second)); + log("#X# GET-BY-NAME '%s' (index %d, refcount %u)\n", global_id_storage_.at(it->second).buf, it->second, refcount(it->second)); #endif return it->second; } @@ -214,7 +216,6 @@ struct RTLIL::IdString log_assert(global_id_storage_.size() < 0x40000000); global_free_idx_list_.push_back(global_id_storage_.size()); global_id_storage_.push_back({nullptr, 0}); - global_refcount_storage_.push_back(0); } int idx = global_free_idx_list_.back(); @@ -236,7 +237,7 @@ struct RTLIL::IdString #ifdef YOSYS_XTRACE_GET_PUT if (yosys_xtrace) - log("#X# GET-BY-NAME '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx).buf, idx, global_refcount_storage_.at(idx)); + log("#X# GET-BY-NAME '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx).buf, idx, refcount(idx)); #endif return idx; @@ -443,11 +444,17 @@ private: static void get_reference(int idx) { #ifndef YOSYS_NO_IDS_REFCNT - global_refcount_storage_[idx]++; + if (idx < static_cast(StaticId::STATIC_ID_END)) + return; + auto it = global_refcount_storage_.find(idx); + if (it == global_refcount_storage_.end()) + global_refcount_storage_.insert(it, {idx, 1}); + else + ++it->second; #endif #ifdef YOSYS_XTRACE_GET_PUT if (yosys_xtrace && idx >= static_cast(StaticId::STATIC_ID_END)) - log("#X# GET-BY-INDEX '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx), idx, global_refcount_storage_.at(idx)); + log("#X# GET-BY-INDEX '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx), idx, refcount(idx)); #endif } @@ -459,11 +466,14 @@ private: if (index_ < static_cast(StaticId::STATIC_ID_END) || !destruct_guard_ok) return; #ifdef YOSYS_XTRACE_GET_PUT - if (yosys_xtrace) { - log("#X# PUT '%s' (index %d, refcount %u)\n", global_id_storage_.at(index_), index_, global_refcount_storage_.at(index_)); - } + if (yosys_xtrace) + log("#X# PUT '%s' (index %d, refcount %u)\n", global_id_storage_.at(index_), index_, refcount(index_)); #endif - --global_refcount_storage_[index_]; + auto it = global_refcount_storage_.find(index_); + log_assert(it != global_refcount_storage_.end() && it->second >= 1); + if (--it->second == 0) { + global_refcount_storage_.erase(it); + } #endif } }; From b3f3f425771c1cc0c401528b067b9b7b33983ebb Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Tue, 14 Oct 2025 01:00:20 +0000 Subject: [PATCH 031/153] Remove StaticIdString and just use IdString now that we can make it constexpr --- kernel/rtlil.cc | 4 ---- kernel/rtlil.h | 45 +++++++++------------------------------------ 2 files changed, 9 insertions(+), 40 deletions(-) diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 466ed0a74..6084dd03e 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -42,10 +42,6 @@ std::unordered_map RTLIL::IdString::global_refcount_storage_; std::vector RTLIL::IdString::global_free_idx_list_; #endif -#define X(_id) const RTLIL::OwningIdString RTLIL::IDInternal::_id(RTLIL::StaticId::_id); -#include "kernel/constids.inc" -#undef X - static void populate(std::string_view name) { if (name[1] == '$') { diff --git a/kernel/rtlil.h b/kernel/rtlil.h index a081762c9..2f7319d4a 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -121,20 +121,9 @@ namespace RTLIL struct Process; struct Binding; struct IdString; - struct StaticIdString; struct OwningIdString; typedef std::pair SigSig; - - struct StaticIdString { - constexpr StaticIdString(StaticId id, const OwningIdString &id_str) : id_str(id_str), id(id) {} - constexpr inline operator const OwningIdString &() const { return id_str; } - constexpr inline int index() const { return static_cast(id); } - constexpr inline const OwningIdString &id_string() const { return id_str; } - - const OwningIdString &id_str; - const StaticId id; - }; }; struct RTLIL::IdString @@ -247,13 +236,13 @@ struct RTLIL::IdString int index_; - inline IdString() : index_(0) { } + constexpr inline IdString() : index_(0) { } inline IdString(const char *str) : index_(insert(std::string_view(str))) { } - inline IdString(const IdString &str) : index_(str.index_) { } + constexpr inline IdString(const IdString &str) : index_(str.index_) { } inline IdString(IdString &&str) : index_(str.index_) { str.index_ = 0; } inline IdString(const std::string &str) : index_(insert(std::string_view(str))) { } inline IdString(std::string_view str) : index_(insert(str)) { } - inline IdString(StaticId id) : index_(static_cast(id)) {} + constexpr inline IdString(StaticId id) : index_(static_cast(id)) {} IdString &operator=(const IdString &rhs) = default; @@ -289,8 +278,6 @@ struct RTLIL::IdString inline bool operator==(const IdString &rhs) const { return index_ == rhs.index_; } inline bool operator!=(const IdString &rhs) const { return index_ != rhs.index_; } - inline bool operator==(const StaticIdString &rhs) const; - inline bool operator!=(const StaticIdString &rhs) const; // The methods below are just convenience functions for better compatibility with std::string. @@ -375,7 +362,6 @@ struct RTLIL::IdString } bool in(const IdString &rhs) const { return *this == rhs; } - bool in(const StaticIdString &rhs) const { return *this == rhs; } bool in(const char *rhs) const { return *this == rhs; } bool in(const std::string &rhs) const { return *this == rhs; } inline bool in(const pool &rhs) const; @@ -395,7 +381,6 @@ public: struct RTLIL::OwningIdString : public RTLIL::IdString { inline OwningIdString() { } - inline OwningIdString(const StaticIdString &str) : IdString(str) { } inline OwningIdString(const OwningIdString &str) : IdString(str) { get_reference(); } inline OwningIdString(const char *str) : IdString(str) { get_reference(); } inline OwningIdString(const IdString &str) : IdString(str) { get_reference(); } @@ -503,21 +488,9 @@ inline bool RTLIL::IdString::in(const pool &rhs) const { return rhs.co [[deprecated]] inline bool RTLIL::IdString::in(const pool &&rhs) const { return rhs.count(*this) != 0; } -inline bool RTLIL::IdString::operator==(const RTLIL::StaticIdString &rhs) const { - return index_ == rhs.index(); -} -inline bool RTLIL::IdString::operator!=(const RTLIL::StaticIdString &rhs) const { - return index_ != rhs.index(); -} - namespace RTLIL { - namespace IDInternal { -#define X(_id) extern const OwningIdString _id; -#include "kernel/constids.inc" -#undef X - } namespace ID { -#define X(_id) constexpr StaticIdString _id(StaticId::_id, IDInternal::_id); +#define X(_id) constexpr IdString _id(StaticId::_id); #include "kernel/constids.inc" #undef X } @@ -525,7 +498,7 @@ namespace RTLIL { struct IdTableEntry { const std::string_view name; - const RTLIL::StaticIdString static_id; + const RTLIL::IdString static_id; }; constexpr IdTableEntry IdTable[] = { @@ -558,15 +531,15 @@ constexpr int lookup_well_known_id(std::string_view name) // // sed -i.orig -r 's/"\\\\([a-zA-Z0-9_]+)"/ID(\1)/g; s/"(\$[a-zA-Z0-9_]+)"/ID(\1)/g;' // -typedef const RTLIL::IdString &IDMacroHelperFunc(); +typedef RTLIL::IdString IDMacroHelperFunc(); template struct IDMacroHelper { - static constexpr RTLIL::StaticIdString eval(IDMacroHelperFunc) { + static constexpr RTLIL::IdString eval(IDMacroHelperFunc) { return IdTable[IdTableIndex].static_id; } }; template <> struct IDMacroHelper<-1> { - static constexpr const RTLIL::IdString &eval(IDMacroHelperFunc func) { + static constexpr RTLIL::IdString eval(IDMacroHelperFunc func) { return func(); } }; @@ -576,7 +549,7 @@ template <> struct IDMacroHelper<-1> { YOSYS_NAMESPACE_PREFIX IDMacroHelper< \ YOSYS_NAMESPACE_PREFIX lookup_well_known_id(#_id) \ >::eval([]() \ - -> const YOSYS_NAMESPACE_PREFIX RTLIL::IdString & { \ + -> YOSYS_NAMESPACE_PREFIX RTLIL::IdString { \ const char *p = "\\" #_id, *q = p[1] == '$' ? p+1 : p; \ static const YOSYS_NAMESPACE_PREFIX RTLIL::IdString id = \ YOSYS_NAMESPACE_PREFIX RTLIL::OwningIdString::immortal(q); \ From 9577a028c8dd25669c982a82c5ea69ec7737066b Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Mon, 13 Oct 2025 00:28:49 +0000 Subject: [PATCH 032/153] Make new_id/new_id_suffix taking string_view to avoid allocating strings --- kernel/yosys.cc | 12 ++++++------ kernel/yosys_common.h | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/kernel/yosys.cc b/kernel/yosys.cc index ca1db3aa6..85de1ea72 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -295,35 +295,35 @@ void yosys_shutdown() #endif } -RTLIL::IdString new_id(std::string file, int line, std::string func) +RTLIL::IdString new_id(std::string_view file, int line, std::string_view func) { #ifdef _WIN32 size_t pos = file.find_last_of("/\\"); #else size_t pos = file.find_last_of('/'); #endif - if (pos != std::string::npos) + if (pos != std::string_view::npos) file = file.substr(pos+1); pos = func.find_last_of(':'); - if (pos != std::string::npos) + if (pos != std::string_view::npos) func = func.substr(pos+1); return stringf("$auto$%s:%d:%s$%d", file, line, func, autoidx++); } -RTLIL::IdString new_id_suffix(std::string file, int line, std::string func, std::string suffix) +RTLIL::IdString new_id_suffix(std::string_view file, int line, std::string_view func, std::string_view suffix) { #ifdef _WIN32 size_t pos = file.find_last_of("/\\"); #else size_t pos = file.find_last_of('/'); #endif - if (pos != std::string::npos) + if (pos != std::string_view::npos) file = file.substr(pos+1); pos = func.find_last_of(':'); - if (pos != std::string::npos) + if (pos != std::string_view::npos) func = func.substr(pos+1); return stringf("$auto$%s:%d:%s$%s$%d", file, line, func, suffix, autoidx++); diff --git a/kernel/yosys_common.h b/kernel/yosys_common.h index 4794b5618..08b7fdc08 100644 --- a/kernel/yosys_common.h +++ b/kernel/yosys_common.h @@ -271,8 +271,8 @@ extern int autoidx; extern int yosys_xtrace; extern bool yosys_write_versions; -RTLIL::IdString new_id(std::string file, int line, std::string func); -RTLIL::IdString new_id_suffix(std::string file, int line, std::string func, std::string suffix); +RTLIL::IdString new_id(std::string_view file, int line, std::string_view func); +RTLIL::IdString new_id_suffix(std::string_view file, int line, std::string_view func, std::string_view suffix); #define NEW_ID \ YOSYS_NAMESPACE_PREFIX new_id(__FILE__, __LINE__, __FUNCTION__) From 889575736485854fa87fb247da83a3bc12e9dd41 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Mon, 13 Oct 2025 00:44:15 +0000 Subject: [PATCH 033/153] Ensure that `new_id(_suffix)()` cannot create collisions with existing `IdString`s. --- kernel/rtlil.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 2f7319d4a..fb0849902 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -23,6 +23,7 @@ #include "kernel/yosys_common.h" #include "kernel/yosys.h" +#include #include #include @@ -200,6 +201,16 @@ struct RTLIL::IdString if ((unsigned)ch <= (unsigned)' ') log_error("Found control character or space (0x%02x) in string '%s' which is not allowed in RTLIL identifiers\n", ch, std::string(p).c_str()); + if (p.substr(0, 6) == "$auto$") { + // Ensure new_id(_suffix) will not create collisions. + size_t autoidx_pos = p.find_last_of('$'); + int p_autoidx; + std::string_view v = p.substr(autoidx_pos + 1); + if (std::from_chars(v.begin(), v.end(), p_autoidx).ec == std::errc()) { + autoidx = std::max(autoidx, p_autoidx + 1); + } + } + #ifndef YOSYS_NO_IDS_REFCNT if (global_free_idx_list_.empty()) { log_assert(global_id_storage_.size() < 0x40000000); From e95ed7bbaba71645860745eaeb88fa77bbece53c Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Mon, 13 Oct 2025 02:56:32 +0000 Subject: [PATCH 034/153] Make NEW_ID create IDs whose string allocation is delayed --- kernel/io.cc | 2 +- kernel/rtlil.cc | 104 ++++++++++++++++++++++++++++--- kernel/rtlil.h | 110 ++++++++++++++++----------------- kernel/yosys.cc | 4 +- kernel/yosys_common.h | 7 ++- pyosys/generator.py | 2 + tests/unit/kernel/rtlilTest.cc | 6 ++ 7 files changed, 165 insertions(+), 70 deletions(-) diff --git a/kernel/io.cc b/kernel/io.cc index e713cff85..4f805e43b 100644 --- a/kernel/io.cc +++ b/kernel/io.cc @@ -606,7 +606,7 @@ void format_emit_idstring(std::string &result, std::string_view spec, int *dynam { if (spec == "%s") { // Format checking will have guaranteed num_dynamic_ints == 0. - result += arg.str_view(); + arg.append_to(&result); return; } format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg.c_str()); diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 6084dd03e..91fbc7960 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -37,6 +38,8 @@ bool RTLIL::IdString::destruct_guard_ok = false; RTLIL::IdString::destruct_guard_t RTLIL::IdString::destruct_guard; std::vector RTLIL::IdString::global_id_storage_; std::unordered_map RTLIL::IdString::global_id_index_; +std::unordered_map RTLIL::IdString::global_autoidx_id_prefix_storage_; +std::unordered_map RTLIL::IdString::global_autoidx_id_storage_; #ifndef YOSYS_NO_IDS_REFCNT std::unordered_map RTLIL::IdString::global_refcount_storage_; std::vector RTLIL::IdString::global_free_idx_list_; @@ -64,6 +67,74 @@ void RTLIL::IdString::prepopulate() #undef X } +static std::optional parse_autoidx(std::string_view v) +{ + // autoidx values can never be <= 0, so there can never be a leading 0 digit. + if (v.empty() || v[0] == '0') + return std::nullopt; + for (char ch : v) { + if (ch < '0' || ch > '9') + return std::nullopt; + } + int p_autoidx; + if (std::from_chars(v.data(), v.data() + v.size(), p_autoidx).ec != std::errc()) + return std::nullopt; + return p_autoidx; +} + +int RTLIL::IdString::really_insert(std::string_view p, std::unordered_map::iterator &it) +{ + ensure_prepopulated(); + + log_assert(p[0] == '$' || p[0] == '\\'); + for (char ch : p) + if ((unsigned)ch <= (unsigned)' ') + log_error("Found control character or space (0x%02x) in string '%s' which is not allowed in RTLIL identifiers\n", ch, std::string(p).c_str()); + + if (p.substr(0, 6) == "$auto$") { + size_t autoidx_pos = p.find_last_of('$') + 1; + std::optional p_autoidx = parse_autoidx(p.substr(autoidx_pos)); + if (p_autoidx.has_value()) { + auto prefix_it = global_autoidx_id_prefix_storage_.find(-*p_autoidx); + if (prefix_it != global_autoidx_id_prefix_storage_.end() && p.substr(0, autoidx_pos) == *prefix_it->second) + return -*p_autoidx; + // Ensure NEW_ID/NEW_ID_SUFFIX will not create collisions with the ID + // we're about to create. + autoidx = std::max(autoidx, *p_autoidx + 1); + } + } + +#ifndef YOSYS_NO_IDS_REFCNT + if (global_free_idx_list_.empty()) { + log_assert(global_id_storage_.size() < 0x40000000); + global_free_idx_list_.push_back(global_id_storage_.size()); + global_id_storage_.push_back({nullptr, 0}); + } + + int idx = global_free_idx_list_.back(); + global_free_idx_list_.pop_back(); +#else + int idx = global_id_storage_.size(); + global_id_index_[global_id_storage_.back()] = idx; +#endif + char* buf = static_cast(malloc(p.size() + 1)); + memcpy(buf, p.data(), p.size()); + buf[p.size()] = 0; + global_id_storage_.at(idx) = {buf, GetSize(p)}; + global_id_index_.insert(it, {std::string_view(buf, p.size()), idx}); + + if (yosys_xtrace) { + log("#X# New IdString '%s' with index %d.\n", global_id_storage_.at(idx).buf, idx); + log_backtrace("-X- ", yosys_xtrace-1); + } + +#ifdef YOSYS_XTRACE_GET_PUT + if (yosys_xtrace) + log("#X# GET-BY-NAME '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx), idx, refcount(idx)); +#endif + return idx; +} + static constexpr bool check_well_known_id_order() { int size = sizeof(IdTable) / sizeof(IdTable[0]); @@ -78,9 +149,9 @@ static constexpr bool check_well_known_id_order() static_assert(check_well_known_id_order()); struct IdStringCollector { - IdStringCollector(int size) : live(size, false) {} - - void trace(IdString id) { live[id.index_] = true; } + void trace(IdString id) { + live.insert(id.index_); + } template void trace(const T* v) { trace(*v); } @@ -169,23 +240,23 @@ struct IdStringCollector { trace(action.memid); } - std::vector live; + std::unordered_set live; }; void RTLIL::OwningIdString::collect_garbage() { #ifndef YOSYS_NO_IDS_REFCNT - int size = GetSize(global_id_storage_); - IdStringCollector collector(size); + IdStringCollector collector; for (auto &[idx, design] : *RTLIL::Design::get_all_designs()) { collector.trace(*design); } + int size = GetSize(global_id_storage_); for (int i = static_cast(StaticId::STATIC_ID_END); i < size; ++i) { - if (collector.live[i]) - continue; RTLIL::IdString::Storage &storage = global_id_storage_.at(i); if (storage.buf == nullptr) continue; + if (collector.live.find(i) != collector.live.end()) + continue; if (global_refcount_storage_.find(i) != global_refcount_storage_.end()) continue; @@ -199,6 +270,23 @@ void RTLIL::OwningIdString::collect_garbage() storage = {nullptr, 0}; global_free_idx_list_.push_back(i); } + + for (auto it = global_autoidx_id_prefix_storage_.begin(); it != global_autoidx_id_prefix_storage_.end();) { + if (collector.live.find(it->first) != collector.live.end()) { + ++it; + continue; + } + if (global_refcount_storage_.find(it->first) != global_refcount_storage_.end()) { + ++it; + continue; + } + auto str_it = global_autoidx_id_storage_.find(it->first); + if (str_it != global_autoidx_id_storage_.end()) { + delete[] str_it->second; + global_autoidx_id_storage_.erase(str_it); + } + it = global_autoidx_id_prefix_storage_.erase(it); + } #endif } diff --git a/kernel/rtlil.h b/kernel/rtlil.h index fb0849902..7a6dad57f 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -23,7 +23,6 @@ #include "kernel/yosys_common.h" #include "kernel/yosys.h" -#include #include #include @@ -146,8 +145,16 @@ struct RTLIL::IdString ~destruct_guard_t() { destruct_guard_ok = false; } } destruct_guard; + // String storage for non-autoidx IDs static std::vector global_id_storage_; + // Lookup table for non-autoidx IDs static std::unordered_map global_id_index_; + // Shared prefix string storage for autoidx IDs, which have negative + // indices. Append the negated (i.e. positive) ID to this string to get + // the real string. The prefix strings must live forever. + static std::unordered_map global_autoidx_id_prefix_storage_; + // Explicit string storage for autoidx IDs + static std::unordered_map global_autoidx_id_storage_; #ifndef YOSYS_NO_IDS_REFCNT // All (index, refcount) pairs in this map have refcount > 0. static std::unordered_map global_refcount_storage_; @@ -193,54 +200,15 @@ struct RTLIL::IdString #endif return it->second; } + return really_insert(p, it); + } - ensure_prepopulated(); - - log_assert(p[0] == '$' || p[0] == '\\'); - for (char ch : p) - if ((unsigned)ch <= (unsigned)' ') - log_error("Found control character or space (0x%02x) in string '%s' which is not allowed in RTLIL identifiers\n", ch, std::string(p).c_str()); - - if (p.substr(0, 6) == "$auto$") { - // Ensure new_id(_suffix) will not create collisions. - size_t autoidx_pos = p.find_last_of('$'); - int p_autoidx; - std::string_view v = p.substr(autoidx_pos + 1); - if (std::from_chars(v.begin(), v.end(), p_autoidx).ec == std::errc()) { - autoidx = std::max(autoidx, p_autoidx + 1); - } - } - - #ifndef YOSYS_NO_IDS_REFCNT - if (global_free_idx_list_.empty()) { - log_assert(global_id_storage_.size() < 0x40000000); - global_free_idx_list_.push_back(global_id_storage_.size()); - global_id_storage_.push_back({nullptr, 0}); - } - - int idx = global_free_idx_list_.back(); - global_free_idx_list_.pop_back(); - #else - int idx = global_id_storage_.size(); - global_id_storage_.push_back({nullptr, 0}); - #endif - char* buf = static_cast(malloc(p.size() + 1)); - memcpy(buf, p.data(), p.size()); - buf[p.size()] = 0; - global_id_storage_.at(idx) = {buf, GetSize(p)}; - global_id_index_.insert(it, {std::string_view(buf, p.size()), idx}); - - if (yosys_xtrace) { - log("#X# New IdString '%s' with index %d.\n", global_id_storage_.at(idx).buf, idx); - log_backtrace("-X- ", yosys_xtrace-1); - } - - #ifdef YOSYS_XTRACE_GET_PUT - if (yosys_xtrace) - log("#X# GET-BY-NAME '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx).buf, idx, refcount(idx)); - #endif - - return idx; + // Inserts an ID with string `prefix + autoidx', incrementing autoidx. + // `prefix` must start with '$auto$', end with '$', and live forever. + static IdString new_autoidx_with_prefix(const std::string *prefix) { + int index = -(autoidx++); + global_autoidx_id_prefix_storage_.insert({index, prefix}); + return from_index(index); } // the actual IdString object is just is a single int @@ -270,17 +238,35 @@ struct RTLIL::IdString constexpr inline const IdString &id_string() const { return *this; } inline const char *c_str() const { - return global_id_storage_.at(index_).buf; + if (index_ >= 0) + return global_id_storage_.at(index_).buf; + auto it = global_autoidx_id_storage_.find(index_); + if (it != global_autoidx_id_storage_.end()) + return it->second; + + const std::string &prefix = *global_autoidx_id_prefix_storage_.at(index_); + std::string suffix = std::to_string(-index_); + char *c = new char[prefix.size() + suffix.size() + 1]; + memcpy(c, prefix.data(), prefix.size()); + memcpy(c + prefix.size(), suffix.c_str(), suffix.size() + 1); + global_autoidx_id_storage_.insert(it, {index_, c}); + return c; } inline std::string str() const { - const Storage &storage = global_id_storage_.at(index_); - return std::string(storage.buf, storage.size); + std::string result; + append_to(&result); + return result; } - inline std::string_view str_view() const { - const Storage &storage = global_id_storage_.at(index_); - return std::string_view(storage.buf, storage.size); + inline void append_to(std::string *out) const { + if (index_ >= 0) { + const Storage &storage = global_id_storage_.at(index_); + *out += std::string_view(storage.buf, storage.size); + return; + } + *out += *global_autoidx_id_prefix_storage_.at(index_); + *out += std::to_string(-index_); } inline bool operator<(const IdString &rhs) const { @@ -336,7 +322,9 @@ struct RTLIL::IdString } size_t size() const { - return global_id_storage_.at(index_).size; + if (index_ >= 0) + return global_id_storage_.at(index_).size; + return strlen(c_str()); } bool empty() const { @@ -382,6 +370,14 @@ struct RTLIL::IdString private: static void prepopulate(); + static int really_insert(std::string_view p, std::unordered_map::iterator &it); + +protected: + static IdString from_index(int index) { + IdString result; + result.index_ = index; + return result; + } public: static void ensure_prepopulated() { @@ -450,7 +446,7 @@ private: #endif #ifdef YOSYS_XTRACE_GET_PUT if (yosys_xtrace && idx >= static_cast(StaticId::STATIC_ID_END)) - log("#X# GET-BY-INDEX '%s' (index %d, refcount %u)\n", global_id_storage_.at(idx), idx, refcount(idx)); + log("#X# GET-BY-INDEX '%s' (index %d, refcount %u)\n", from_index(idx), idx, refcount(idx)); #endif } @@ -463,7 +459,7 @@ private: return; #ifdef YOSYS_XTRACE_GET_PUT if (yosys_xtrace) - log("#X# PUT '%s' (index %d, refcount %u)\n", global_id_storage_.at(index_), index_, refcount(index_)); + log("#X# PUT '%s' (index %d, refcount %u)\n", from_index(index_), index_, refcount(index_)); #endif auto it = global_refcount_storage_.find(index_); log_assert(it != global_refcount_storage_.end() && it->second >= 1); diff --git a/kernel/yosys.cc b/kernel/yosys.cc index 85de1ea72..bf59302f8 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -295,7 +295,7 @@ void yosys_shutdown() #endif } -RTLIL::IdString new_id(std::string_view file, int line, std::string_view func) +const std::string *create_id_prefix(std::string_view file, int line, std::string_view func) { #ifdef _WIN32 size_t pos = file.find_last_of("/\\"); @@ -309,7 +309,7 @@ RTLIL::IdString new_id(std::string_view file, int line, std::string_view func) if (pos != std::string_view::npos) func = func.substr(pos+1); - return stringf("$auto$%s:%d:%s$%d", file, line, func, autoidx++); + return new std::string(stringf("$auto$%s:%d:%s$", file, line, func)); } RTLIL::IdString new_id_suffix(std::string_view file, int line, std::string_view func, std::string_view suffix) diff --git a/kernel/yosys_common.h b/kernel/yosys_common.h index 08b7fdc08..bc8500654 100644 --- a/kernel/yosys_common.h +++ b/kernel/yosys_common.h @@ -271,11 +271,14 @@ extern int autoidx; extern int yosys_xtrace; extern bool yosys_write_versions; -RTLIL::IdString new_id(std::string_view file, int line, std::string_view func); +const std::string *create_id_prefix(std::string_view file, int line, std::string_view func); RTLIL::IdString new_id_suffix(std::string_view file, int line, std::string_view func, std::string_view suffix); #define NEW_ID \ - YOSYS_NAMESPACE_PREFIX new_id(__FILE__, __LINE__, __FUNCTION__) + YOSYS_NAMESPACE_PREFIX RTLIL::IdString::new_autoidx_with_prefix([](std::string_view func) -> const std::string * { \ + static const std::string *prefix = create_id_prefix(__FILE__, __LINE__, func); \ + return prefix; \ + }(__FUNCTION__)) #define NEW_ID_SUFFIX(suffix) \ YOSYS_NAMESPACE_PREFIX new_id_suffix(__FILE__, __LINE__, __FUNCTION__, suffix) diff --git a/pyosys/generator.py b/pyosys/generator.py index 0883a7ba4..f89819504 100644 --- a/pyosys/generator.py +++ b/pyosys/generator.py @@ -164,6 +164,8 @@ pyosys_headers = [ { "global_id_storage_", "global_id_index_", + "global_negative_id_storage_", + "global_negative_id_prefix_storage_", "global_refcount_storage_", "global_free_idx_list_", "last_created_idx_ptr_", diff --git a/tests/unit/kernel/rtlilTest.cc b/tests/unit/kernel/rtlilTest.cc index 8bba9ac28..a5923e02c 100644 --- a/tests/unit/kernel/rtlilTest.cc +++ b/tests/unit/kernel/rtlilTest.cc @@ -367,6 +367,12 @@ namespace RTLIL { EXPECT_EQ(own.str(), "\\figblortle"); } + TEST_F(KernelRtlilTest, LookupAutoidxId) { + IdString id = NEW_ID; + IdString id2 = IdString(id.str()); + EXPECT_EQ(id, id2); + } + class WireRtlVsHdlIndexConversionTest : public KernelRtlilTest, public testing::WithParamInterface> From df8444c5e73de481f7129dbfc7220d1d525cbb9f Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Mon, 13 Oct 2025 20:51:35 +0000 Subject: [PATCH 035/153] Optimize IdString operations to avoid calling c_str() --- kernel/rtlil.h | 203 +++++++++++++++++++++++++++++---- tests/unit/kernel/rtlilTest.cc | 18 +++ 2 files changed, 200 insertions(+), 21 deletions(-) diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 7a6dad57f..de80a9c60 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -131,6 +131,8 @@ struct RTLIL::IdString struct Storage { char *buf; int size; + + std::string_view str_view() const { return {buf, static_cast(size)}; } }; #undef YOSYS_XTRACE_GET_PUT @@ -261,14 +263,135 @@ struct RTLIL::IdString inline void append_to(std::string *out) const { if (index_ >= 0) { - const Storage &storage = global_id_storage_.at(index_); - *out += std::string_view(storage.buf, storage.size); + *out += global_id_storage_.at(index_).str_view(); return; } *out += *global_autoidx_id_prefix_storage_.at(index_); *out += std::to_string(-index_); } + class Substrings { + std::string_view first_; + int suffix_number; + char buf[10]; + public: + Substrings(const Storage &storage) : first_(storage.str_view()), suffix_number(-1) {} + // suffix_number must be non-negative + Substrings(const std::string *prefix, int suffix_number) + : first_(*prefix), suffix_number(suffix_number) {} + std::string_view first() { return first_; } + std::optional next() { + if (suffix_number < 0) + return std::nullopt; + int i = sizeof(buf); + do { + --i; + buf[i] = (suffix_number % 10) + '0'; + suffix_number /= 10; + } while (suffix_number > 0); + suffix_number = -1; + return std::string_view(buf + i, sizeof(buf) - i); + } + }; + + class const_iterator { + const std::string *prefix; + std::string suffix; + const char *c_str; + int c_str_len; + // When this is INT_MAX it's the generic "end" value. + int index; + + public: + using iterator_category = std::forward_iterator_tag; + using value_type = char; + using difference_type = std::ptrdiff_t; + using pointer = const char*; + using reference = const char&; + + const_iterator(const Storage &storage) : prefix(nullptr), c_str(storage.buf), c_str_len(storage.size), index(0) {} + const_iterator(const std::string *prefix, int number) : + prefix(prefix), suffix(std::to_string(number)), c_str(nullptr), c_str_len(0), index(0) {} + // Construct end-marker + const_iterator() : prefix(nullptr), c_str(nullptr), c_str_len(0), index(INT_MAX) {} + + int size() const { + if (c_str != nullptr) + return c_str_len; + return GetSize(*prefix) + GetSize(suffix); + } + + char operator*() const { + if (c_str != nullptr) + return c_str[index]; + int prefix_size = GetSize(*prefix); + if (index < prefix_size) + return prefix->at(index); + return suffix[index - prefix_size]; + } + + const_iterator& operator++() { ++index; return *this; } + const_iterator operator++(int) { const_iterator result(*this); ++index; return result; } + const_iterator& operator+=(int i) { index += i; return *this; } + + const_iterator operator+(int add) { + const_iterator result = *this; + result += add; + return result; + } + + bool operator==(const const_iterator& other) const { + return index == other.index || (other.index == INT_MAX && index == size()) + || (index == INT_MAX && other.index == other.size()); + } + bool operator!=(const const_iterator& other) const { + return !(*this == other); + } + }; + const_iterator begin() const { + if (index_ >= 0) { + return const_iterator(global_id_storage_.at(index_)); + } + return const_iterator(global_autoidx_id_prefix_storage_.at(index_), -index_); + } + const_iterator end() const { + return const_iterator(); + } + + Substrings substrings() const { + if (index_ >= 0) { + return Substrings(global_id_storage_.at(index_)); + } + return Substrings(global_autoidx_id_prefix_storage_.at(index_), -index_); + } + + inline bool lt_by_name(const IdString &rhs) const { + Substrings lhs_it = substrings(); + Substrings rhs_it = rhs.substrings(); + std::string_view lhs_substr = lhs_it.first(); + std::string_view rhs_substr = rhs_it.first(); + while (true) { + int min = std::min(GetSize(lhs_substr), GetSize(rhs_substr)); + int diff = memcmp(lhs_substr.data(), rhs_substr.data(), min); + if (diff != 0) + return diff < 0; + lhs_substr = lhs_substr.substr(min); + rhs_substr = rhs_substr.substr(min); + if (rhs_substr.empty()) { + if (std::optional s = rhs_it.next()) + rhs_substr = *s; + else + return false; + } + if (lhs_substr.empty()) { + if (std::optional s = lhs_it.next()) + lhs_substr = *s; + else + return true; + } + } + } + inline bool operator<(const IdString &rhs) const { return index_ < rhs.index_; } @@ -285,30 +408,68 @@ struct RTLIL::IdString bool operator!=(const char *rhs) const { return strcmp(c_str(), rhs) != 0; } char operator[](size_t i) const { - const char *p = c_str(); + if (index_ >= 0) { + const Storage &storage = global_id_storage_.at(index_); #ifndef NDEBUG - for (; i != 0; i--, p++) - log_assert(*p != 0); - return *p; -#else - return *(p + i); + log_assert(static_cast(i) < storage.size); #endif + return *(storage.buf + i); + } + const std::string &id_start = *global_autoidx_id_prefix_storage_.at(index_); + if (i < id_start.size()) + return id_start[i]; + i -= id_start.size(); + std::string suffix = std::to_string(-index_); +#ifndef NDEBUG + // Allow indexing to access the trailing null. + log_assert(i <= suffix.size()); +#endif + return suffix[i]; } std::string substr(size_t pos = 0, size_t len = std::string::npos) const { - if (len == std::string::npos || len + pos >= size()) - return std::string(c_str() + pos); - else - return std::string(c_str() + pos, len); + std::string result; + const_iterator it = begin() + pos; + const_iterator end_it = end(); + if (len != std::string::npos && len < it.size() - pos) { + end_it = it + len; + } + std::copy(it, end_it, std::back_inserter(result)); + return result; } int compare(size_t pos, size_t len, const char* s) const { - return strncmp(c_str()+pos, s, len); + const_iterator it = begin() + pos; + const_iterator end_it = end(); + while (len > 0 && *s != 0 && it != end_it) { + int diff = *it - *s; + if (diff != 0) + return diff; + ++it; + ++s; + --len; + } + return 0; } bool begins_with(std::string_view prefix) const { - if (size() < prefix.size()) return false; - return compare(0, prefix.size(), prefix.data()) == 0; + Substrings it = substrings(); + std::string_view substr = it.first(); + while (true) { + int min = std::min(GetSize(substr), GetSize(prefix)); + if (memcmp(substr.data(), prefix.data(), min) != 0) + return false; + prefix = prefix.substr(min); + if (prefix.empty()) + return true; + substr = substr.substr(min); + if (substr.empty()) { + if (std::optional s = it.next()) + substr = *s; + else + return false; + } + } } bool ends_with(std::string_view suffix) const { @@ -318,13 +479,13 @@ struct RTLIL::IdString } bool contains(std::string_view s) const { - return std::string_view(c_str()).find(s) != std::string::npos; + if (index_ >= 0) + return global_id_storage_.at(index_).str_view().find(s) != std::string::npos; + return str().find(s) != std::string::npos; } size_t size() const { - if (index_ >= 0) - return global_id_storage_.at(index_).size; - return strlen(c_str()); + return begin().size(); } bool empty() const { @@ -603,13 +764,13 @@ namespace RTLIL { template struct sort_by_name_str { bool operator()(T *a, T *b) const { - return strcmp(a->name.c_str(), b->name.c_str()) < 0; + return a->name.lt_by_name(b->name); } }; struct sort_by_id_str { bool operator()(const RTLIL::IdString &a, const RTLIL::IdString &b) const { - return strcmp(a.c_str(), b.c_str()) < 0; + return a.lt_by_name(b); } }; diff --git a/tests/unit/kernel/rtlilTest.cc b/tests/unit/kernel/rtlilTest.cc index a5923e02c..32c40616e 100644 --- a/tests/unit/kernel/rtlilTest.cc +++ b/tests/unit/kernel/rtlilTest.cc @@ -373,6 +373,24 @@ namespace RTLIL { EXPECT_EQ(id, id2); } + TEST_F(KernelRtlilTest, NewIdBeginsWith) { + IdString id = NEW_ID; + EXPECT_TRUE(id.begins_with("$auto")); + EXPECT_FALSE(id.begins_with("xyz")); + EXPECT_TRUE(id.begins_with("$auto$")); + EXPECT_FALSE(id.begins_with("abcdefghijklmn")); + EXPECT_TRUE(id.begins_with("$auto$rtlilTest")); + EXPECT_FALSE(id.begins_with("$auto$rtlilX")); + } + + TEST_F(KernelRtlilTest, NewIdIndexing) { + IdString id = NEW_ID; + std::string str = id.str(); + for (int i = 0; i < GetSize(str) + 1; ++i) { + EXPECT_EQ(id[i], str.c_str()[i]); + } + } + class WireRtlVsHdlIndexConversionTest : public KernelRtlilTest, public testing::WithParamInterface> From 325b27f43ad7b694d611a48a6a216d136db1d0be Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Mon, 13 Oct 2025 20:52:35 +0000 Subject: [PATCH 036/153] Avoid calling IdString::c_str() in opt_clean --- passes/opt/opt_clean.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/passes/opt/opt_clean.cc b/passes/opt/opt_clean.cc index b3e3cd33a..3892c7581 100644 --- a/passes/opt/opt_clean.cc +++ b/passes/opt/opt_clean.cc @@ -283,7 +283,7 @@ bool compare_signals(RTLIL::SigBit &s1, RTLIL::SigBit &s2, SigPool ®s, SigPoo if (attrs1 != attrs2) return attrs2 > attrs1; - return strcmp(w2->name.c_str(), w1->name.c_str()) < 0; + return w2->name.lt_by_name(w1->name); } bool check_public_name(RTLIL::IdString id) From c4c389fdd71f9dfc653d64789437084fd091f21e Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Mon, 13 Oct 2025 22:23:51 +0000 Subject: [PATCH 037/153] Fix verilog backend to avoid IdString::c_str() --- backends/verilog/verilog_backend.cc | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index faeb2cd0b..67ff1f808 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -108,22 +108,30 @@ IdString initial_id; void reset_auto_counter_id(RTLIL::IdString id, bool may_rename) { - const char *str = id.c_str(); - - if (*str == '$' && may_rename && !norename) - auto_name_map[id] = auto_name_counter++; - - if (str[0] != '\\' || str[1] != '_' || str[2] == 0) + auto it = id.begin(); + auto it_end = id.end(); + if (it == it_end) return; - for (int i = 2; str[i] != 0; i++) { - if (str[i] == '_' && str[i+1] == 0) + if (*it == '$' && may_rename && !norename) + auto_name_map[id] = auto_name_counter++; + + if (*it != '\\' || *it != '_' || (it + 1) == it_end) + return; + + it += 2; + auto start = it; + while (it != it_end) { + char ch = *it; + if (ch == '_' && (it + 1) == it_end) continue; - if (str[i] < '0' || str[i] > '9') + if (ch < '0' || ch > '9') return; } - int num = atoi(str+2); + std::string s; + std::copy(start, it_end, std::back_inserter(s)); + int num = atoi(s.c_str()); if (num >= auto_name_offset) auto_name_offset = num + 1; } From 8c2984dc5f29ac22736febbda462e3f164dcb641 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Mon, 13 Oct 2025 22:43:31 +0000 Subject: [PATCH 038/153] Fix AbcModuleState::remap_name() to avoid calling IdString::c_str() --- passes/techmap/abc.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/passes/techmap/abc.cc b/passes/techmap/abc.cc index e2894aee3..3a814d0b7 100644 --- a/passes/techmap/abc.cc +++ b/passes/techmap/abc.cc @@ -611,7 +611,7 @@ std::string AbcModuleState::remap_name(RTLIL::IdString abc_name, RTLIL::Wire **o } } } - return stringf("$abc$%d$%s", map_autoidx, abc_name.c_str()+1); + return stringf("$abc$%d$%s", map_autoidx, abc_name.substr(1)); } void AbcModuleState::dump_loop_graph(FILE *f, int &nr, dict> &edges, pool &workpool, std::vector &in_counts) From 578d6588713b4a82a493dfdac70fed26984dcaed Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Fri, 17 Oct 2025 12:15:53 +1300 Subject: [PATCH 039/153] Add timing stats for IdString garbage collection --- kernel/driver.cc | 2 ++ kernel/register.cc | 5 +++++ kernel/register.h | 2 ++ kernel/rtlil.cc | 8 ++++++++ kernel/rtlil.h | 5 +++++ 5 files changed, 22 insertions(+) diff --git a/kernel/driver.cc b/kernel/driver.cc index 795fd9fc5..eae9c5276 100644 --- a/kernel/driver.cc +++ b/kernel/driver.cc @@ -709,6 +709,8 @@ int main(int argc, char **argv) total_ns += it.second->runtime_ns + 1; timedat.insert(make_tuple(it.second->runtime_ns + 1, it.second->call_counter, it.first)); } + timedat.insert(make_tuple(RTLIL::OwningIdString::garbage_collection_ns() + 1, + RTLIL::OwningIdString::garbage_collection_count(), "id_gc")); if (timing_details) { diff --git a/kernel/register.cc b/kernel/register.cc index 1f8e4a9a5..3f5aa49ca 100644 --- a/kernel/register.cc +++ b/kernel/register.cc @@ -129,6 +129,11 @@ void Pass::post_execute(Pass::pre_post_exec_state_t state) int64_t time_ns = PerformanceTimer::query() - state.begin_ns; runtime_ns += time_ns; current_pass = state.parent_pass; + subtract_from_current_runtime_ns(time_ns); +} + +void Pass::subtract_from_current_runtime_ns(int64_t time_ns) +{ if (current_pass) current_pass->runtime_ns -= time_ns; } diff --git a/kernel/register.h b/kernel/register.h index b9c709dc1..78f9b430d 100644 --- a/kernel/register.h +++ b/kernel/register.h @@ -95,6 +95,8 @@ struct Pass bool experimental_flag = false; bool internal_flag = false; + static void subtract_from_current_runtime_ns(int64_t time_ns); + void experimental() { experimental_flag = true; } diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 91fbc7960..bc89fc6a4 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -243,8 +243,12 @@ struct IdStringCollector { std::unordered_set live; }; +int64_t RTLIL::OwningIdString::gc_ns; +int RTLIL::OwningIdString::gc_count; + void RTLIL::OwningIdString::collect_garbage() { + int64_t start = PerformanceTimer::query(); #ifndef YOSYS_NO_IDS_REFCNT IdStringCollector collector; for (auto &[idx, design] : *RTLIL::Design::get_all_designs()) { @@ -288,6 +292,10 @@ void RTLIL::OwningIdString::collect_garbage() it = global_autoidx_id_prefix_storage_.erase(it); } #endif + int64_t time_ns = PerformanceTimer::query() - start; + Pass::subtract_from_current_runtime_ns(time_ns); + gc_ns += time_ns; + ++gc_count; } dict RTLIL::constpad; diff --git a/kernel/rtlil.h b/kernel/rtlil.h index de80a9c60..4ae284b25 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -579,6 +579,8 @@ struct RTLIL::OwningIdString : public RTLIL::IdString { // Collect all non-owning references. static void collect_garbage(); + static int64_t garbage_collection_ns() { return gc_ns; } + static int garbage_collection_count() { return gc_count; } // Used by the ID() macro to create an IdString with no destructor whose string will // never be released. If ID() creates a closure-static `OwningIdString` then @@ -590,6 +592,9 @@ struct RTLIL::OwningIdString : public RTLIL::IdString { return result; } private: + static int64_t gc_ns; + static int gc_count; + void get_reference() { get_reference(index_); From ae281720cf25fa7b0d74a9eaa7c25fb7b2672614 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Tue, 21 Oct 2025 00:00:59 +0200 Subject: [PATCH 040/153] tests: remove unstable FPGA synthesis result checks --- tests/arch/quicklogic/pp3/fsm.ys | 2 -- tests/arch/xilinx/dsp_cascade.ys | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/arch/quicklogic/pp3/fsm.ys b/tests/arch/quicklogic/pp3/fsm.ys index 9679628e9..3276e45c6 100644 --- a/tests/arch/quicklogic/pp3/fsm.ys +++ b/tests/arch/quicklogic/pp3/fsm.ys @@ -11,8 +11,6 @@ sat -verify -prove-asserts -show-public -set-at 1 in_reset 1 -seq 20 -prove-skip design -load postopt # load the post-opt design (otherwise equiv_opt loads the pre-opt design) cd fsm # Constrain all select calls below inside the top module -select -assert-count 2 t:LUT2 -select -assert-count 4 t:LUT3 select -assert-count 4 t:dffepc select -assert-count 1 t:logic_0 select -assert-count 1 t:logic_1 diff --git a/tests/arch/xilinx/dsp_cascade.ys b/tests/arch/xilinx/dsp_cascade.ys index ca6b619b9..0a68377f6 100644 --- a/tests/arch/xilinx/dsp_cascade.ys +++ b/tests/arch/xilinx/dsp_cascade.ys @@ -69,7 +69,8 @@ equiv_opt -assert -map +/xilinx/cells_sim.v synth_xilinx -noiopad design -load postopt cd cascade select -assert-count 2 t:DSP48E1 -select -assert-none t:DSP48E1 t:BUFG %% t:* %D +# TODO Disabled check, FDREs emitted due to order sensitivity +# select -assert-none t:DSP48E1 t:BUFG %% t:* %D # Very crude method of checking that DSP48E1.PCOUT -> DSP48E1.PCIN # (see above for explanation) select -assert-count 1 t:DSP48E1 %co:+[PCOUT] t:DSP48E1 %d %co:+[PCIN] w:* %d t:DSP48E1 %i From 50bfb5c5c9bfd464bf2c33b6e2b3bd2c755ed78f Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 12 Nov 2025 20:19:42 +0100 Subject: [PATCH 041/153] constids: add $input_port and $connect --- kernel/constids.inc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/constids.inc b/kernel/constids.inc index 7aed897e1..c99aa788d 100644 --- a/kernel/constids.inc +++ b/kernel/constids.inc @@ -196,6 +196,7 @@ X($bweqx) X($bwmux) X($check) X($concat) +X($connect) X($cover) X($demux) X($dff) @@ -222,6 +223,7 @@ X($get_tag) X($gt) X($initstate) X($input) +X($input_port) X($lcu) X($le) X($live) From 1ee4fc9d27292a1508029c188814ddc043741e44 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 12 Nov 2025 20:20:51 +0100 Subject: [PATCH 042/153] fix YOSYS_XTRACE_GET_PUT --- kernel/rtlil.cc | 2 +- kernel/rtlil.h | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index bc89fc6a4..d18a709c9 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -130,7 +130,7 @@ int RTLIL::IdString::really_insert(std::string_view p, std::unordered_map(size)}; } }; - #undef YOSYS_XTRACE_GET_PUT - #undef YOSYS_SORT_ID_FREE_LIST - #undef YOSYS_NO_IDS_REFCNT - // the global id string cache static bool destruct_guard_ok; // POD, will be initialized to zero @@ -178,7 +174,7 @@ struct RTLIL::IdString if (global_id_storage_.at(idx).buf == nullptr) log("#X# DB-DUMP index %d: FREE\n", idx); else - log("#X# DB-DUMP index %d: '%s' (ref %u)\n", idx, refcount(idx).buf, refcount); + log("#X# DB-DUMP index %d: '%s' (ref %u)\n", idx, global_id_storage_.at(idx).buf, refcount(idx)); } #endif } From 8c76f93fcef412605d0ef01e7987a903be6c9e99 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 12 Nov 2025 20:22:30 +0100 Subject: [PATCH 043/153] rtlil: make tracing optional in IdString garbage collection --- kernel/rtlil.cc | 9 +++++---- kernel/rtlil.h | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index d18a709c9..ee53afe41 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -246,14 +246,15 @@ struct IdStringCollector { int64_t RTLIL::OwningIdString::gc_ns; int RTLIL::OwningIdString::gc_count; -void RTLIL::OwningIdString::collect_garbage() +void RTLIL::OwningIdString::collect_garbage(bool trace) { int64_t start = PerformanceTimer::query(); #ifndef YOSYS_NO_IDS_REFCNT IdStringCollector collector; - for (auto &[idx, design] : *RTLIL::Design::get_all_designs()) { - collector.trace(*design); - } + if (trace) + for (auto &[idx, design] : *RTLIL::Design::get_all_designs()) { + collector.trace(*design); + } int size = GetSize(global_id_storage_); for (int i = static_cast(StaticId::STATIC_ID_END); i < size; ++i) { RTLIL::IdString::Storage &storage = global_id_storage_.at(i); diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 584c4e11b..6098d916b 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -574,7 +574,7 @@ struct RTLIL::OwningIdString : public RTLIL::IdString { } // Collect all non-owning references. - static void collect_garbage(); + static void collect_garbage(bool trace = true); static int64_t garbage_collection_ns() { return gc_ns; } static int garbage_collection_count() { return gc_count; } From ead0f922e1b8116f114633e31c79a6f1db2673c6 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Wed, 12 Nov 2025 22:29:54 +0000 Subject: [PATCH 044/153] Add missing YOSYS_NAMESPACE_PREFIX to fix callers of NEW_ID that aren't in the Yosys namespace --- kernel/yosys_common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/yosys_common.h b/kernel/yosys_common.h index bc8500654..2374182ee 100644 --- a/kernel/yosys_common.h +++ b/kernel/yosys_common.h @@ -276,7 +276,7 @@ RTLIL::IdString new_id_suffix(std::string_view file, int line, std::string_view #define NEW_ID \ YOSYS_NAMESPACE_PREFIX RTLIL::IdString::new_autoidx_with_prefix([](std::string_view func) -> const std::string * { \ - static const std::string *prefix = create_id_prefix(__FILE__, __LINE__, func); \ + static const std::string *prefix = YOSYS_NAMESPACE_PREFIX create_id_prefix(__FILE__, __LINE__, func); \ return prefix; \ }(__FUNCTION__)) #define NEW_ID_SUFFIX(suffix) \ From 04135ba3e48b835efc5c8a453e3fa4f3243b10f2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 00:24:44 +0000 Subject: [PATCH 045/153] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f29c15484..d9cf6e998 100644 --- a/Makefile +++ b/Makefile @@ -161,7 +161,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.59+9 +YOSYS_VER := 0.59+30 YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2 | cut -d'+' -f1) YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'+' -f2) From bc3fc212480abf0ccdf475761bf7a4131074cef5 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 13 Nov 2025 02:02:02 +0100 Subject: [PATCH 046/153] microchip: fix IdString memory leak --- techlibs/microchip/microchip_dsp.pmg | 2 +- techlibs/microchip/microchip_dsp_cascade.pmg | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/techlibs/microchip/microchip_dsp.pmg b/techlibs/microchip/microchip_dsp.pmg index 9a6b9e1fa..2573135ee 100644 --- a/techlibs/microchip/microchip_dsp.pmg +++ b/techlibs/microchip/microchip_dsp.pmg @@ -268,7 +268,7 @@ endmatch code if (postAdd) { - if (postAdd->type.in(ID($sub)) && postAddAB == \A) { + if (postAdd->type.in($sub) && postAddAB == \A) { // if $sub, the multiplier output must match to $sub.B, otherwise no match } else { u_postAddAB = postAddAB; diff --git a/techlibs/microchip/microchip_dsp_cascade.pmg b/techlibs/microchip/microchip_dsp_cascade.pmg index d7ea5911e..fa276d5b5 100644 --- a/techlibs/microchip/microchip_dsp_cascade.pmg +++ b/techlibs/microchip/microchip_dsp_cascade.pmg @@ -115,9 +115,9 @@ finally Wire *cascade = module->addWire(NEW_ID, 48); // zero port C and move wire to cascade - dsp_pcin->setPort(ID(C), Const(0, 48)); - dsp_pcin->setPort(ID(CDIN), cascade); - dsp->setPort(ID(CDOUT), cascade); + dsp_pcin->setPort(\C, Const(0, 48)); + dsp_pcin->setPort(\CDIN, cascade); + dsp->setPort(\CDOUT, cascade); // Configure wire to cascade the dsps add_siguser(cascade, dsp_pcin); From 1dd5b150e545d9b22e790d649dc46a9d433864e0 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 13 Nov 2025 11:10:13 +0100 Subject: [PATCH 047/153] driver: garbage collect IdStrings without tracing at exit --- kernel/driver.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/kernel/driver.cc b/kernel/driver.cc index eae9c5276..792f78b7f 100644 --- a/kernel/driver.cc +++ b/kernel/driver.cc @@ -158,6 +158,7 @@ extern "C" { void yosys_atexit() { + RTLIL::OwningIdString::collect_garbage(false); #if defined(YOSYS_ENABLE_READLINE) || defined(YOSYS_ENABLE_EDITLINE) if (!yosys_history_file.empty()) { #if defined(YOSYS_ENABLE_READLINE) From c48bc56f4aa8873e8f32d2a7542ade803cc3043c Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 13 Nov 2025 11:10:50 +0100 Subject: [PATCH 048/153] driver: fix timing data divison by zero when no passes run --- kernel/driver.cc | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/kernel/driver.cc b/kernel/driver.cc index 792f78b7f..d8b104117 100644 --- a/kernel/driver.cc +++ b/kernel/driver.cc @@ -707,11 +707,16 @@ int main(int argc, char **argv) for (auto &it : pass_register) if (it.second->call_counter) { - total_ns += it.second->runtime_ns + 1; - timedat.insert(make_tuple(it.second->runtime_ns + 1, it.second->call_counter, it.first)); + auto pass_ns = it.second->runtime_ns + 1; + total_ns += pass_ns; + timedat.insert(make_tuple(pass_ns, it.second->call_counter, it.first)); } - timedat.insert(make_tuple(RTLIL::OwningIdString::garbage_collection_ns() + 1, - RTLIL::OwningIdString::garbage_collection_count(), "id_gc")); + { + auto gc_ns = RTLIL::OwningIdString::garbage_collection_ns() + 1; + total_ns += gc_ns; + timedat.insert(make_tuple(gc_ns, + RTLIL::OwningIdString::garbage_collection_count(), "id_gc")); + } if (timing_details) { From a9151437688b06b068114170066d1242e4721dbc Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 13 Nov 2025 12:40:04 +0100 Subject: [PATCH 049/153] ice40: fix IdString memory leak --- techlibs/ice40/ice40_opt.cc | 4 ++-- techlibs/ice40/ice40_wrapcarry.cc | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/techlibs/ice40/ice40_opt.cc b/techlibs/ice40/ice40_opt.cc index b13d33018..c88fd69b6 100644 --- a/techlibs/ice40/ice40_opt.cc +++ b/techlibs/ice40/ice40_opt.cc @@ -117,7 +117,7 @@ static void run_ice40_opts(Module *module) if (GetSize(replacement_output)) { optimized_co.insert(sigmap(cell->getPort(ID::CO)[0])); - auto it = cell->attributes.find(ID(SB_LUT4.name)); + auto it = cell->attributes.find(IdString{"\\SB_LUT4.name"}); if (it != cell->attributes.end()) { module->rename(cell, it->second.decode_string()); decltype(Cell::attributes) new_attr; @@ -126,7 +126,7 @@ static void run_ice40_opts(Module *module) new_attr[a.first.c_str() + strlen("\\SB_LUT4.")] = a.second; else if (a.first == ID::src) new_attr.insert(std::make_pair(a.first, a.second)); - else if (a.first.in(ID(SB_LUT4.name), ID::keep, ID::module_not_derived)) + else if (a.first.in(IdString{"\\SB_LUT4.name"}, ID::keep, ID::module_not_derived)) continue; else if (a.first.begins_with("\\SB_CARRY.\\")) continue; diff --git a/techlibs/ice40/ice40_wrapcarry.cc b/techlibs/ice40/ice40_wrapcarry.cc index fe928ba6d..82218ff11 100644 --- a/techlibs/ice40/ice40_wrapcarry.cc +++ b/techlibs/ice40/ice40_wrapcarry.cc @@ -62,7 +62,7 @@ void create_ice40_wrapcarry(ice40_wrapcarry_pm &pm) cell->attributes[stringf("\\SB_CARRY.%s", a.first)] = a.second; for (const auto &a : st.lut->attributes) cell->attributes[stringf("\\SB_LUT4.%s", a.first)] = a.second; - cell->attributes[ID(SB_LUT4.name)] = Const(st.lut->name.str()); + cell->attributes[IdString{"\\SB_LUT4.name"}] = Const(st.lut->name.str()); if (st.carry->get_bool_attribute(ID::keep) || st.lut->get_bool_attribute(ID::keep)) cell->attributes[ID::keep] = true; @@ -122,7 +122,7 @@ struct Ice40WrapCarryPass : public Pass { carry->setPort(ID::CI, cell->getPort(ID::CI)); carry->setPort(ID::CO, cell->getPort(ID::CO)); module->swap_names(carry, cell); - auto lut_name = cell->attributes.at(ID(SB_LUT4.name), Const(NEW_ID.str())).decode_string(); + auto lut_name = cell->attributes.at(IdString{"\\SB_LUT4.name"}, Const(NEW_ID.str())).decode_string(); auto lut = module->addCell(lut_name, ID($lut)); lut->setParam(ID::WIDTH, 4); lut->setParam(ID::LUT, cell->getParam(ID::LUT)); @@ -138,7 +138,7 @@ struct Ice40WrapCarryPass : public Pass { lut->attributes[a.first.c_str() + strlen("\\SB_LUT4.")] = a.second; else if (a.first == ID::src) src = a.second; - else if (a.first.in(ID(SB_LUT4.name), ID::keep, ID::module_not_derived)) + else if (a.first.in(IdString{"\\SB_LUT4.name"}, ID::keep, ID::module_not_derived)) continue; else log_abort(); From f2263642a4de789069edcefb4a0a1c68e0b01446 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 13 Nov 2025 12:42:06 +0100 Subject: [PATCH 050/153] xilinx: fix IdString memory leak --- techlibs/xilinx/xilinx_dsp_cascade.pmg | 28 +++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/techlibs/xilinx/xilinx_dsp_cascade.pmg b/techlibs/xilinx/xilinx_dsp_cascade.pmg index 29fc27dfe..9eebd33c3 100644 --- a/techlibs/xilinx/xilinx_dsp_cascade.pmg +++ b/techlibs/xilinx/xilinx_dsp_cascade.pmg @@ -90,9 +90,9 @@ finally if (i % MAX_DSP_CASCADE > 0) { if (P >= 0) { Wire *cascade = module->addWire(NEW_ID, 48); - dsp_pcin->setPort(ID(C), Const(0, 48)); - dsp_pcin->setPort(ID(PCIN), cascade); - dsp->setPort(ID(PCOUT), cascade); + dsp_pcin->setPort(\C, Const(0, 48)); + dsp_pcin->setPort(\PCIN, cascade); + dsp->setPort(\PCOUT, cascade); add_siguser(cascade, dsp_pcin); add_siguser(cascade, dsp); @@ -118,15 +118,15 @@ finally } if (AREG >= 0) { Wire *cascade = module->addWire(NEW_ID, 30); - dsp_pcin->setPort(ID(A), Const(0, 30)); - dsp_pcin->setPort(ID(ACIN), cascade); - dsp->setPort(ID(ACOUT), cascade); + dsp_pcin->setPort(\A, Const(0, 30)); + dsp_pcin->setPort(\ACIN, cascade); + dsp->setPort(\ACOUT, cascade); add_siguser(cascade, dsp_pcin); add_siguser(cascade, dsp); if (dsp->type.in(\DSP48E1)) - dsp->setParam(ID(ACASCREG), AREG); - dsp_pcin->setParam(ID(A_INPUT), Const("CASCADE")); + dsp->setParam(\ACASCREG, AREG); + dsp_pcin->setParam(\A_INPUT, Const("CASCADE")); log_debug("ACOUT -> ACIN cascade for %s -> %s\n", log_id(dsp), log_id(dsp_pcin)); } @@ -138,18 +138,18 @@ finally // BCOUT from an adjacent DSP48A1 slice. The tools then // translate BCOUT cascading to the dedicated BCIN input // and set the B_INPUT attribute for implementation." - dsp_pcin->setPort(ID(B), cascade); + dsp_pcin->setPort(\B, cascade); } else { - dsp_pcin->setPort(ID(B), Const(0, 18)); - dsp_pcin->setPort(ID(BCIN), cascade); + dsp_pcin->setPort(\B, Const(0, 18)); + dsp_pcin->setPort(\BCIN, cascade); } - dsp->setPort(ID(BCOUT), cascade); + dsp->setPort(\BCOUT, cascade); add_siguser(cascade, dsp_pcin); add_siguser(cascade, dsp); if (dsp->type.in(\DSP48E1)) { - dsp->setParam(ID(BCASCREG), BREG); + dsp->setParam(\BCASCREG, BREG); // According to UG389 p13 [https://www.xilinx.com/support/documentation/user_guides/ug389.pdf] // "The attribute is only used by place and route tools and // is not necessary for the users to set for synthesis. The @@ -158,7 +158,7 @@ finally // BCOUT of another DSP48A1 slice, then the tools automatically // set the attribute to 'CASCADE', otherwise it is set to // 'DIRECT'". - dsp_pcin->setParam(ID(B_INPUT), Const("CASCADE")); + dsp_pcin->setParam(\B_INPUT, Const("CASCADE")); } log_debug("BCOUT -> BCIN cascade for %s -> %s\n", log_id(dsp), log_id(dsp_pcin)); From 5e0bc3fd9f951229afa1b71d9f5b115899094bd4 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Wed, 12 Nov 2025 11:26:46 +0100 Subject: [PATCH 051/153] Build pyosys on CI --- .github/workflows/test-verific.yml | 39 ++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/.github/workflows/test-verific.yml b/.github/workflows/test-verific.yml index 503d6c31b..6619e1124 100644 --- a/.github/workflows/test-verific.yml +++ b/.github/workflows/test-verific.yml @@ -83,3 +83,42 @@ jobs: shell: bash run: | make -j$procs unit-test ENABLE_LTO=1 ENABLE_LIBYOSYS=1 + + test-pyosys: + needs: pre-job + if: ${{ needs.pre-job.outputs.should_skip != 'true' && github.repository == 'YosysHQ/Yosys' }} + runs-on: [self-hosted, linux, x64, fast] + steps: + - name: Checkout Yosys + uses: actions/checkout@v4 + with: + persist-credentials: false + submodules: true + - name: Install UV + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + - name: Runtime environment + run: | + echo "procs=$(nproc)" >> $GITHUB_ENV + echo "${{ github.workspace }}/.local/bin" >> $GITHUB_PATH + + - name: Build pyosys + run: | + make config-clang + echo "ENABLE_VERIFIC := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_EDIF := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_LIBERTY := 1" >> Makefile.conf + echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 1" >> Makefile.conf + echo "ENABLE_CCACHE := 1" >> Makefile.conf + echo "ENABLE_PYOSYS := 1" >> Makefile.conf + echo "PYTHON_DESTDIR := /usr/lib/python3/site-packages" >> Makefile.conf + make -j$procs + + - name: Install pyosys + run: | + make install DESTDIR=${GITHUB_WORKSPACE}/.local PREFIX= + + - name: Run pyosys tests + run: | + export PYTHONPATH=${GITHUB_WORKSPACE}/.local/usr/lib/python3/site-packages:$PYTHONPATH + python3 tests/pyosys/run_tests.py From 10b49f3a916a924b6ef31aeac0d5a31289c2d54a Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Wed, 12 Nov 2025 13:47:47 +0100 Subject: [PATCH 052/153] Update generator to reflect IdString changes --- pyosys/generator.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyosys/generator.py b/pyosys/generator.py index f89819504..c0ccdac5a 100644 --- a/pyosys/generator.py +++ b/pyosys/generator.py @@ -168,9 +168,8 @@ pyosys_headers = [ "global_negative_id_prefix_storage_", "global_refcount_storage_", "global_free_idx_list_", - "last_created_idx_ptr_", - "last_created_idx_", "builtin_ff_cell_types", + "substrings", } ), ), @@ -455,7 +454,7 @@ class PyosysWrapperGenerator(object): ) -> str: is_method = isinstance(function, Method) function_return_type = function.return_type.format() - if class_basename == "Const" and function_return_type in { + if class_basename in {"Const","IdString"} and function_return_type in { "iterator", "const_iterator", }: From 40bbb847667ea11236b03a5825bbcb51a5e986e8 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Wed, 12 Nov 2025 13:53:40 +0100 Subject: [PATCH 053/153] Enable pyosys install in out of tree builds --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d9cf6e998..937cc42bf 100644 --- a/Makefile +++ b/Makefile @@ -1040,7 +1040,7 @@ ifeq ($(ENABLE_LIBYOSYS),1) if [ -n "$(STRIP)" ]; then $(INSTALL_SUDO) $(STRIP) -S $(DESTDIR)$(LIBDIR)/libyosys.so; fi ifeq ($(ENABLE_PYOSYS),1) $(INSTALL_SUDO) mkdir -p $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys - $(INSTALL_SUDO) cp pyosys/__init__.py $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/__init__.py + $(INSTALL_SUDO) cp $(YOSYS_SRC)/pyosys/__init__.py $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/__init__.py $(INSTALL_SUDO) cp libyosys.so $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/libyosys.so $(INSTALL_SUDO) cp -r share $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys ifeq ($(ENABLE_ABC),1) From abc78f04241d3f6e59f9256814c54968923bb0e5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 00:25:00 +0000 Subject: [PATCH 054/153] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 937cc42bf..1016e3d13 100644 --- a/Makefile +++ b/Makefile @@ -161,7 +161,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.59+30 +YOSYS_VER := 0.59+44 YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2 | cut -d'+' -f1) YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'+' -f2) From a75b999f133b90c35f366e6b170ad03b3fda796a Mon Sep 17 00:00:00 2001 From: Anhijkt Date: Fri, 14 Nov 2025 13:25:51 +0200 Subject: [PATCH 055/153] fsm_detect: fix test --- tests/various/fsm-arst.ys | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/tests/various/fsm-arst.ys b/tests/various/fsm-arst.ys index 2cfca1d57..d3c40444b 100644 --- a/tests/various/fsm-arst.ys +++ b/tests/various/fsm-arst.ys @@ -33,7 +33,6 @@ endmodule module semi_self_rs_fsm ( input wire clk, - inout wire reset, input wire test, output wire s1 ); @@ -44,7 +43,7 @@ module semi_self_rs_fsm ( reg [7:0] current_state, next_state; reg [1:0] reset_test; - assign reset = (test || (reset_test == 2)); + wire reset = (test || (reset_test == 2)); always @(posedge clk or posedge reset) begin if (reset) begin @@ -75,39 +74,28 @@ endmodule module self_rs_fsm ( input wire clk, - inout wire reset, output wire s1 ); localparam [7:0] RST = 8'b10010010; localparam [7:0] S1 = 8'b01001000; localparam [7:0] S2 = 8'b11000111; - reg [7:0] current_state, next_state; - reg reset_reg; - + reg [7:0] next_state; wire reset = (reset_reg || next_state == S1); + always @(posedge clk or posedge reset) begin if (reset) begin - current_state <= RST; - reset_reg = 0; + next_state <= RST; end else begin - current_state <= next_state; + case (next_state) + RST: next_state = S1; + S1: next_state = S2; + S2: next_state = S1; + default: next_state = RST; + endcase end end - always @(*) begin - next_state = current_state; - - case (current_state) - RST: next_state = S1; - S1: next_state = S2; - S2: next_state = S1; - default: begin - reset_reg = 1; - next_state = RST; - end - endcase - end assign s1 = next_state == S1; endmodule From b08195a9cffdeff14fc93047745033952d20f914 Mon Sep 17 00:00:00 2001 From: Anhijkt Date: Fri, 14 Nov 2025 13:34:58 +0200 Subject: [PATCH 056/153] typo --- tests/various/fsm-arst.ys | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/various/fsm-arst.ys b/tests/various/fsm-arst.ys index d3c40444b..4b2c9c66a 100644 --- a/tests/various/fsm-arst.ys +++ b/tests/various/fsm-arst.ys @@ -81,7 +81,7 @@ module self_rs_fsm ( localparam [7:0] S2 = 8'b11000111; reg [7:0] next_state; - wire reset = (reset_reg || next_state == S1); + wire reset = next_state == S1; always @(posedge clk or posedge reset) begin if (reset) begin From 65d7d70507cc007a66d5e876a2246b4aa6d784c5 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 14 Nov 2025 15:31:45 +0100 Subject: [PATCH 057/153] driver: move IdString collection to yosys_shutdown --- kernel/driver.cc | 1 - kernel/yosys.cc | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/driver.cc b/kernel/driver.cc index d8b104117..726e25302 100644 --- a/kernel/driver.cc +++ b/kernel/driver.cc @@ -158,7 +158,6 @@ extern "C" { void yosys_atexit() { - RTLIL::OwningIdString::collect_garbage(false); #if defined(YOSYS_ENABLE_READLINE) || defined(YOSYS_ENABLE_EDITLINE) if (!yosys_history_file.empty()) { #if defined(YOSYS_ENABLE_READLINE) diff --git a/kernel/yosys.cc b/kernel/yosys.cc index bf59302f8..4e4abed20 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -260,6 +260,7 @@ void yosys_shutdown() delete yosys_design; yosys_design = NULL; + RTLIL::OwningIdString::collect_garbage(); for (auto f : log_files) if (f != stderr) From c497b3b24c4168659288be344c6548163f604a45 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 14 Nov 2025 15:32:10 +0100 Subject: [PATCH 058/153] Revert "rtlil: make tracing optional in IdString garbage collection" This reverts commit 8c76f93fcef412605d0ef01e7987a903be6c9e99. --- kernel/rtlil.cc | 9 ++++----- kernel/rtlil.h | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index ee53afe41..d18a709c9 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -246,15 +246,14 @@ struct IdStringCollector { int64_t RTLIL::OwningIdString::gc_ns; int RTLIL::OwningIdString::gc_count; -void RTLIL::OwningIdString::collect_garbage(bool trace) +void RTLIL::OwningIdString::collect_garbage() { int64_t start = PerformanceTimer::query(); #ifndef YOSYS_NO_IDS_REFCNT IdStringCollector collector; - if (trace) - for (auto &[idx, design] : *RTLIL::Design::get_all_designs()) { - collector.trace(*design); - } + for (auto &[idx, design] : *RTLIL::Design::get_all_designs()) { + collector.trace(*design); + } int size = GetSize(global_id_storage_); for (int i = static_cast(StaticId::STATIC_ID_END); i < size; ++i) { RTLIL::IdString::Storage &storage = global_id_storage_.at(i); diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 6098d916b..584c4e11b 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -574,7 +574,7 @@ struct RTLIL::OwningIdString : public RTLIL::IdString { } // Collect all non-owning references. - static void collect_garbage(bool trace = true); + static void collect_garbage(); static int64_t garbage_collection_ns() { return gc_ns; } static int garbage_collection_count() { return gc_count; } From f47540b950d3b3b1f10831effafa77f7e9f33d8b Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 14 Nov 2025 15:40:14 +0100 Subject: [PATCH 059/153] techlibs: remove cells.lib --- techlibs/common/Makefile.inc | 1 - techlibs/common/cells.lib | 108 ----------------------------------- 2 files changed, 109 deletions(-) delete mode 100644 techlibs/common/cells.lib diff --git a/techlibs/common/Makefile.inc b/techlibs/common/Makefile.inc index e76d70a1a..22b119800 100644 --- a/techlibs/common/Makefile.inc +++ b/techlibs/common/Makefile.inc @@ -28,7 +28,6 @@ $(eval $(call add_share_file,share,techlibs/common/adff2dff.v)) $(eval $(call add_share_file,share,techlibs/common/dff2ff.v)) $(eval $(call add_share_file,share,techlibs/common/gate2lut.v)) $(eval $(call add_share_file,share,techlibs/common/cmp2lut.v)) -$(eval $(call add_share_file,share,techlibs/common/cells.lib)) $(eval $(call add_share_file,share,techlibs/common/mul2dsp.v)) $(eval $(call add_share_file,share,techlibs/common/abc9_model.v)) $(eval $(call add_share_file,share,techlibs/common/abc9_map.v)) diff --git a/techlibs/common/cells.lib b/techlibs/common/cells.lib deleted file mode 100644 index eb89036d7..000000000 --- a/techlibs/common/cells.lib +++ /dev/null @@ -1,108 +0,0 @@ -library(yosys_cells) { - cell(DFF_N) { - ff(IQ, IQN) { - clocked_on: "!C"; - next_state: "D"; - } - pin(D) { direction: input; } - pin(C) { direction: input; clock: true; } - pin(Q) { direction: output; function: "IQ"; } - } - cell(DFF_P) { - ff(IQ, IQN) { - clocked_on: "C"; - next_state: "D"; - } - pin(D) { direction: input; } - pin(C) { direction: input; clock: true; } - pin(Q) { direction: output; function: "IQ"; } - } - cell(DFF_NN0) { - ff(IQ, IQN) { - clocked_on: "!C"; - next_state: "D"; - clear: "!R"; - } - pin(D) { direction: input; } - pin(R) { direction: input; } - pin(C) { direction: input; clock: true; } - pin(Q) { direction: output; function: "IQ"; } - } - cell(DFF_NN1) { - ff(IQ, IQN) { - clocked_on: "!C"; - next_state: "D"; - preset: "!R"; - } - pin(D) { direction: input; } - pin(R) { direction: input; } - pin(C) { direction: input; clock: true; } - pin(Q) { direction: output; function: "IQ"; } - } - cell(DFF_NP0) { - ff(IQ, IQN) { - clocked_on: "!C"; - next_state: "D"; - clear: "R"; - } - pin(D) { direction: input; } - pin(R) { direction: input; } - pin(C) { direction: input; clock: true; } - pin(Q) { direction: output; function: "IQ"; } - } - cell(DFF_NP1) { - ff(IQ, IQN) { - clocked_on: "!C"; - next_state: "D"; - preset: "R"; - } - pin(D) { direction: input; } - pin(R) { direction: input; } - pin(C) { direction: input; clock: true; } - pin(Q) { direction: output; function: "IQ"; } - } - cell(DFF_PN0) { - ff(IQ, IQN) { - clocked_on: "C"; - next_state: "D"; - clear: "!R"; - } - pin(D) { direction: input; } - pin(R) { direction: input; } - pin(C) { direction: input; clock: true; } - pin(Q) { direction: output; function: "IQ"; } - } - cell(DFF_PN1) { - ff(IQ, IQN) { - clocked_on: "C"; - next_state: "D"; - preset: "!R"; - } - pin(D) { direction: input; } - pin(R) { direction: input; } - pin(C) { direction: input; clock: true; } - pin(Q) { direction: output; function: "IQ"; } - } - cell(DFF_PP0) { - ff(IQ, IQN) { - clocked_on: "C"; - next_state: "D"; - clear: "R"; - } - pin(D) { direction: input; } - pin(R) { direction: input; } - pin(C) { direction: input; clock: true; } - pin(Q) { direction: output; function: "IQ"; } - } - cell(DFF_PP1) { - ff(IQ, IQN) { - clocked_on: "C"; - next_state: "D"; - preset: "R"; - } - pin(D) { direction: input; } - pin(R) { direction: input; } - pin(C) { direction: input; clock: true; } - pin(Q) { direction: output; function: "IQ"; } - } -} From 677bf21947d80eb341f15109392d7f65e6ed2589 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 15 Nov 2025 00:23:54 +0000 Subject: [PATCH 060/153] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1016e3d13..b040cf39f 100644 --- a/Makefile +++ b/Makefile @@ -161,7 +161,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.59+44 +YOSYS_VER := 0.59+54 YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2 | cut -d'+' -f1) YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'+' -f2) From 261a0ae9e13a0b05c430d0666f4933f14d7f573d Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Mon, 17 Nov 2025 09:14:51 +0000 Subject: [PATCH 061/153] Implement at() methods on SigSpec so that SigSpec::bits().at() continues to work as it did Fixes a regression caused by commit 745222fa3bf2ac570935ffd044c86fa2eb12d123, which caused some third-party code to fail to build, e.g.: https://github.com/google/heir/blob/5d7aa035c6e42394cd4dd45faca0b0933af89950/lib/Transforms/YosysOptimizer/RTLILImporter.cpp#L229 --- kernel/rtlil.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 584c4e11b..a8e43fe1d 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -1726,6 +1726,8 @@ public: operator std::vector() const; operator std::vector() const { return to_sigbit_vector(); } const RTLIL::SigBit &at(int offset, const RTLIL::SigBit &defval) { return offset < size() ? (*this)[offset] : defval; } + RTLIL::SigBit& at(int offset) { return (*this)[offset]; } + RTLIL::SigBit at(int offset) const { return (*this)[offset]; } [[nodiscard]] Hasher hash_into(Hasher h) const { Hasher::hash_t val; From b870693393db0842ed5b00623809397fbad3fc7c Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Mon, 17 Nov 2025 09:11:15 +0000 Subject: [PATCH 062/153] Fix reset_auto_counter_id to correctly detect _NNN_ patterns This fixes a regression caused by commit c4c389fdd71f9dfc653d64789437084fd091f21e. --- backends/verilog/verilog_backend.cc | 10 +++++----- tests/verilog/reset_auto_counter.ys | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 tests/verilog/reset_auto_counter.ys diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index 67ff1f808..8d77160fd 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -116,21 +116,21 @@ void reset_auto_counter_id(RTLIL::IdString id, bool may_rename) if (*it == '$' && may_rename && !norename) auto_name_map[id] = auto_name_counter++; - if (*it != '\\' || *it != '_' || (it + 1) == it_end) + if (*it != '\\' || (it + 1) == it_end || *(it + 1) != '_' || (it + 2) == it_end) return; + std::string s; it += 2; - auto start = it; while (it != it_end) { char ch = *it; if (ch == '_' && (it + 1) == it_end) - continue; + break; if (ch < '0' || ch > '9') return; + s.push_back(ch); + ++it; } - std::string s; - std::copy(start, it_end, std::back_inserter(s)); int num = atoi(s.c_str()); if (num >= auto_name_offset) auto_name_offset = num + 1; diff --git a/tests/verilog/reset_auto_counter.ys b/tests/verilog/reset_auto_counter.ys new file mode 100644 index 000000000..c9756cbac --- /dev/null +++ b/tests/verilog/reset_auto_counter.ys @@ -0,0 +1,17 @@ +read_verilog -sv < Date: Mon, 17 Nov 2025 13:35:38 +0100 Subject: [PATCH 063/153] ignore generated file --- tests/verilog/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/verilog/.gitignore b/tests/verilog/.gitignore index d3e8690d5..b16ed0890 100644 --- a/tests/verilog/.gitignore +++ b/tests/verilog/.gitignore @@ -1,6 +1,7 @@ /const_arst.v /const_sr.v /doubleslash.v +/reset_auto_counter.v /roundtrip_proc_1.v /roundtrip_proc_2.v /assign_to_reg.v From 9aa2dde7efb2edb75f35f01d5b0b6ded1d638ba3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 00:24:34 +0000 Subject: [PATCH 064/153] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b040cf39f..6984ba5f1 100644 --- a/Makefile +++ b/Makefile @@ -161,7 +161,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.59+54 +YOSYS_VER := 0.59+62 YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2 | cut -d'+' -f1) YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'+' -f2) From b3ead7e47dfb42aa2c35457bfccb7496618a8c85 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 16 Jul 2025 23:15:43 +0200 Subject: [PATCH 065/153] icell_liberty: start --- passes/cmds/Makefile.inc | 1 + passes/cmds/icell_liberty.cc | 138 +++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 passes/cmds/icell_liberty.cc diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index 2dbeadac2..bb726aa7d 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -58,3 +58,4 @@ 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 diff --git a/passes/cmds/icell_liberty.cc b/passes/cmds/icell_liberty.cc new file mode 100644 index 000000000..19692fa01 --- /dev/null +++ b/passes/cmds/icell_liberty.cc @@ -0,0 +1,138 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Martin Povišer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ +#include "kernel/yosys.h" +#include "kernel/celltypes.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct LibertyStubber { + CellTypes ct; + CellTypes ct_ff; + LibertyStubber() { + ct.setup(); + ct.setup_internals_ff(); + } + void liberty_prefix(std::ostream& f) + { + 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"; + } + 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; + } + 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", "derive box modules") {} + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" icell_liberty \n"); // TODO + log("\n"); + log("\n"); + } + void execute(std::vector args, RTLIL::Design *d) override + { + log_header(d, "Executing ICELL_LIBERTY pass.\n"); + + size_t argidx; + IdString naming_attr; + std::string liberty_filename; + std::ofstream* liberty_file = new std::ofstream; + + for (argidx = 1; argidx < args.size(); argidx++) { + break; + } + if (argidx < args.size()) + liberty_filename = args[argidx++]; + else + log_error("no Liberty filename specified\n"); + + // extra_args(args, argidx, d); + + if (liberty_filename.size()) { + liberty_file->open(liberty_filename.c_str()); + if (liberty_file->fail()) { + delete liberty_file; + log_error("Can't open file `%s' for writing: %s\n", liberty_filename.c_str(), strerror(errno)); + } + } + + pool done; + LibertyStubber stubber = {}; + + if (liberty_file) + 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); + } + } + } + + if (liberty_file) { + stubber.liberty_suffix(*liberty_file); + delete liberty_file; + } + } +} IcellLiberty; + +PRIVATE_NAMESPACE_END From 9e81db43731bc90f42612191f33f63af509774d1 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 18 Jul 2025 12:34:10 +0200 Subject: [PATCH 066/153] ff: split out type-only information --- kernel/ff.cc | 489 +++++++++++++++++++++++++++++---------------------- kernel/ff.h | 78 ++++---- 2 files changed, 323 insertions(+), 244 deletions(-) diff --git a/kernel/ff.cc b/kernel/ff.cc index a72e6a65c..7dd5e24ac 100644 --- a/kernel/ff.cc +++ b/kernel/ff.cc @@ -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>> +void manufacture_info(InputType flop, OutputType& info, FfInitVals *initvals) { + Cell* cell = nullptr; + IdString type; + constexpr bool have_cell = std::is_same_v; + if constexpr (std::is_same_v) { + type = flop; + } else { + static_assert(std::is_same_v); + 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 &bits) { diff --git a/kernel/ff.h b/kernel/ff.h index d6cf99b9b..217658d35 100644 --- a/kernel/ff.h +++ b/kernel/ff.h @@ -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; From c2c9506f4f0bd3d0d0a59bc904258d1c24cd9341 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 18 Jul 2025 13:32:49 +0200 Subject: [PATCH 067/153] icell_liberty: flops --- passes/cmds/icell_liberty.cc | 86 +++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/passes/cmds/icell_liberty.cc b/passes/cmds/icell_liberty.cc index 19692fa01..9b89c1125 100644 --- a/passes/cmds/icell_liberty.cc +++ b/passes/cmds/icell_liberty.cc @@ -18,19 +18,27 @@ */ #include "kernel/yosys.h" #include "kernel/celltypes.h" +#include "kernel/ff.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN struct LibertyStubber { CellTypes ct; - CellTypes ct_ff; LibertyStubber() { ct.setup(); ct.setup_internals_ff(); } void liberty_prefix(std::ostream& f) { + f << "/*\n"; + f << stringf("Models interfaces of select Yosys internal cell.\n"); + f << stringf("Likely contains INCORRECT POLARITIES.\n"); + f << stringf("Impractical for any simulation, synthesis, or timing.\n"); + f << stringf("Intended purely for SDC expansion.\n"); + f << stringf("Do not microwave or tumble dry.\n"); + f << stringf("Generated 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"; @@ -45,6 +53,76 @@ struct LibertyStubber { { 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; + 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) { + i.item("direction", "input"); + } else if (!is_input && is_output) { + i.item("direction", "output"); + } else { + i.item("direction", "inout"); + } + if (RTLIL::unescape_id(x) == "CLK" || RTLIL::unescape_id(x) == "C") + i.item("clock", "true"); + if (RTLIL::unescape_id(x) == "Q") + i.item("function", "IQ"); + 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"; + + + // bool has_aload; + // bool has_srst; + // bool has_arst; + // bool has_sr; + f << "\t}\n"; + } void liberty_cell(Module* base, Module* derived, std::ostream& f) { auto base_name = base->name.str().substr(1); @@ -53,6 +131,10 @@ struct LibertyStubber { 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) { @@ -73,7 +155,7 @@ struct LibertyStubber { }; struct IcellLiberty : Pass { - IcellLiberty() : Pass("icell_liberty", "derive box modules") {} + IcellLiberty() : Pass("icell_liberty", "write Liberty interfaces for used internal cells") {} void help() override { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| From e4e32d7966865f107ba63349d6551ce39f6c6ff4 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 18 Jul 2025 22:27:11 +0200 Subject: [PATCH 068/153] icell_liberty: flop harder --- passes/cmds/icell_liberty.cc | 51 +++++++++++++++++------------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/passes/cmds/icell_liberty.cc b/passes/cmds/icell_liberty.cc index 9b89c1125..83252ffb4 100644 --- a/passes/cmds/icell_liberty.cc +++ b/passes/cmds/icell_liberty.cc @@ -1,21 +1,3 @@ -/* - * yosys -- Yosys Open SYnthesis Suite - * - * Copyright (C) 2024 Martin Povišer - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - */ #include "kernel/yosys.h" #include "kernel/celltypes.h" #include "kernel/ff.h" @@ -32,12 +14,12 @@ struct LibertyStubber { void liberty_prefix(std::ostream& f) { f << "/*\n"; - f << stringf("Models interfaces of select Yosys internal cell.\n"); - f << stringf("Likely contains INCORRECT POLARITIES.\n"); - f << stringf("Impractical for any simulation, synthesis, or timing.\n"); - f << stringf("Intended purely for SDC expansion.\n"); - f << stringf("Do not microwave or tumble dry.\n"); - f << stringf("Generated by %s\n", yosys_maybe_version()); + 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"; @@ -82,7 +64,13 @@ struct LibertyStubber { f << "\tcell (\"" << derived_name << "\") {\n"; auto& base_type = ct.cell_types[base_name]; i.indent = 3; - for (auto x : derived->ports) { + 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"; @@ -93,10 +81,19 @@ struct LibertyStubber { } else { i.item("direction", "inout"); } - if (RTLIL::unescape_id(x) == "CLK" || RTLIL::unescape_id(x) == "C") + if (port_name == "CLK" || port_name == "C") { i.item("clock", "true"); - if (RTLIL::unescape_id(x) == "Q") + 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"; } From bbf1e4bca2a1e55ad2534efb35196a3150b7c341 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 18 Jul 2025 22:45:31 +0200 Subject: [PATCH 069/153] sdc_expand, opensta: start --- techlibs/common/Makefile.inc | 2 + techlibs/common/opensta.cc | 118 ++++++++++++++++++++++++++ techlibs/common/sdc_expand.cc | 150 ++++++++++++++++++++++++++++++++++ 3 files changed, 270 insertions(+) create mode 100644 techlibs/common/opensta.cc create mode 100644 techlibs/common/sdc_expand.cc diff --git a/techlibs/common/Makefile.inc b/techlibs/common/Makefile.inc index 22b119800..df96fdd46 100644 --- a/techlibs/common/Makefile.inc +++ b/techlibs/common/Makefile.inc @@ -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 diff --git a/techlibs/common/opensta.cc b/techlibs/common/opensta.cc new file mode 100644 index 000000000..1b3571973 --- /dev/null +++ b/techlibs/common/opensta.cc @@ -0,0 +1,118 @@ +#include "kernel/rtlil.h" +#include "kernel/log.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +#if !defined(YOSYS_DISABLE_SPAWN) +struct OpenstaPass : public Pass +{ + const char* default_sta_cmd = "sta"; + 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"); + // TOOD + log("\n"); + log(" -exe \n"); + log(" use to run OpenSTA instead of \"%s\"\n", default_sta_cmd); + log("\n"); + log(" -sdc-in \n"); + log(" expand SDC input file \n"); + log("\n"); + log(" -sdc-out \n"); + log(" expand SDC file to output file \n"); + log("\n"); + log(" -nocleanup\n"); + log("\n"); + log("\n"); + } + + void execute(std::vector args, RTLIL::Design *design) override + { + string run_from, run_to; + string opensta_exe = "sta"; + string sdc_filename, sdc_expanded_filename; + string tempdir_name, script_filename; + string verilog_filename, liberty_filename; + bool cleanup = true; + + log_header(design, "Executing OPENSTA pass.\n"); + 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; + int ret = run_command(command); + if (ret) + log_error("OpenSTA return %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 diff --git a/techlibs/common/sdc_expand.cc b/techlibs/common/sdc_expand.cc new file mode 100644 index 000000000..7efaf8c0d --- /dev/null +++ b/techlibs/common/sdc_expand.cc @@ -0,0 +1,150 @@ +#include "kernel/rtlil.h" +#include "kernel/log.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct SdcexpandPass : public ScriptPass +{ + const char* default_sta_cmd = "sta"; + SdcexpandPass() : ScriptPass("sdc_expand", "run OpenSTA") { } + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" sdc_expand [options]\n"); + log("\n"); + // TODO + log("\n"); + log(" -exe \n"); + log(" use to run OpenSTA instead of \"%s\"\n", default_sta_cmd); + log("\n"); + log(" -sdc-in \n"); + log(" expand SDC file \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 args, RTLIL::Design *design) override + { + string run_from, run_to; + 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; + } + // repetitive boring bit + 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; + } + 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; + std::string liberty_path; + std::string verilog_path; + + run("design -save pre_expand"); + run("proc"); + run("memory"); + // run("dfflegalize -cell $dff"); + + std::string write_verilog_cmd = "write_verilog -norename -noexpr -attr2comment -defparam "; + if (help_mode) { + run(write_verilog_cmd + "/elaborated.v"); + } 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); + verilog_path = tempdir_name + "/elaborated.v"; + 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"); + + if (help_mode) { + run("icell_liberty /yosys.lib"); + } else { + liberty_path = tempdir_name + "/yosys.lib"; + run(stringf("icell_liberty %s", liberty_path.c_str())); + } + + std::string opensta_pass_call = "opensta -exe "; + opensta_pass_call += help_mode ? "" : opensta_exe; + opensta_pass_call += " -sdc-in "; + opensta_pass_call += help_mode ? "" : sdc_filename; + opensta_pass_call += " -sdc-out "; + opensta_pass_call += help_mode ? "" : sdc_expanded_filename; + opensta_pass_call += " -verilog "; + opensta_pass_call += help_mode ? "" : verilog_path; + opensta_pass_call += " -liberty "; + opensta_pass_call += help_mode ? "/yosys.lib" : liberty_path; + if (!cleanup) + opensta_pass_call += " -nocleanup"; + run(opensta_pass_call.c_str()); + + 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 From 0c4105d72c3d5b512176b75a8c825307cd95debb Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 31 Jul 2025 15:38:45 +0200 Subject: [PATCH 070/153] opensta: quiet net width mismatch warning --- techlibs/common/opensta.cc | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/techlibs/common/opensta.cc b/techlibs/common/opensta.cc index 1b3571973..bfd36c380 100644 --- a/techlibs/common/opensta.cc +++ b/techlibs/common/opensta.cc @@ -99,8 +99,13 @@ struct OpenstaPass : public Pass 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; - int ret = run_command(command); + std::string command = opensta_exe + " -exit " + script_filename; + auto process_line = [](const std::string &line) { + 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 return %d (error)\n", ret); else From 7bed6ec658e2f9137131dca03b8a7b55a5d843a6 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 2 Oct 2025 15:26:24 +0200 Subject: [PATCH 071/153] opensta: quiet blackbox warning --- techlibs/common/opensta.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/techlibs/common/opensta.cc b/techlibs/common/opensta.cc index bfd36c380..0a93a915c 100644 --- a/techlibs/common/opensta.cc +++ b/techlibs/common/opensta.cc @@ -101,6 +101,8 @@ struct OpenstaPass : public Pass 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()); From 793594bd5973d2f48100aaec7c67c93d439d9956 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 2 Oct 2025 16:45:32 +0200 Subject: [PATCH 072/153] sdc_expand: log header --- techlibs/common/sdc_expand.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/techlibs/common/sdc_expand.cc b/techlibs/common/sdc_expand.cc index 7efaf8c0d..baa281dd7 100644 --- a/techlibs/common/sdc_expand.cc +++ b/techlibs/common/sdc_expand.cc @@ -35,6 +35,7 @@ struct SdcexpandPass : public ScriptPass bool cleanup; void execute(std::vector args, RTLIL::Design *design) override { + log_header(design, "Executing SDC_EXPAND pass.\n"); string run_from, run_to; cleanup = true; From 7bc88d5c4098255a8be65cba16c0e12b35c8d0e5 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 8 Oct 2025 16:11:03 +0200 Subject: [PATCH 073/153] sdc_expand: cleanup --- techlibs/common/sdc_expand.cc | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/techlibs/common/sdc_expand.cc b/techlibs/common/sdc_expand.cc index baa281dd7..2fd5f380e 100644 --- a/techlibs/common/sdc_expand.cc +++ b/techlibs/common/sdc_expand.cc @@ -32,12 +32,12 @@ struct SdcexpandPass : public ScriptPass } string opensta_exe, sdc_filename, sdc_expanded_filename; - bool cleanup; + bool cleanup = true; void execute(std::vector args, RTLIL::Design *design) override { log_header(design, "Executing SDC_EXPAND pass.\n"); string run_from, run_to; - cleanup = true; + opensta_exe = "sta"; size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) @@ -58,7 +58,6 @@ struct SdcexpandPass : public ScriptPass cleanup = false; continue; } - // repetitive boring bit if (args[argidx] == "-run" && argidx+1 < args.size()) { size_t pos = args[argidx+1].find(':'); if (pos == std::string::npos) { @@ -72,6 +71,12 @@ struct SdcexpandPass : public ScriptPass } 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()) @@ -88,17 +93,14 @@ struct SdcexpandPass : public ScriptPass void script() override { std::string tempdir_name; - std::string liberty_path; - std::string verilog_path; run("design -save pre_expand"); run("proc"); run("memory"); // run("dfflegalize -cell $dff"); - std::string write_verilog_cmd = "write_verilog -norename -noexpr -attr2comment -defparam "; if (help_mode) { - run(write_verilog_cmd + "/elaborated.v"); + tempdir_name = ""; } else { if (cleanup) tempdir_name = get_base_tmpdir() + "/"; @@ -106,21 +108,18 @@ struct SdcexpandPass : public ScriptPass tempdir_name = "_tmp_"; tempdir_name += proc_program_prefix() + "yosys-sdc_expand-XXXXXX"; tempdir_name = make_temp_dir(tempdir_name); - verilog_path = tempdir_name + "/elaborated.v"; - run(write_verilog_cmd + verilog_path); } + 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"); - if (help_mode) { - run("icell_liberty /yosys.lib"); - } else { - liberty_path = tempdir_name + "/yosys.lib"; - run(stringf("icell_liberty %s", liberty_path.c_str())); - } + std::string liberty_path = tempdir_name + "/yosys.lib"; + run("icell_liberty " + liberty_path); std::string opensta_pass_call = "opensta -exe "; opensta_pass_call += help_mode ? "" : opensta_exe; @@ -134,7 +133,7 @@ struct SdcexpandPass : public ScriptPass opensta_pass_call += help_mode ? "/yosys.lib" : liberty_path; if (!cleanup) opensta_pass_call += " -nocleanup"; - run(opensta_pass_call.c_str()); + run(opensta_pass_call); if (!help_mode) { if (cleanup) { From 9f07e21e35558200fc180c98bf301942bdc6861c Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 8 Oct 2025 16:25:47 +0200 Subject: [PATCH 074/153] rtlil: undeprecate builtin_ff_cell_types --- kernel/rtlil.h | 1 - 1 file changed, 1 deletion(-) diff --git a/kernel/rtlil.h b/kernel/rtlil.h index a8e43fe1d..4da030e8d 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -728,7 +728,6 @@ template <> struct IDMacroHelper<-1> { namespace RTLIL { extern dict constpad; - [[deprecated("Call cell->is_builtin_ff() instead")]] const pool &builtin_ff_cell_types(); static inline std::string escape_id(const std::string &str) { From cee3d0b598046f6410f417891f5942372a1a09d6 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 14 Nov 2025 13:23:14 +0100 Subject: [PATCH 075/153] icell_liberty: refactor and add help --- passes/cmds/icell_liberty.cc | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/passes/cmds/icell_liberty.cc b/passes/cmds/icell_liberty.cc index 83252ffb4..01e25b9e9 100644 --- a/passes/cmds/icell_liberty.cc +++ b/passes/cmds/icell_liberty.cc @@ -112,12 +112,6 @@ struct LibertyStubber { auto next_state = ffType.has_ce ? "D & EN" : "D"; i.item("next_state", next_state); f << "\t\t}\n"; - - - // bool has_aload; - // bool has_srst; - // bool has_arst; - // bool has_sr; f << "\t}\n"; } void liberty_cell(Module* base, Module* derived, std::ostream& f) @@ -157,8 +151,11 @@ struct IcellLiberty : Pass { { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); - log(" icell_liberty \n"); // TODO + log(" icell_liberty \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 args, RTLIL::Design *d) override @@ -168,7 +165,7 @@ struct IcellLiberty : Pass { size_t argidx; IdString naming_attr; std::string liberty_filename; - std::ofstream* liberty_file = new std::ofstream; + auto liberty_file = std::make_unique(); for (argidx = 1; argidx < args.size(); argidx++) { break; @@ -176,15 +173,12 @@ struct IcellLiberty : Pass { if (argidx < args.size()) liberty_filename = args[argidx++]; else - log_error("no Liberty filename specified\n"); - - // extra_args(args, argidx, d); + log_cmd_error("no Liberty filename specified\n"); if (liberty_filename.size()) { liberty_file->open(liberty_filename.c_str()); if (liberty_file->fail()) { - delete liberty_file; - log_error("Can't open file `%s' for writing: %s\n", liberty_filename.c_str(), strerror(errno)); + log_cmd_error("Can't open file `%s' for writing: %s\n", liberty_filename.c_str(), strerror(errno)); } } @@ -209,7 +203,6 @@ struct IcellLiberty : Pass { if (liberty_file) { stubber.liberty_suffix(*liberty_file); - delete liberty_file; } } } IcellLiberty; From 5acb77cab1a9b1c67f5f978771c9d336756f4293 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 14 Nov 2025 13:30:07 +0100 Subject: [PATCH 076/153] sdc_expand, opensta: typos --- techlibs/common/opensta.cc | 2 +- techlibs/common/sdc_expand.cc | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/techlibs/common/opensta.cc b/techlibs/common/opensta.cc index 0a93a915c..7f2b15243 100644 --- a/techlibs/common/opensta.cc +++ b/techlibs/common/opensta.cc @@ -109,7 +109,7 @@ struct OpenstaPass : public Pass }; int ret = run_command(command, process_line); if (ret) - log_error("OpenSTA return %d (error)\n", ret); + log_error("OpenSTA returned %d (error)\n", ret); else log("sdc_expanded_filename: %s\n", sdc_expanded_filename.c_str()); diff --git a/techlibs/common/sdc_expand.cc b/techlibs/common/sdc_expand.cc index 2fd5f380e..1b13923f0 100644 --- a/techlibs/common/sdc_expand.cc +++ b/techlibs/common/sdc_expand.cc @@ -4,10 +4,10 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -struct SdcexpandPass : public ScriptPass +struct SdcExpandPass : public ScriptPass { const char* default_sta_cmd = "sta"; - SdcexpandPass() : ScriptPass("sdc_expand", "run OpenSTA") { } + SdcExpandPass() : ScriptPass("sdc_expand", "run OpenSTA") { } void help() override { @@ -145,6 +145,6 @@ struct SdcexpandPass : public ScriptPass } run("design -load pre_expand"); } -} SdcexpandPass; +} SdcExpandPass; PRIVATE_NAMESPACE_END From 6846168db3e634a737cd1349458de35d55a5e8c2 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 14 Nov 2025 13:35:51 +0100 Subject: [PATCH 077/153] opensta: opensta.exe scratchpad variable --- techlibs/common/opensta.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/techlibs/common/opensta.cc b/techlibs/common/opensta.cc index 7f2b15243..fbdf27304 100644 --- a/techlibs/common/opensta.cc +++ b/techlibs/common/opensta.cc @@ -35,7 +35,7 @@ struct OpenstaPass : public Pass void execute(std::vector args, RTLIL::Design *design) override { string run_from, run_to; - string opensta_exe = "sta"; + string opensta_exe = design->scratchpad_get_string("opensta.exe", "sta"); string sdc_filename, sdc_expanded_filename; string tempdir_name, script_filename; string verilog_filename, liberty_filename; From 9a5465bc83c568b7890bbd5b506a642bba8ebe5c Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 14 Nov 2025 13:37:19 +0100 Subject: [PATCH 078/153] icell_liberty: simplify --- passes/cmds/icell_liberty.cc | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/passes/cmds/icell_liberty.cc b/passes/cmds/icell_liberty.cc index 01e25b9e9..d49cd360a 100644 --- a/passes/cmds/icell_liberty.cc +++ b/passes/cmds/icell_liberty.cc @@ -185,8 +185,7 @@ struct IcellLiberty : Pass { pool done; LibertyStubber stubber = {}; - if (liberty_file) - stubber.liberty_prefix(*liberty_file); + stubber.liberty_prefix(*liberty_file); for (auto module : d->selected_modules()) { for (auto cell : module->selected_cells()) { @@ -201,9 +200,7 @@ struct IcellLiberty : Pass { } } - if (liberty_file) { - stubber.liberty_suffix(*liberty_file); - } + stubber.liberty_suffix(*liberty_file); } } IcellLiberty; From a5b6c3cc191c2f18f52f45c1e34588a7bbe639b4 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 14 Nov 2025 13:44:12 +0100 Subject: [PATCH 079/153] opensta, sdc_expand: more scratchpad --- techlibs/common/opensta.cc | 2 +- techlibs/common/sdc_expand.cc | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/techlibs/common/opensta.cc b/techlibs/common/opensta.cc index fbdf27304..6d793b3e1 100644 --- a/techlibs/common/opensta.cc +++ b/techlibs/common/opensta.cc @@ -39,7 +39,7 @@ struct OpenstaPass : public Pass string sdc_filename, sdc_expanded_filename; string tempdir_name, script_filename; string verilog_filename, liberty_filename; - bool cleanup = true; + bool cleanup = design->scratchpad_get_bool("opensta.cleanup", true); log_header(design, "Executing OPENSTA pass.\n"); log_push(); diff --git a/techlibs/common/sdc_expand.cc b/techlibs/common/sdc_expand.cc index 1b13923f0..cec5e8c1e 100644 --- a/techlibs/common/sdc_expand.cc +++ b/techlibs/common/sdc_expand.cc @@ -32,12 +32,13 @@ struct SdcExpandPass : public ScriptPass } string opensta_exe, sdc_filename, sdc_expanded_filename; - bool cleanup = true; + bool cleanup; void execute(std::vector args, RTLIL::Design *design) override { log_header(design, "Executing SDC_EXPAND pass.\n"); string run_from, run_to; - opensta_exe = "sta"; + opensta_exe = ""; + cleanup = design->scratchpad_get_bool("sdc_expand.cleanup", true); size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) @@ -121,8 +122,11 @@ struct SdcExpandPass : public ScriptPass std::string liberty_path = tempdir_name + "/yosys.lib"; run("icell_liberty " + liberty_path); - std::string opensta_pass_call = "opensta -exe "; - opensta_pass_call += help_mode ? "" : opensta_exe; + std::string opensta_pass_call = "opensta "; + if (opensta_exe.length()) { + opensta_pass_call += "-exe "; + opensta_pass_call += help_mode ? "" : opensta_exe; + } opensta_pass_call += " -sdc-in "; opensta_pass_call += help_mode ? "" : sdc_filename; opensta_pass_call += " -sdc-out "; From 411fc149df9d727f561ca04e2149e1dc4af2abf3 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 14 Nov 2025 16:06:46 +0100 Subject: [PATCH 080/153] opensta: refactor default command --- techlibs/common/opensta.cc | 6 +++--- techlibs/common/opensta.h | 8 ++++++++ techlibs/common/sdc_expand.cc | 4 ++-- 3 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 techlibs/common/opensta.h diff --git a/techlibs/common/opensta.cc b/techlibs/common/opensta.cc index 6d793b3e1..2a806edf6 100644 --- a/techlibs/common/opensta.cc +++ b/techlibs/common/opensta.cc @@ -1,5 +1,6 @@ #include "kernel/rtlil.h" #include "kernel/log.h" +#include "techlibs/common/opensta.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -7,7 +8,6 @@ PRIVATE_NAMESPACE_BEGIN #if !defined(YOSYS_DISABLE_SPAWN) struct OpenstaPass : public Pass { - const char* default_sta_cmd = "sta"; OpenstaPass() : Pass("opensta", "run OpenSTA") { } void help() override @@ -19,7 +19,7 @@ struct OpenstaPass : public Pass // TOOD log("\n"); log(" -exe \n"); - log(" use to run OpenSTA instead of \"%s\"\n", default_sta_cmd); + log(" use to run OpenSTA instead of \"%s\"\n", default_opensta_cmd); log("\n"); log(" -sdc-in \n"); log(" expand SDC input file \n"); @@ -35,7 +35,7 @@ struct OpenstaPass : public Pass void execute(std::vector args, RTLIL::Design *design) override { string run_from, run_to; - string opensta_exe = design->scratchpad_get_string("opensta.exe", "sta"); + 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; diff --git a/techlibs/common/opensta.h b/techlibs/common/opensta.h new file mode 100644 index 000000000..4c5ac871f --- /dev/null +++ b/techlibs/common/opensta.h @@ -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 */ diff --git a/techlibs/common/sdc_expand.cc b/techlibs/common/sdc_expand.cc index cec5e8c1e..95254d1f4 100644 --- a/techlibs/common/sdc_expand.cc +++ b/techlibs/common/sdc_expand.cc @@ -1,12 +1,12 @@ #include "kernel/rtlil.h" #include "kernel/log.h" +#include "techlibs/common/opensta.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN struct SdcExpandPass : public ScriptPass { - const char* default_sta_cmd = "sta"; SdcExpandPass() : ScriptPass("sdc_expand", "run OpenSTA") { } void help() override @@ -18,7 +18,7 @@ struct SdcExpandPass : public ScriptPass // TODO log("\n"); log(" -exe \n"); - log(" use to run OpenSTA instead of \"%s\"\n", default_sta_cmd); + log(" use to run OpenSTA instead of \"%s\"\n", default_opensta_cmd); log("\n"); log(" -sdc-in \n"); log(" expand SDC file \n"); From 85d2702ef6b78dbcc6138cc8745f6bd796fa2d23 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 14 Nov 2025 16:16:33 +0100 Subject: [PATCH 081/153] opensta, sdc_expand: fix help --- techlibs/common/opensta.cc | 4 +++- techlibs/common/sdc_expand.cc | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/techlibs/common/opensta.cc b/techlibs/common/opensta.cc index 2a806edf6..282481953 100644 --- a/techlibs/common/opensta.cc +++ b/techlibs/common/opensta.cc @@ -16,7 +16,9 @@ struct OpenstaPass : public Pass log("\n"); log(" opensta [options]\n"); log("\n"); - // TOOD + 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 \n"); log(" use to run OpenSTA instead of \"%s\"\n", default_opensta_cmd); diff --git a/techlibs/common/sdc_expand.cc b/techlibs/common/sdc_expand.cc index 95254d1f4..fe8f20aa9 100644 --- a/techlibs/common/sdc_expand.cc +++ b/techlibs/common/sdc_expand.cc @@ -7,7 +7,7 @@ PRIVATE_NAMESPACE_BEGIN struct SdcExpandPass : public ScriptPass { - SdcExpandPass() : ScriptPass("sdc_expand", "run OpenSTA") { } + SdcExpandPass() : ScriptPass("sdc_expand", "expand SDC design with opensta") { } void help() override { @@ -15,7 +15,8 @@ struct SdcExpandPass : public ScriptPass log("\n"); log(" sdc_expand [options]\n"); log("\n"); - // TODO + log("Expand SDC file with opensta based on arbitrary current design.\n"); + log("Changes the design but only temporarily.\n"); log("\n"); log(" -exe \n"); log(" use to run OpenSTA instead of \"%s\"\n", default_opensta_cmd); From c281bac461ee64157e66134cee3828856d99c5c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Ji=C5=99=C3=AD=20Tywoniak?= Date: Fri, 2 May 2025 17:48:33 +0200 Subject: [PATCH 082/153] sdc: add initial stubbed demo --- kernel/driver.cc | 4 ++-- kernel/tclapi.cc | 60 +++++++++++++++++++++++++++++++++++++++++++++++- kernel/yosys.cc | 34 +++++++++++++++++++++++++-- 3 files changed, 93 insertions(+), 5 deletions(-) diff --git a/kernel/driver.cc b/kernel/driver.cc index 726e25302..4097411b4 100644 --- a/kernel/driver.cc +++ b/kernel/driver.cc @@ -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 diff --git a/kernel/tclapi.cc b/kernel/tclapi.cc index a2ebaffa2..a25aa4c0c 100644 --- a/kernel/tclapi.cc +++ b/kernel/tclapi.cc @@ -533,7 +533,65 @@ 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) +static int sdc_get_pins_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + (void)interp; + bool hierarchical_flag = false; + bool regexp_flag = false; + bool nocase_flag = false; + std::string separator = "/"; + Tcl_Obj* of_objects; + std::vector patterns; + int i = 1; + for (; i < objc; i++) { + FLAG2(hierarchical) + FLAG2(regexp) + FLAG2(nocase) + if (!strcmp(Tcl_GetString(objv[i]), "-hsc")) { + i++; + separator = Tcl_GetString(objv[i]); + continue; + } + if (!strcmp(Tcl_GetString(objv[i]), "-of_objects")) { + i++; + of_objects = objv[i]; + continue; + } + // Onto the next loop + break; + } + for (; i < objc; i++) { + patterns.push_back(Tcl_GetString(objv[i])); + } + log("get_pins patterns:\n"); + for (auto pat : patterns) { + log("\t%s\n", pat.c_str()); + } + (void)hierarchical_flag; + (void)regexp_flag; + (void)nocase_flag; + (void)separator; + (void)of_objects; + return TCL_OK; +} +static int sdc_dummy_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) +{ + (void)interp; + (void)objc; + log("Ignoring known SDC command %s\n", Tcl_GetString(objv[0])); + return TCL_OK; +} + +int yosys_sdc_interp_init(Tcl_Interp *interp) +{ + if (Tcl_Init(interp)!=TCL_OK) + log_warning("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); + Tcl_CreateObjCommand(interp, "set_false_path", sdc_dummy_cmd, NULL, NULL); + Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, NULL, NULL); + return TCL_OK; +} + +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())); diff --git a/kernel/yosys.cc b/kernel/yosys.cc index 4e4abed20..cab1e3ee7 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -95,6 +95,7 @@ CellTypes yosys_celltypes; #ifdef YOSYS_ENABLE_TCL Tcl_Interp *yosys_tcl_interp = NULL; +Tcl_Interp *yosys_sdc_interp = NULL; #endif std::set yosys_input_files, yosys_output_files; @@ -393,16 +394,25 @@ 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 int yosys_sdc_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; } +extern Tcl_Interp *yosys_get_sdc_interp() +{ + if (yosys_sdc_interp == NULL) { + yosys_sdc_interp = Tcl_CreateInterp(); + yosys_sdc_interp_init(yosys_sdc_interp); + } + return yosys_sdc_interp; +} struct TclPass : public Pass { TclPass() : Pass("tcl", "execute a TCL script file") { } @@ -446,6 +456,26 @@ struct TclPass : public Pass { Tcl_Release(interp); } } TclPass; +struct SdcPass : public Pass { + SdcPass() : Pass("sdc", "sniff at some SDC") { } + void execute(std::vector args, RTLIL::Design *) override { + if (args.size() < 2) + log_cmd_error("Missing script file.\n"); + + std::vector script_args; + for (auto it = args.begin() + 2; it != args.end(); ++it) + script_args.push_back(Tcl_NewStringObj((*it).c_str(), (*it).size())); + + Tcl_Interp *interp = yosys_get_sdc_interp(); + Tcl_Preserve(interp); + Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argc", 4), NULL, Tcl_NewIntObj(script_args.size()), 0); + Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argv", 4), NULL, Tcl_NewListObj(script_args.size(), script_args.data()), 0); + Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argv0", 5), NULL, Tcl_NewStringObj(args[1].c_str(), args[1].size()), 0); + if (Tcl_EvalFile(interp, args[1].c_str()) != TCL_OK) + log_cmd_error("SDC interpreter returned an error: %s\n", Tcl_GetStringResult(interp)); + Tcl_Release(interp); + } +} SdcPass; #endif #if defined(__linux__) || defined(__CYGWIN__) From 5a798b64ef19122c3539eb208afcbf757dff56f8 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Mon, 26 May 2025 22:53:16 +0200 Subject: [PATCH 083/153] sdc: separate out --- kernel/tclapi.cc | 58 -------------------- kernel/yosys.cc | 29 +--------- passes/cmds/Makefile.inc | 3 ++ passes/cmds/sdc.cc | 112 +++++++++++++++++++++++++++++++++++++++ passes/cmds/stubs.sdc | 1 + 5 files changed, 117 insertions(+), 86 deletions(-) create mode 100644 passes/cmds/sdc.cc create mode 100644 passes/cmds/stubs.sdc diff --git a/kernel/tclapi.cc b/kernel/tclapi.cc index a25aa4c0c..ec0483a4a 100644 --- a/kernel/tclapi.cc +++ b/kernel/tclapi.cc @@ -533,64 +533,6 @@ static int tcl_set_param(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj *cons return TCL_OK; } -static int sdc_get_pins_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) -{ - (void)interp; - bool hierarchical_flag = false; - bool regexp_flag = false; - bool nocase_flag = false; - std::string separator = "/"; - Tcl_Obj* of_objects; - std::vector patterns; - int i = 1; - for (; i < objc; i++) { - FLAG2(hierarchical) - FLAG2(regexp) - FLAG2(nocase) - if (!strcmp(Tcl_GetString(objv[i]), "-hsc")) { - i++; - separator = Tcl_GetString(objv[i]); - continue; - } - if (!strcmp(Tcl_GetString(objv[i]), "-of_objects")) { - i++; - of_objects = objv[i]; - continue; - } - // Onto the next loop - break; - } - for (; i < objc; i++) { - patterns.push_back(Tcl_GetString(objv[i])); - } - log("get_pins patterns:\n"); - for (auto pat : patterns) { - log("\t%s\n", pat.c_str()); - } - (void)hierarchical_flag; - (void)regexp_flag; - (void)nocase_flag; - (void)separator; - (void)of_objects; - return TCL_OK; -} -static int sdc_dummy_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) -{ - (void)interp; - (void)objc; - log("Ignoring known SDC command %s\n", Tcl_GetString(objv[0])); - return TCL_OK; -} - -int yosys_sdc_interp_init(Tcl_Interp *interp) -{ - if (Tcl_Init(interp)!=TCL_OK) - log_warning("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); - Tcl_CreateObjCommand(interp, "set_false_path", sdc_dummy_cmd, NULL, NULL); - Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, NULL, NULL); - return TCL_OK; -} - int yosys_tcl_interp_init(Tcl_Interp *interp) { if (Tcl_Init(interp)!=TCL_OK) diff --git a/kernel/yosys.cc b/kernel/yosys.cc index cab1e3ee7..009da56b9 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -395,7 +395,6 @@ void rewrite_filename(std::string &filename) // defined in tclapi.cc extern int yosys_tcl_interp_init(Tcl_Interp *interp); -extern int yosys_sdc_interp_init(Tcl_Interp *interp); extern Tcl_Interp *yosys_get_tcl_interp() { @@ -405,15 +404,8 @@ extern Tcl_Interp *yosys_get_tcl_interp() } return yosys_tcl_interp; } -extern Tcl_Interp *yosys_get_sdc_interp() -{ - if (yosys_sdc_interp == NULL) { - yosys_sdc_interp = Tcl_CreateInterp(); - yosys_sdc_interp_init(yosys_sdc_interp); - } - return yosys_sdc_interp; -} +// Also see SdcPass struct TclPass : public Pass { TclPass() : Pass("tcl", "execute a TCL script file") { } void help() override { @@ -456,26 +448,7 @@ struct TclPass : public Pass { Tcl_Release(interp); } } TclPass; -struct SdcPass : public Pass { - SdcPass() : Pass("sdc", "sniff at some SDC") { } - void execute(std::vector args, RTLIL::Design *) override { - if (args.size() < 2) - log_cmd_error("Missing script file.\n"); - std::vector script_args; - for (auto it = args.begin() + 2; it != args.end(); ++it) - script_args.push_back(Tcl_NewStringObj((*it).c_str(), (*it).size())); - - Tcl_Interp *interp = yosys_get_sdc_interp(); - Tcl_Preserve(interp); - Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argc", 4), NULL, Tcl_NewIntObj(script_args.size()), 0); - Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argv", 4), NULL, Tcl_NewListObj(script_args.size(), script_args.data()), 0); - Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argv0", 5), NULL, Tcl_NewStringObj(args[1].c_str(), args[1].size()), 0); - if (Tcl_EvalFile(interp, args[1].c_str()) != TCL_OK) - log_cmd_error("SDC interpreter returned an error: %s\n", Tcl_GetStringResult(interp)); - Tcl_Release(interp); - } -} SdcPass; #endif #if defined(__linux__) || defined(__CYGWIN__) diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index 2dbeadac2..f64fdea55 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -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/sdc.o + +$(eval $(call add_share_file,share/sdc,passes/cmds/stubs.sdc)) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc new file mode 100644 index 000000000..539b349f7 --- /dev/null +++ b/passes/cmds/sdc.cc @@ -0,0 +1,112 @@ +#include "kernel/register.h" +#include "kernel/celltypes.h" +#include "kernel/rtlil.h" +#include "kernel/log.h" +#include + + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +template +static bool parse_flag(char* arg, const char* flag_name, T& flag_var) { + std::string expected = std::string("-") + flag_name; + if (expected == arg) { + flag_var = true; + return true; + } + return false; +} + +static int sdc_get_pins_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) +{ + (void)interp; + bool hierarchical_flag = false; + bool regexp_flag = false; + bool nocase_flag = false; + std::string separator = "/"; + Tcl_Obj* of_objects; + std::vector patterns; + int i = 1; + for (; i < objc; i++) { + if (parse_flag(Tcl_GetString(objv[i]), "hierarchical", hierarchical_flag)) + if (parse_flag(Tcl_GetString(objv[i]), "regexp", regexp_flag)) continue; + if (parse_flag(Tcl_GetString(objv[i]), "nocase", nocase_flag)) continue; + if (!strcmp(Tcl_GetString(objv[i]), "-hsc")) { + separator = Tcl_GetString(objv[++i]); + continue; + } + if (!strcmp(Tcl_GetString(objv[i]), "-of_objects")) { + of_objects = objv[++i]; + continue; + } + // Onto the next loop + break; + } + for (; i < objc; i++) { + patterns.push_back(Tcl_GetString(objv[i])); + } + log("get_pins patterns:\n"); + for (auto pat : patterns) { + log("\t%s\n", pat.c_str()); + } + (void)hierarchical_flag; + (void)regexp_flag; + (void)nocase_flag; + (void)separator; + (void)of_objects; + return TCL_OK; +} + +class SDCInterpreter +{ +private: + Tcl_Interp* interp = nullptr; +public: + ~SDCInterpreter() { + if (interp) + Tcl_DeleteInterp(interp); + } + static SDCInterpreter& get() { + static SDCInterpreter instance; + return instance; + } + Tcl_Interp* fresh_interp() { + if (interp) + Tcl_DeleteInterp(interp); + interp = Tcl_CreateInterp(); + if (Tcl_Init(interp)!=TCL_OK) + log_warning("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); + Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, NULL, NULL); + return interp; + } +}; + +// Also see TclPass +struct SdcPass : public Pass { + SdcPass() : Pass("sdc", "sniff at some SDC") { } + void execute(std::vector args, RTLIL::Design *) override { + if (args.size() < 2) + log_cmd_error("Missing script file.\n"); + + std::vector script_args; + for (auto it = args.begin() + 2; it != args.end(); ++it) + script_args.push_back(Tcl_NewStringObj((*it).c_str(), (*it).size())); + + Tcl_Interp *interp = SDCInterpreter::get().fresh_interp(); + Tcl_Preserve(interp); + Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argc", 4), NULL, Tcl_NewIntObj(script_args.size()), 0); + Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argv", 4), NULL, Tcl_NewListObj(script_args.size(), script_args.data()), 0); + Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argv0", 5), NULL, Tcl_NewStringObj(args[1].c_str(), args[1].size()), 0); + + std::string stub_path = "+/sdc/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 file: %s\n", Tcl_GetStringResult(interp)); + if (Tcl_EvalFile(interp, args[1].c_str()) != TCL_OK) + log_cmd_error("SDC interpreter returned an error: %s\n", Tcl_GetStringResult(interp)); + Tcl_Release(interp); + } +} SdcPass; + +YOSYS_NAMESPACE_END diff --git a/passes/cmds/stubs.sdc b/passes/cmds/stubs.sdc new file mode 100644 index 000000000..bf41c4620 --- /dev/null +++ b/passes/cmds/stubs.sdc @@ -0,0 +1 @@ +proc set_false_path {args} {} \ No newline at end of file From c1c6ec12662c7a3330910f035c647fe6bce2df42 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Tue, 27 May 2025 11:33:32 +0200 Subject: [PATCH 084/153] sdc: stubs SDC commands supported by Vivado --- passes/cmds/stubs.sdc | 106 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/passes/cmds/stubs.sdc b/passes/cmds/stubs.sdc index bf41c4620..9f5d03b87 100644 --- a/passes/cmds/stubs.sdc +++ b/passes/cmds/stubs.sdc @@ -1 +1,105 @@ -proc set_false_path {args} {} \ No newline at end of file +proc stub {function_name} { + proc $function_name {args} {} +} + +# Vivado UG903 +stub add_cells_to_pblock +stub add_to_power_rail +stub all_clocks +stub all_cpus +stub all_dsps +stub all_fanin +stub all_fanout +stub all_ffs +stub all_hsios +stub all_inputs +stub all_latches +stub all_outputs +stub all_rams +stub all_registers +stub connect_debug_port +stub create_clock +stub create_debug_core +stub create_debug_port +stub create_generated_clock +stub create_macro +stub create_pblock +stub create_power_rail +stub create_property +stub create_waiver +stub current_design +stub current_instance +stub delete_macros +stub delete_pblock +stub delete_power_rails +stub endgroup +stub get_bel_pins +stub get_bels +stub get_cells +stub get_clocks +stub get_debug_cores +stub get_debug_ports +stub get_generated_clocks +stub get_hierarchy_separator +stub get_iobanks +stub get_macros +stub get_nets +stub get_nodes +stub get_package_pins +stub get_path_groups +stub get_pblocks +stub get_pins +stub get_pips +stub get_pkgpin_bytegroups +stub get_pkgpin_nibbles +stub get_ports +stub get_power_rails +stub get_property +stub get_site_pins +stub get_site_pips +stub get_sites +stub get_slrs +stub get_speed_models +stub get_tiles +stub get_timing_arcs +stub get_wires +stub group_path +stub make_diff_pair_ports +stub remove_cells_from_pblock +stub remove_from_power_rail +stub reset_operating_conditions +stub reset_switching_activity +stub resize_pblock +stub set_bus_skew +stub set_case_analysis +stub set_clock_groups +stub set_clock_latency +stub set_clock_sense +stub set_clock_uncertainty +stub set_data_check +stub set_disable_timing +stub set_external_delay +stub set_false_path +stub set_hierarchy_separator +stub set_input_delay +stub set_input_jitter +stub set_load +stub set_logic_dc +stub set_logic_one +stub set_logic_unconnected +stub set_logic_zero +stub set_max_delay +stub set_max_time_borrow +stub set_min_delay +stub set_multicycle_path +stub set_operating_conditions +stub set_output_delay +stub set_package_pin_val +stub set_power_opt +stub set_propagated_clock +stub set_property +stub set_switching_activity +stub set_system_jitter +stub set_units +stub startgroup +stub update_macro From c26aa3186de8b7ccb9b27985580790b441fc6ff7 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Tue, 27 May 2025 17:14:23 +0200 Subject: [PATCH 085/153] sdc: collect design objects --- passes/cmds/sdc.cc | 202 +++++++++++++++++++++++++++++++++--------- passes/cmds/stubs.sdc | 14 +-- tests/various/sdc.sdc | 5 ++ 3 files changed, 174 insertions(+), 47 deletions(-) create mode 100644 tests/various/sdc.sdc diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index 539b349f7..0343c6b09 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -3,6 +3,7 @@ #include "kernel/rtlil.h" #include "kernel/log.h" #include +#include USING_YOSYS_NAMESPACE @@ -10,17 +11,22 @@ PRIVATE_NAMESPACE_BEGIN template static bool parse_flag(char* arg, const char* flag_name, T& flag_var) { - std::string expected = std::string("-") + flag_name; - if (expected == arg) { - flag_var = true; - return true; - } - return false; + std::string expected = std::string("-") + flag_name; + if (expected == arg) { + flag_var = true; + return true; + } + return false; } +// TODO return values like json_to_tcl on result.json? +// TODO vectors +// TODO cell arrays? + static int sdc_get_pins_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { (void)interp; + // When this flag is present, the search for the pattern is made in all positions in the hierarchy. bool hierarchical_flag = false; bool regexp_flag = false; bool nocase_flag = false; @@ -29,9 +35,10 @@ static int sdc_get_pins_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj* c std::vector patterns; int i = 1; for (; i < objc; i++) { - if (parse_flag(Tcl_GetString(objv[i]), "hierarchical", hierarchical_flag)) - if (parse_flag(Tcl_GetString(objv[i]), "regexp", regexp_flag)) continue; - if (parse_flag(Tcl_GetString(objv[i]), "nocase", nocase_flag)) continue; + if (parse_flag(Tcl_GetString(objv[i]), "hierarchical", hierarchical_flag)) continue; + if (parse_flag(Tcl_GetString(objv[i]), "hier", hierarchical_flag)) continue; + if (parse_flag(Tcl_GetString(objv[i]), "regexp", regexp_flag)) continue; + if (parse_flag(Tcl_GetString(objv[i]), "nocase", nocase_flag)) continue; if (!strcmp(Tcl_GetString(objv[i]), "-hsc")) { separator = Tcl_GetString(objv[++i]); continue; @@ -53,56 +60,167 @@ static int sdc_get_pins_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj* c (void)hierarchical_flag; (void)regexp_flag; (void)nocase_flag; - (void)separator; (void)of_objects; + if (separator != "/") { + Tcl_SetObjResult(interp, Tcl_NewStringObj("Only '/' accepted as separator", -1)); + return TCL_ERROR; + } + return TCL_OK; +} + +static int sdc_get_ports_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) +{ + (void)interp; + bool regexp_flag = false; + bool nocase_flag = false; + std::vector patterns; + int i = 1; + for (; i < objc; i++) { + if (parse_flag(Tcl_GetString(objv[i]), "regexp", regexp_flag)) continue; + if (parse_flag(Tcl_GetString(objv[i]), "nocase", nocase_flag)) continue; + // Onto the next loop + break; + } + for (; i < objc; i++) { + patterns.push_back(Tcl_GetString(objv[i])); + } + log("get_ports patterns:\n"); + for (auto pat : patterns) { + log("\t%s\n", pat.c_str()); + } + (void)regexp_flag; + (void)nocase_flag; return TCL_OK; } class SDCInterpreter { private: - Tcl_Interp* interp = nullptr; + Tcl_Interp* interp = nullptr; public: - ~SDCInterpreter() { - if (interp) - Tcl_DeleteInterp(interp); - } - static SDCInterpreter& get() { - static SDCInterpreter instance; - return instance; - } - Tcl_Interp* fresh_interp() { - if (interp) - Tcl_DeleteInterp(interp); - interp = Tcl_CreateInterp(); - if (Tcl_Init(interp)!=TCL_OK) - log_warning("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); - Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, NULL, NULL); - return interp; - } + ~SDCInterpreter() { + if (interp) + Tcl_DeleteInterp(interp); + } + static SDCInterpreter& get() { + static SDCInterpreter instance; + return instance; + } + Tcl_Interp* fresh_interp() { + if (interp) + Tcl_DeleteInterp(interp); + interp = Tcl_CreateInterp(); + if (Tcl_Init(interp)!=TCL_OK) + log_warning("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); + Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, NULL, NULL); + Tcl_CreateObjCommand(interp, "get_ports", sdc_get_ports_cmd, NULL, NULL); + return interp; + } +}; + +struct SdcObjects { + std::vector ports; + std::vector> cells; + std::vector> pins; + std::vector> nets; + + void sniff_module(std::list& hierarchy, Module* mod) { + log_debug("sniffing module %s\n", mod->name.c_str()); + + 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; + 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; + cells.push_back(std::make_pair(path, cell)); + for (auto pin : cell->connections()) { + std::string pin_name = path + "/" + pin.first.str().substr(1); + pins.push_back(std::make_pair(pin_name, cell)); + } + if (auto sub_mod = mod->design->module(cell->type)) { + hierarchy.push_back(name); + sniff_module(hierarchy, sub_mod); + hierarchy.pop_back(); + } + } + } + SdcObjects(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) { + ports.push_back(port.str().substr(1)); + } + std::list hierarchy{}; + sniff_module(hierarchy, top); + } + void dump() { + log("Dumping detected design objects visible to SDC constraints\n"); + log("Ports:\n"); + for (auto name : ports) { + log("\t%s\n", name.c_str()); + } + log("Cells:\n"); + for (auto [name, cell] : cells) { + (void)cell; + log("\t%s\n", name.c_str()); + } + log("Pins:\n"); + for (auto [name, pin] : pins) { + (void)pin; + log("\t%s\n", name.c_str()); + } + log("Nets:\n"); + for (auto [name, net] : nets) { + (void)net; + log("\t%s\n", name.c_str()); + } + log("\n"); + } }; // Also see TclPass struct SdcPass : public Pass { + // TODO help SdcPass() : Pass("sdc", "sniff at some SDC") { } - void execute(std::vector args, RTLIL::Design *) override { + void execute(std::vector args, RTLIL::Design *design) override { if (args.size() < 2) - log_cmd_error("Missing script file.\n"); - - std::vector script_args; - for (auto it = args.begin() + 2; it != args.end(); ++it) - script_args.push_back(Tcl_NewStringObj((*it).c_str(), (*it).size())); - + log_cmd_error("Missing SDC file.\n"); + // TODO optional extra stub file Tcl_Interp *interp = SDCInterpreter::get().fresh_interp(); Tcl_Preserve(interp); - Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argc", 4), NULL, Tcl_NewIntObj(script_args.size()), 0); - Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argv", 4), NULL, Tcl_NewListObj(script_args.size(), script_args.data()), 0); - Tcl_ObjSetVar2(interp, Tcl_NewStringObj("argv0", 5), NULL, Tcl_NewStringObj(args[1].c_str(), args[1].size()), 0); - - std::string stub_path = "+/sdc/stubs.sdc"; - rewrite_filename(stub_path); + std::string stub_path = "+/sdc/stubs.sdc"; + rewrite_filename(stub_path); + SdcObjects objects(design); + objects.dump(); if (Tcl_EvalFile(interp, stub_path.c_str()) != TCL_OK) - log_cmd_error("SDC interpreter returned an error in stub file: %s\n", Tcl_GetStringResult(interp)); + log_cmd_error("SDC interpreter returned an error in stub file: %s\n", Tcl_GetStringResult(interp)); if (Tcl_EvalFile(interp, args[1].c_str()) != TCL_OK) log_cmd_error("SDC interpreter returned an error: %s\n", Tcl_GetStringResult(interp)); Tcl_Release(interp); diff --git a/passes/cmds/stubs.sdc b/passes/cmds/stubs.sdc index 9f5d03b87..8659ca392 100644 --- a/passes/cmds/stubs.sdc +++ b/passes/cmds/stubs.sdc @@ -1,7 +1,16 @@ +# with Tcl's eager evaluation, we will still eval args if they're unused by a stub proc stub {function_name} { proc $function_name {args} {} } +# OpenROAD +proc get_name {thing} { + return thing +} +proc get_full_name {thing} { + return thing +} + # Vivado UG903 stub add_cells_to_pblock stub add_to_power_rail @@ -35,7 +44,6 @@ stub delete_power_rails stub endgroup stub get_bel_pins stub get_bels -stub get_cells stub get_clocks stub get_debug_cores stub get_debug_ports @@ -43,16 +51,13 @@ stub get_generated_clocks stub get_hierarchy_separator stub get_iobanks stub get_macros -stub get_nets stub get_nodes stub get_package_pins stub get_path_groups stub get_pblocks -stub get_pins stub get_pips stub get_pkgpin_bytegroups stub get_pkgpin_nibbles -stub get_ports stub get_power_rails stub get_property stub get_site_pins @@ -62,7 +67,6 @@ stub get_slrs stub get_speed_models stub get_tiles stub get_timing_arcs -stub get_wires stub group_path stub make_diff_pair_ports stub remove_cells_from_pblock diff --git a/tests/various/sdc.sdc b/tests/various/sdc.sdc new file mode 100644 index 000000000..69efcb40a --- /dev/null +++ b/tests/various/sdc.sdc @@ -0,0 +1,5 @@ +puts "SDC constraints file says hello from arbitrary Tcl execution" +set_false_path -from [get_pins s1/sa] -to [get_pins s1/sb] +puts "This should print something:" +puts [get_ports {slink_clk_o slink_*_o}] +puts "Did it?" \ No newline at end of file From 9a3c7f70adaa1b9f7bebde3befd82701324a1804 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Tue, 27 May 2025 17:47:26 +0200 Subject: [PATCH 086/153] sdc: stubs SDC commands supported by OpenSTA --- passes/cmds/stubs.sdc | 125 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/passes/cmds/stubs.sdc b/passes/cmds/stubs.sdc index 8659ca392..ea1600139 100644 --- a/passes/cmds/stubs.sdc +++ b/passes/cmds/stubs.sdc @@ -107,3 +107,128 @@ stub set_system_jitter stub set_units stub startgroup stub update_macro + +# OpenSTA +stub source_ +stub write_sdc +stub current_instance +stub set_hierarchy_separator +stub check_path_divider +stub set_units +stub check_unit +stub unit_prefix_scale +stub check_unit_scale +stub all_clocks +stub all_inputs +stub all_outputs +stub all_registers +stub current_design +stub filter_objs +stub check_nocase_flag +stub find_liberty_libraries_matching +stub create_clock +stub delete_clock +stub create_generated_clock +stub delete_generated_clock +stub remove_gclk_cmd +stub group_path +stub check_exception_pins +stub set_clock_gating_check +stub set_clock_gating_check1 +stub set_clock_groups +stub unset_clock_groups +stub unset_clk_groups_cmd +stub set_clock_latency +stub unset_clock_latency +stub unset_clk_latency_cmd +stub set_sense +stub set_clock_sense +stub set_clock_sense_cmd1 +stub set_clock_transition +stub unset_clock_transition +stub set_clock_uncertainty +stub unset_clock_uncertainty +stub unset_clk_uncertainty_cmd +stub set_data_check +stub unset_data_check +stub unset_data_checks_cmd +stub set_disable_timing +stub set_disable_timing_instance +stub parse_disable_inst_ports +stub port_members +stub set_disable_timing_cell +stub parse_disable_cell_ports +stub unset_disable_timing +stub unset_disable_cmd +stub unset_disable_timing_cell +stub unset_disable_timing_instance +stub set_false_path +stub set_ideal_latency +stub set_ideal_network +stub set_ideal_transition +stub set_input_delay +stub set_port_delay +stub unset_input_delay +stub set_max_delay +stub set_path_delay +stub set_max_time_borrow +stub set_min_delay +stub set_min_pulse_width +stub set_multicycle_path +stub unset_path_exceptions +stub unset_path_exceptions_cmd +stub set_output_delay +stub unset_output_delay +stub unset_port_delay +stub set_propagated_clock +stub unset_propagated_clock +stub set_case_analysis +stub unset_case_analysis +stub set_drive +stub set_driving_cell +stub port_direction_any_output +stub set_fanout_load +stub set_input_transition +stub set_load +stub set_logic_dc +stub set_logic_value +stub set_logic_one +stub set_logic_zero +stub set_max_area +stub set_max_capacitance +stub set_capacitance_limit +stub set_max_fanout +stub set_fanout_limit +stub set_max_transition +stub set_port_fanout_number +stub set_resistance +stub set_timing_derate +stub unset_timing_derate +stub parse_from_arg +stub parse_thrus_arg +stub parse_to_arg +stub parse_to_arg1 +stub delete_from_thrus_to +stub parse_comment_key +stub set_min_capacitance +stub set_operating_conditions +stub parse_op_cond +stub parse_op_cond_analysis_type +stub set_wire_load_min_block_size +stub set_wire_load_mode +stub set_wire_load_model +stub set_wire_load_selection_group +stub set_voltage +stub create_voltage_area +stub set_level_shifter_strategy +stub set_level_shifter_threshold +stub set_max_dynamic_power +stub set_max_leakage_power +stub define_corners +stub set_pvt +stub set_pvt_min_max +stub default_operating_conditions +stub cell_regexp +stub cell_regexp_hsc +stub port_regexp +stub port_regexp_hsc \ No newline at end of file From dc48ceadd9510bbb7b628901b1badf41400f264c Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Tue, 27 May 2025 18:00:30 +0200 Subject: [PATCH 087/153] sdc: collect strictly matching objects --- passes/cmds/sdc.cc | 245 +++++++++++++++++++++++++----------------- tests/various/sdc.sdc | 2 +- 2 files changed, 146 insertions(+), 101 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index 0343c6b09..4d7347f54 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -9,6 +9,117 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN + +struct SdcObjects { + std::vector design_ports; + std::vector> design_cells; + std::vector> design_pins; + std::vector> design_nets; + + pool constrained_ports; + pool> constrained_cells; + pool> constrained_pins; + pool> constrained_nets; + + void sniff_module(std::list& 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()) { + std::string pin_name = path + "/" + pin.first.str().substr(1); + design_pins.push_back(std::make_pair(pin_name, cell)); + } + if (auto sub_mod = mod->design->module(cell->type)) { + hierarchy.push_back(name); + sniff_module(hierarchy, sub_mod); + hierarchy.pop_back(); + } + } + } + SdcObjects(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(port.str().substr(1)); + } + std::list hierarchy{}; + sniff_module(hierarchy, top); + } + ~SdcObjects() = default; + void dump() { + 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 name : constrained_ports) { + log("\t%s\n", name.c_str()); + } + 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 [name, pin] : constrained_pins) { + (void)pin; + log("\t%s\n", name.c_str()); + } + log("Constrained nets:\n"); + for (auto [name, net] : constrained_nets) { + (void)net; + log("\t%s\n", name.c_str()); + } + log("\n"); + } +}; + template static bool parse_flag(char* arg, const char* flag_name, T& flag_var) { std::string expected = std::string("-") + flag_name; @@ -23,9 +134,10 @@ static bool parse_flag(char* arg, const char* flag_name, T& flag_var) { // TODO vectors // TODO cell arrays? -static int sdc_get_pins_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) +static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { (void)interp; + auto* objects = (SdcObjects*)data; // When this flag is present, the search for the pattern is made in all positions in the hierarchy. bool hierarchical_flag = false; bool regexp_flag = false; @@ -53,9 +165,16 @@ static int sdc_get_pins_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj* c for (; i < objc; i++) { patterns.push_back(Tcl_GetString(objv[i])); } - log("get_pins patterns:\n"); for (auto pat : patterns) { - log("\t%s\n", pat.c_str()); + bool found = false; + for (auto [name, pin] : objects->design_pins) { + if (name == pat) { + found = true; + objects->constrained_pins.insert(std::make_pair(name, pin)); + } + } + if (!found) + log_warning("No matches in design for pattern %s\n", pat.c_str()); } (void)hierarchical_flag; (void)regexp_flag; @@ -68,9 +187,10 @@ static int sdc_get_pins_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj* c return TCL_OK; } -static int sdc_get_ports_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) +static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { (void)interp; + auto* objects = (SdcObjects*)data; bool regexp_flag = false; bool nocase_flag = false; std::vector patterns; @@ -84,20 +204,29 @@ static int sdc_get_ports_cmd(ClientData, Tcl_Interp *interp, int objc, Tcl_Obj* for (; i < objc; i++) { patterns.push_back(Tcl_GetString(objv[i])); } - log("get_ports patterns:\n"); for (auto pat : patterns) { - log("\t%s\n", pat.c_str()); + bool found = false; + for (auto name : objects->design_ports) { + if (name == pat) { + found = true; + objects->constrained_ports.insert(name); + } + } + if (!found) + log_warning("No matches in design for pattern %s\n", pat.c_str()); } (void)regexp_flag; (void)nocase_flag; return TCL_OK; } + class SDCInterpreter { private: Tcl_Interp* interp = nullptr; public: + std::unique_ptr objects; ~SDCInterpreter() { if (interp) Tcl_DeleteInterp(interp); @@ -106,105 +235,21 @@ public: static SDCInterpreter instance; return instance; } - Tcl_Interp* fresh_interp() { + Tcl_Interp* fresh_interp(Design* design) { if (interp) Tcl_DeleteInterp(interp); + interp = Tcl_CreateInterp(); if (Tcl_Init(interp)!=TCL_OK) - log_warning("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); - Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, NULL, NULL); - Tcl_CreateObjCommand(interp, "get_ports", sdc_get_ports_cmd, NULL, NULL); + log_error("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); + + objects = std::make_unique(design); + Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, (ClientData) objects.get(), NULL); + Tcl_CreateObjCommand(interp, "get_ports", sdc_get_ports_cmd, (ClientData) objects.get(), NULL); return interp; } }; -struct SdcObjects { - std::vector ports; - std::vector> cells; - std::vector> pins; - std::vector> nets; - - void sniff_module(std::list& hierarchy, Module* mod) { - log_debug("sniffing module %s\n", mod->name.c_str()); - - 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; - 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; - cells.push_back(std::make_pair(path, cell)); - for (auto pin : cell->connections()) { - std::string pin_name = path + "/" + pin.first.str().substr(1); - pins.push_back(std::make_pair(pin_name, cell)); - } - if (auto sub_mod = mod->design->module(cell->type)) { - hierarchy.push_back(name); - sniff_module(hierarchy, sub_mod); - hierarchy.pop_back(); - } - } - } - SdcObjects(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) { - ports.push_back(port.str().substr(1)); - } - std::list hierarchy{}; - sniff_module(hierarchy, top); - } - void dump() { - log("Dumping detected design objects visible to SDC constraints\n"); - log("Ports:\n"); - for (auto name : ports) { - log("\t%s\n", name.c_str()); - } - log("Cells:\n"); - for (auto [name, cell] : cells) { - (void)cell; - log("\t%s\n", name.c_str()); - } - log("Pins:\n"); - for (auto [name, pin] : pins) { - (void)pin; - log("\t%s\n", name.c_str()); - } - log("Nets:\n"); - for (auto [name, net] : nets) { - (void)net; - log("\t%s\n", name.c_str()); - } - log("\n"); - } -}; - // Also see TclPass struct SdcPass : public Pass { // TODO help @@ -213,16 +258,16 @@ struct SdcPass : public Pass { if (args.size() < 2) log_cmd_error("Missing SDC file.\n"); // TODO optional extra stub file - Tcl_Interp *interp = SDCInterpreter::get().fresh_interp(); + SDCInterpreter& sdc = SDCInterpreter::get(); + Tcl_Interp *interp = sdc.fresh_interp(design); Tcl_Preserve(interp); std::string stub_path = "+/sdc/stubs.sdc"; rewrite_filename(stub_path); - SdcObjects objects(design); - objects.dump(); if (Tcl_EvalFile(interp, stub_path.c_str()) != TCL_OK) log_cmd_error("SDC interpreter returned an error in stub file: %s\n", Tcl_GetStringResult(interp)); if (Tcl_EvalFile(interp, args[1].c_str()) != TCL_OK) log_cmd_error("SDC interpreter returned an error: %s\n", Tcl_GetStringResult(interp)); + sdc.objects->dump(); Tcl_Release(interp); } } SdcPass; diff --git a/tests/various/sdc.sdc b/tests/various/sdc.sdc index 69efcb40a..2143b34d7 100644 --- a/tests/various/sdc.sdc +++ b/tests/various/sdc.sdc @@ -1,5 +1,5 @@ puts "SDC constraints file says hello from arbitrary Tcl execution" set_false_path -from [get_pins s1/sa] -to [get_pins s1/sb] puts "This should print something:" -puts [get_ports {slink_clk_o slink_*_o}] +puts [get_ports a b] puts "Did it?" \ No newline at end of file From 834125a0763be101cac5bcd3242f89ab09328055 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Tue, 27 May 2025 18:29:59 +0200 Subject: [PATCH 088/153] sdc: return resolved patterns --- passes/cmds/sdc.cc | 62 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index 4d7347f54..dcbd60ce5 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -4,6 +4,7 @@ #include "kernel/log.h" #include #include +#include USING_YOSYS_NAMESPACE @@ -77,6 +78,14 @@ struct SdcObjects { } ~SdcObjects() = default; 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()); @@ -130,9 +139,27 @@ static bool parse_flag(char* arg, const char* flag_name, T& flag_var) { return false; } -// TODO return values like json_to_tcl on result.json? // 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 bool matches(std::string name, const std::string& pat, const MatchConfig& config) { + return name == pat; +} static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { @@ -165,25 +192,36 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O for (; i < objc; i++) { patterns.push_back(Tcl_GetString(objv[i])); } + + MatchConfig config(regexp_flag, nocase_flag, hierarchical_flag); + std::vector> resolved; for (auto pat : patterns) { bool found = false; for (auto [name, pin] : objects->design_pins) { - if (name == pat) { + if (matches(name, pat, config)) { found = true; - objects->constrained_pins.insert(std::make_pair(name, pin)); + resolved.push_back(std::make_pair(name, pin)); } } if (!found) - log_warning("No matches in design for pattern %s\n", pat.c_str()); + log_warning("No matches in design for pin %s\n", pat.c_str()); + } + Tcl_Obj *result = Tcl_NewListObj(resolved.size(), nullptr); + for (auto obj : resolved) { + Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(obj.first.c_str(), obj.first.size())); + objects->constrained_pins.insert(obj); } (void)hierarchical_flag; (void)regexp_flag; (void)nocase_flag; (void)of_objects; + if (separator != "/") { - Tcl_SetObjResult(interp, Tcl_NewStringObj("Only '/' accepted as separator", -1)); + Tcl_SetResult(interp, (char *)"Only '/' accepted as separator", TCL_STATIC); return TCL_ERROR; } + + Tcl_SetObjResult(interp, result); return TCL_OK; } @@ -204,19 +242,27 @@ static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_ for (; i < objc; i++) { patterns.push_back(Tcl_GetString(objv[i])); } + MatchConfig config(regexp_flag, nocase_flag, false); + std::vector resolved; for (auto pat : patterns) { bool found = false; for (auto name : objects->design_ports) { - if (name == pat) { + if (matches(name, pat, config)) { found = true; - objects->constrained_ports.insert(name); + resolved.push_back(name); } } if (!found) - log_warning("No matches in design for pattern %s\n", pat.c_str()); + log_warning("No matches in design for port %s\n", pat.c_str()); + } + Tcl_Obj *result = Tcl_NewListObj(resolved.size(), nullptr); + for (auto obj : resolved) { + Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(obj.c_str(), obj.size())); + objects->constrained_ports.insert(obj); } (void)regexp_flag; (void)nocase_flag; + Tcl_SetObjResult(interp, result); return TCL_OK; } From 420a083d9b0dbd46e1dd7602a54ee88d8907b9e8 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Tue, 10 Jun 2025 16:34:04 +0200 Subject: [PATCH 089/153] sdc: simple mode, remove per-tool stubs --- passes/cmds/sdc.cc | 180 +++++++++++++++++++++++++------ passes/cmds/stubs.sdc | 241 +++--------------------------------------- 2 files changed, 160 insertions(+), 261 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index dcbd60ce5..b01cbe025 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -5,6 +5,7 @@ #include #include #include +#include USING_YOSYS_NAMESPACE @@ -12,6 +13,11 @@ PRIVATE_NAMESPACE_BEGIN struct SdcObjects { + enum CollectMode { + SimpleGetter, + FullGetter, + FullConstraint, + } collect_mode; std::vector design_ports; std::vector> design_cells; std::vector> design_pins; @@ -86,26 +92,26 @@ struct SdcObjects { 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("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 name : constrained_ports) { log("\t%s\n", name.c_str()); @@ -158,12 +164,13 @@ struct MatchConfig { }; static bool matches(std::string name, const std::string& pat, const MatchConfig& config) { + (void)config; + // TODO implement full mode return name == pat; } static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { - (void)interp; auto* objects = (SdcObjects*)data; // When this flag is present, the search for the pattern is made in all positions in the hierarchy. bool hierarchical_flag = false; @@ -192,6 +199,14 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O for (; i < objc; i++) { patterns.push_back(Tcl_GetString(objv[i])); } + if (objects->collect_mode == SdcObjects::CollectMode::SimpleGetter) { + if (regexp_flag || hierarchical_flag || nocase_flag || separator != "/" || of_objects) { + log_error("get_pins got unexpected flags in simple mode\n"); + } + if (patterns.size() != 1) { + log_error("get_pins got unexpected number of patterns in simple mode\n"); + } + } MatchConfig config(regexp_flag, nocase_flag, hierarchical_flag); std::vector> resolved; @@ -209,12 +224,9 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O Tcl_Obj *result = Tcl_NewListObj(resolved.size(), nullptr); for (auto obj : resolved) { Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(obj.first.c_str(), obj.first.size())); - objects->constrained_pins.insert(obj); + if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) + objects->constrained_pins.insert(obj); } - (void)hierarchical_flag; - (void)regexp_flag; - (void)nocase_flag; - (void)of_objects; if (separator != "/") { Tcl_SetResult(interp, (char *)"Only '/' accepted as separator", TCL_STATIC); @@ -227,7 +239,6 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { - (void)interp; auto* objects = (SdcObjects*)data; bool regexp_flag = false; bool nocase_flag = false; @@ -242,6 +253,14 @@ static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_ for (; i < objc; i++) { patterns.push_back(Tcl_GetString(objv[i])); } + if (objects->collect_mode == SdcObjects::CollectMode::SimpleGetter) { + if (regexp_flag || nocase_flag) { + log_error("get_ports got unexpected flags in simple mode\n"); + } + if (patterns.size() != 1) { + log_error("get_ports got unexpected number of patterns in simple mode\n"); + } + } MatchConfig config(regexp_flag, nocase_flag, false); std::vector resolved; for (auto pat : patterns) { @@ -258,14 +277,89 @@ static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_ Tcl_Obj *result = Tcl_NewListObj(resolved.size(), nullptr); for (auto obj : resolved) { Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(obj.c_str(), obj.size())); - objects->constrained_ports.insert(obj); + if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) + objects->constrained_ports.insert(obj); } - (void)regexp_flag; - (void)nocase_flag; + Tcl_SetObjResult(interp, result); return TCL_OK; } +std::optional> split_at(std::string s) +{ + size_t pos = s.find('@'); + if (pos == std::string::npos) + return std::nullopt; + return std::make_tuple(s.substr(0, pos), s.substr(pos + 1)); +} + +// Whether string or list of strings, apply op to each string +void apply_args(Tcl_Interp *interp, std::function op, Tcl_Obj* obj) +{ + int length; + Tcl_Obj **value_list; + if (Tcl_ListObjGetElements(interp, obj, &length, &value_list) == TCL_OK) { + for (int i = 0; i < length; i++) { + op(Tcl_GetString(value_list[i])); + } + } else { + op(Tcl_GetString(obj)); + } +} + +static int ys_track_typed_key_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) +{ + log("ys_track_typed_key_cmd\n"); + auto* objects = (SdcObjects*)data; + if (objc != 5) + log_error("ys_track_typed_key: Unexpected number of arguments: %d (expected 5)\n", objc); + + if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) + return TCL_OK; + + std::string key_name = Tcl_GetString(objv[1]); + Tcl_Obj* key_value = objv[2]; + std::string key_expect_type = Tcl_GetString(objv[3]); + std::string proc_name = Tcl_GetString(objv[4]); + + auto track_typed = [key_expect_type, objects, proc_name, key_name](const char* str) -> void { + auto split = split_at(str); + if (!split) + log_error("%s: key %s should be a typed SDC object, but is something weird: %s\n", + proc_name.c_str(), key_name.c_str(), str); + + if (key_expect_type == "pin") { + log("PIN! %s\n", str); + bool found = false; + for (auto [name, pin] : objects->design_pins) { + if (name + "/" + pin->name.str() == str) { + found = true; + objects->constrained_pins.insert(std::make_pair(name, pin)); + break; // resolved, expected unique + } + } + if (!found) + log_error("%s: pin %s not found\n", proc_name.c_str(), str); + } else if (key_expect_type == "port") { + bool found = false; + for (auto name : objects->design_ports) { + if (name == str) { + found = true; + objects->constrained_ports.insert(name); + break; // resolved, expected unique + } + } + if (!found) + log_error("%s: port %s not found\n", proc_name.c_str(), str); + } else { + // TODO + log_warning("%s: unsupported type %s\n", proc_name.c_str(), key_expect_type.c_str()); + } + }; + apply_args(interp, track_typed, key_value); + return TCL_OK; +} + class SDCInterpreter { @@ -290,8 +384,10 @@ public: log_error("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); objects = std::make_unique(design); + objects->collect_mode = SdcObjects::CollectMode::FullConstraint; Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, (ClientData) objects.get(), NULL); Tcl_CreateObjCommand(interp, "get_ports", sdc_get_ports_cmd, (ClientData) objects.get(), NULL); + Tcl_CreateObjCommand(interp, "ys_track_typed_key", ys_track_typed_key_cmd, (ClientData) objects.get(), NULL); return interp; } }; @@ -301,17 +397,33 @@ struct SdcPass : public Pass { // TODO help SdcPass() : Pass("sdc", "sniff at some SDC") { } void execute(std::vector args, RTLIL::Design *design) override { - if (args.size() < 2) - log_cmd_error("Missing SDC file.\n"); + // if (args.size() < 2) + // log_cmd_error("Missing SDC file.\n"); // TODO optional extra stub file + size_t argidx; + std::vector opensta_stubs_paths; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-stubs" && argidx+1 < args.size()) { + opensta_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]; SDCInterpreter& sdc = SDCInterpreter::get(); Tcl_Interp *interp = sdc.fresh_interp(design); Tcl_Preserve(interp); std::string stub_path = "+/sdc/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 file: %s\n", Tcl_GetStringResult(interp)); - if (Tcl_EvalFile(interp, args[1].c_str()) != TCL_OK) + log_cmd_error("SDC interpreter returned an error in stub preamble file: %s\n", Tcl_GetStringResult(interp)); + for (auto path : opensta_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)); sdc.objects->dump(); Tcl_Release(interp); diff --git a/passes/cmds/stubs.sdc b/passes/cmds/stubs.sdc index ea1600139..3466981c9 100644 --- a/passes/cmds/stubs.sdc +++ b/passes/cmds/stubs.sdc @@ -1,234 +1,21 @@ # with Tcl's eager evaluation, we will still eval args if they're unused by a stub proc stub {function_name} { - proc $function_name {args} {} + proc $function_name {args} "puts \"stubbed $function_name\"" } -# OpenROAD -proc get_name {thing} { - return thing -} -proc get_full_name {thing} { - return thing +proc is_suppressed {args} { + return 0 +} + +proc create_clock {args} { + return "CLOCK@" +} +proc get_clocks {args} { + return "CLOCK@" } -# Vivado UG903 -stub add_cells_to_pblock -stub add_to_power_rail -stub all_clocks -stub all_cpus -stub all_dsps -stub all_fanin -stub all_fanout -stub all_ffs -stub all_hsios -stub all_inputs -stub all_latches -stub all_outputs -stub all_rams -stub all_registers -stub connect_debug_port -stub create_clock -stub create_debug_core -stub create_debug_port -stub create_generated_clock -stub create_macro -stub create_pblock -stub create_power_rail -stub create_property -stub create_waiver stub current_design -stub current_instance -stub delete_macros -stub delete_pblock -stub delete_power_rails -stub endgroup -stub get_bel_pins -stub get_bels -stub get_clocks -stub get_debug_cores -stub get_debug_ports -stub get_generated_clocks -stub get_hierarchy_separator -stub get_iobanks -stub get_macros -stub get_nodes -stub get_package_pins -stub get_path_groups -stub get_pblocks -stub get_pips -stub get_pkgpin_bytegroups -stub get_pkgpin_nibbles -stub get_power_rails -stub get_property -stub get_site_pins -stub get_site_pips -stub get_sites -stub get_slrs -stub get_speed_models -stub get_tiles -stub get_timing_arcs -stub group_path -stub make_diff_pair_ports -stub remove_cells_from_pblock -stub remove_from_power_rail -stub reset_operating_conditions -stub reset_switching_activity -stub resize_pblock -stub set_bus_skew -stub set_case_analysis -stub set_clock_groups -stub set_clock_latency -stub set_clock_sense -stub set_clock_uncertainty -stub set_data_check -stub set_disable_timing -stub set_external_delay -stub set_false_path -stub set_hierarchy_separator -stub set_input_delay -stub set_input_jitter -stub set_load -stub set_logic_dc -stub set_logic_one -stub set_logic_unconnected -stub set_logic_zero -stub set_max_delay -stub set_max_time_borrow -stub set_min_delay -stub set_multicycle_path -stub set_operating_conditions -stub set_output_delay -stub set_package_pin_val -stub set_power_opt -stub set_propagated_clock -stub set_property -stub set_switching_activity -stub set_system_jitter -stub set_units -stub startgroup -stub update_macro - -# OpenSTA -stub source_ -stub write_sdc -stub current_instance -stub set_hierarchy_separator -stub check_path_divider -stub set_units -stub check_unit -stub unit_prefix_scale -stub check_unit_scale -stub all_clocks -stub all_inputs -stub all_outputs -stub all_registers -stub current_design -stub filter_objs -stub check_nocase_flag -stub find_liberty_libraries_matching -stub create_clock -stub delete_clock -stub create_generated_clock -stub delete_generated_clock -stub remove_gclk_cmd -stub group_path -stub check_exception_pins -stub set_clock_gating_check -stub set_clock_gating_check1 -stub set_clock_groups -stub unset_clock_groups -stub unset_clk_groups_cmd -stub set_clock_latency -stub unset_clock_latency -stub unset_clk_latency_cmd -stub set_sense -stub set_clock_sense -stub set_clock_sense_cmd1 -stub set_clock_transition -stub unset_clock_transition -stub set_clock_uncertainty -stub unset_clock_uncertainty -stub unset_clk_uncertainty_cmd -stub set_data_check -stub unset_data_check -stub unset_data_checks_cmd -stub set_disable_timing -stub set_disable_timing_instance -stub parse_disable_inst_ports -stub port_members -stub set_disable_timing_cell -stub parse_disable_cell_ports -stub unset_disable_timing -stub unset_disable_cmd -stub unset_disable_timing_cell -stub unset_disable_timing_instance -stub set_false_path -stub set_ideal_latency -stub set_ideal_network -stub set_ideal_transition -stub set_input_delay -stub set_port_delay -stub unset_input_delay -stub set_max_delay -stub set_path_delay -stub set_max_time_borrow -stub set_min_delay -stub set_min_pulse_width -stub set_multicycle_path -stub unset_path_exceptions -stub unset_path_exceptions_cmd -stub set_output_delay -stub unset_output_delay -stub unset_port_delay -stub set_propagated_clock -stub unset_propagated_clock -stub set_case_analysis -stub unset_case_analysis -stub set_drive -stub set_driving_cell -stub port_direction_any_output -stub set_fanout_load -stub set_input_transition -stub set_load -stub set_logic_dc -stub set_logic_value -stub set_logic_one -stub set_logic_zero -stub set_max_area -stub set_max_capacitance -stub set_capacitance_limit -stub set_max_fanout -stub set_fanout_limit -stub set_max_transition -stub set_port_fanout_number -stub set_resistance -stub set_timing_derate -stub unset_timing_derate -stub parse_from_arg -stub parse_thrus_arg -stub parse_to_arg -stub parse_to_arg1 -stub delete_from_thrus_to -stub parse_comment_key -stub set_min_capacitance -stub set_operating_conditions -stub parse_op_cond -stub parse_op_cond_analysis_type -stub set_wire_load_min_block_size -stub set_wire_load_mode -stub set_wire_load_model -stub set_wire_load_selection_group -stub set_voltage -stub create_voltage_area -stub set_level_shifter_strategy -stub set_level_shifter_threshold -stub set_max_dynamic_power -stub set_max_leakage_power -stub define_corners -stub set_pvt -stub set_pvt_min_max -stub default_operating_conditions -stub cell_regexp -stub cell_regexp_hsc -stub port_regexp -stub port_regexp_hsc \ No newline at end of file +#stub ys_track_typed_key +stub ys_track_untyped_key +stub ys_err_key +stub ys_err_flag From 21c68e0022680a6f6268368d21c1eeb0604e4112 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 30 Jul 2025 18:51:14 +0200 Subject: [PATCH 090/153] sdc: unknown handler experiment --- passes/cmds/sdc.cc | 5 +++-- passes/cmds/stubs.sdc | 23 +++++++++++++++-------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index b01cbe025..76b9b95ff 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -384,7 +384,7 @@ public: log_error("Tcl_Init() call failed - %s\n",Tcl_ErrnoMsg(Tcl_GetErrno())); objects = std::make_unique(design); - objects->collect_mode = SdcObjects::CollectMode::FullConstraint; + objects->collect_mode = SdcObjects::CollectMode::SimpleGetter; Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, (ClientData) objects.get(), NULL); Tcl_CreateObjCommand(interp, "get_ports", sdc_get_ports_cmd, (ClientData) objects.get(), NULL); Tcl_CreateObjCommand(interp, "ys_track_typed_key", ys_track_typed_key_cmd, (ClientData) objects.get(), NULL); @@ -396,7 +396,8 @@ public: struct SdcPass : public Pass { // TODO help SdcPass() : Pass("sdc", "sniff at some SDC") { } - void execute(std::vector args, RTLIL::Design *design) override { +void execute(std::vector args, RTLIL::Design *design) override { + log_header(design, "Executing SDC pass.\n"); // if (args.size() < 2) // log_cmd_error("Missing SDC file.\n"); // TODO optional extra stub file diff --git a/passes/cmds/stubs.sdc b/passes/cmds/stubs.sdc index 3466981c9..a7845b02d 100644 --- a/passes/cmds/stubs.sdc +++ b/passes/cmds/stubs.sdc @@ -7,15 +7,22 @@ proc is_suppressed {args} { return 0 } -proc create_clock {args} { - return "CLOCK@" -} -proc get_clocks {args} { - return "CLOCK@" -} - -stub current_design +# stub current_design #stub ys_track_typed_key stub ys_track_untyped_key stub ys_err_key stub ys_err_flag + +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 {} + } + incr sdc_call_index + lappend sdc_calls $args + return $sdc_call_index +} \ No newline at end of file From 24a6412ea801b9f399bbb9dc94d89b9a5a309ab6 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 31 Jul 2025 15:31:36 +0200 Subject: [PATCH 091/153] sdc: bit selections --- passes/cmds/sdc.cc | 176 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 142 insertions(+), 34 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index 76b9b95ff..c5dbbc7f5 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -18,15 +18,77 @@ struct SdcObjects { FullGetter, FullConstraint, } collect_mode; + struct BitSelection { + bool all = false; + std::vector 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) { + 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; + } + }; + using CellPin = std::pair; std::vector design_ports; std::vector> design_cells; - std::vector> design_pins; + std::vector> design_pins; std::vector> design_nets; - pool constrained_ports; + dict constrained_ports; pool> constrained_cells; - pool> constrained_pins; - pool> constrained_nets; + dict, BitSelection> constrained_pins; + dict, BitSelection> constrained_nets; void sniff_module(std::list& hierarchy, Module* mod) { std::string prefix; @@ -62,8 +124,9 @@ struct SdcObjects { path += name; design_cells.push_back(std::make_pair(path, cell)); for (auto pin : cell->connections()) { - std::string pin_name = path + "/" + pin.first.str().substr(1); - design_pins.push_back(std::make_pair(pin_name, cell)); + 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); @@ -113,23 +176,28 @@ struct SdcObjects { // } // log("\n"); log("Constrained ports:\n"); - for (auto name : constrained_ports) { + for (auto [name, bits] : constrained_ports) { log("\t%s\n", name.c_str()); + bits.dump(); } log("Constrained cells:\n"); - for (auto [name, cell] : constrained_cells) { + for (auto& [name, cell] : constrained_cells) { (void)cell; log("\t%s\n", name.c_str()); } log("Constrained pins:\n"); - for (auto [name, pin] : constrained_pins) { + 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 [name, net] : constrained_nets) { + for (auto& [ref, bits] : constrained_nets) { + auto [name, net] = ref; (void)net; log("\t%s\n", name.c_str()); + bits.dump(); } log("\n"); } @@ -163,10 +231,36 @@ struct MatchConfig { hier(hierarchical_flag ? FLAT : TREE) { } }; -static bool matches(std::string name, const std::string& pat, const MatchConfig& config) { +static std::pair matches(std::string name, const std::string& pat, const MatchConfig& config) { (void)config; - // TODO implement full mode - return name == pat; + 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); + + } + SdcObjects::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 sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) @@ -177,7 +271,7 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O bool regexp_flag = false; bool nocase_flag = false; std::string separator = "/"; - Tcl_Obj* of_objects; + Tcl_Obj* of_objects = nullptr; std::vector patterns; int i = 1; for (; i < objc; i++) { @@ -209,30 +303,37 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O } MatchConfig config(regexp_flag, nocase_flag, hierarchical_flag); - std::vector> resolved; + std::vector> resolved; for (auto pat : patterns) { bool found = false; for (auto [name, pin] : objects->design_pins) { - if (matches(name, pat, config)) { + auto [does_match, matching_bits] = matches(name, pat, config); + if (does_match) { found = true; - resolved.push_back(std::make_pair(name, pin)); + resolved.push_back(std::make_tuple(name, pin, matching_bits)); } } if (!found) log_warning("No matches in design for pin %s\n", pat.c_str()); } - Tcl_Obj *result = Tcl_NewListObj(resolved.size(), nullptr); - for (auto obj : resolved) { - Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(obj.first.c_str(), obj.first.size())); - if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) - objects->constrained_pins.insert(obj); - } if (separator != "/") { Tcl_SetResult(interp, (char *)"Only '/' accepted as separator", TCL_STATIC); return TCL_ERROR; } + Tcl_Obj *result = Tcl_NewListObj(resolved.size(), nullptr); + for (auto [name, pin, matching_bits] : resolved) { + // TODO change this to graph tracking if desired + size_t width = (size_t)pin.first->getPort(pin.second).size(); + for (size_t i = 0; i < width; i++) + if (matching_bits.is_set(i)) + Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(name.c_str(), name.size())); + + if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) + objects->constrained_pins[std::make_pair(name, pin)].merge(matching_bits); + } + Tcl_SetObjResult(interp, result); return TCL_OK; } @@ -262,23 +363,29 @@ static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_ } } MatchConfig config(regexp_flag, nocase_flag, false); - std::vector resolved; + std::vector> resolved; for (auto pat : patterns) { bool found = false; for (auto name : objects->design_ports) { - if (matches(name, pat, config)) { + auto [does_match, matching_bits] = matches(name, pat, config); + if (does_match) { found = true; - resolved.push_back(name); + resolved.push_back(std::make_tuple(name, matching_bits)); } } if (!found) log_warning("No matches in design for port %s\n", pat.c_str()); } Tcl_Obj *result = Tcl_NewListObj(resolved.size(), nullptr); - for (auto obj : resolved) { + for (auto [obj, matching_bits] : resolved) { Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(obj.c_str(), obj.size())); - if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) - objects->constrained_ports.insert(obj); + if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) { + if (objects->constrained_ports.count(obj) == 0) { + objects->constrained_ports[obj] = matching_bits; + } else { + objects->constrained_ports[obj].merge(matching_bits); + } + } } Tcl_SetObjResult(interp, result); @@ -332,11 +439,12 @@ static int ys_track_typed_key_cmd(ClientData data, Tcl_Interp *interp, int objc, log("PIN! %s\n", str); bool found = false; for (auto [name, pin] : objects->design_pins) { - if (name + "/" + pin->name.str() == str) { - found = true; - objects->constrained_pins.insert(std::make_pair(name, pin)); - break; // resolved, expected unique - } + log_error("TODO temporarily disabled due to working on a different flow\n"); + // if (name + "/" + pin->name.str() == str) { + // found = true; + // objects->constrained_pins.insert(std::make_pair(name, pin)); + // break; // resolved, expected unique + // } } if (!found) log_error("%s: pin %s not found\n", proc_name.c_str(), str); From c6491629d87f3aa2dac76f7f2db7105ca649f93c Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 31 Jul 2025 18:33:20 +0200 Subject: [PATCH 092/153] sdc: start graph --- passes/cmds/sdc.cc | 111 +++++++++++++++++++++++++++++++++++++++--- passes/cmds/stubs.sdc | 7 ++- 2 files changed, 110 insertions(+), 8 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index c5dbbc7f5..cc95302cc 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -14,10 +14,19 @@ PRIVATE_NAMESPACE_BEGIN 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; + enum ValueMode { + // return something sensible and error on unknown + Normal, + // return a new graph node assuming unknown is overridden + Graph, + } value_mode; struct BitSelection { bool all = false; std::vector bits = {}; @@ -263,6 +272,85 @@ static std::pair matches(std::string name, const } } + +static int redirect_unknown(Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { + Tcl_Obj *newCmd = Tcl_NewStringObj("unknown", -1); + Tcl_Obj **newObjv = new Tcl_Obj*[objc+1]; + newObjv[0] = newCmd; + for (int i = 1; i < objc + 1; i++) { + newObjv[i] = objv[i - 1]; + } + int result = Tcl_EvalObjv(interp, objc, newObjv, 0); + Tcl_DecrRefCount(newCmd); + delete[] newObjv; + return result; +} + +void inspect_globals(Tcl_Interp* interp) { + auto get_var = [&](const char* name) -> const char* { + return Tcl_GetVar(interp, name, TCL_GLOBAL_ONLY); + }; + const char* idx_s = get_var("sdc_call_index"); + log_assert(idx_s); + size_t node_count = std::stoi(idx_s); + + // else + // printf("sdc_call_index: unset\n"); + // if (auto calls = get_var("sdc_calls")) + // printf("sdc_calls: %s\n", calls); + // else + // printf("sdc_calls: unset\n"); + Tcl_Obj* listObj = Tcl_GetVar2Ex(interp, "sdc_calls", nullptr, TCL_GLOBAL_ONLY); + int listLength; + + // Get list length first + std::vector> nestedData; + if (Tcl_ListObjLength(interp, listObj, &listLength) == TCL_OK) { + for (int i = 0; i < listLength; i++) { + Tcl_Obj* subListObj; + std::vector 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) { + // It's a 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 { + // It's a single element, not a list + const char* elementStr = Tcl_GetString(subListObj); + subList.push_back(std::string(elementStr)); + } + nestedData.push_back(subList); + } + } + std::vector has_parent; + log_assert(nestedData.size() == node_count); + has_parent.resize(node_count); + for (size_t i = 0; i < node_count; i++) { + for (size_t j = 0; j < nestedData[i].size(); j++) { + auto arg = nestedData[i][j]; + auto pos = arg.find("YOSYS_SDC_MAGIC_NODE_"); + if (pos == std::string::npos) + continue; + std::string rest = arg.substr(pos); + for (auto c : rest) + if (!std::isdigit(c)) + log_error("weird thing %s\n", rest.c_str()); + size_t arg_node_idx = std::stoi(rest); + log("%zu %zu %s IDX %zu\n", i, j, arg.c_str(), arg_node_idx); + // log("%zu %zu %s\n", i, j, arg.c_str()); + + } + } +} + static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { auto* objects = (SdcObjects*)data; @@ -322,19 +410,26 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O return TCL_ERROR; } - Tcl_Obj *result = Tcl_NewListObj(resolved.size(), nullptr); + Tcl_Obj *result = nullptr; for (auto [name, pin, matching_bits] : resolved) { - // TODO change this to graph tracking if desired - size_t width = (size_t)pin.first->getPort(pin.second).size(); - for (size_t i = 0; i < width; i++) - if (matching_bits.is_set(i)) - Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(name.c_str(), name.size())); + if (objects->value_mode == SdcObjects::ValueMode::Normal) { + if (!result) + result = Tcl_NewListObj(resolved.size(), nullptr); + size_t width = (size_t)pin.first->getPort(pin.second).size(); + for (size_t i = 0; i < width; i++) + if (matching_bits.is_set(i)) + Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(name.c_str(), name.size())); + } if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) objects->constrained_pins[std::make_pair(name, pin)].merge(matching_bits); } - Tcl_SetObjResult(interp, result); + if (objects->value_mode == SdcObjects::ValueMode::Graph) { + return redirect_unknown(interp, objc, objv); + } + if (result) + Tcl_SetObjResult(interp, result); return TCL_OK; } @@ -493,6 +588,7 @@ public: objects = std::make_unique(design); objects->collect_mode = SdcObjects::CollectMode::SimpleGetter; + objects->value_mode = SdcObjects::ValueMode::Graph; Tcl_CreateObjCommand(interp, "get_pins", sdc_get_pins_cmd, (ClientData) objects.get(), NULL); Tcl_CreateObjCommand(interp, "get_ports", sdc_get_ports_cmd, (ClientData) objects.get(), NULL); Tcl_CreateObjCommand(interp, "ys_track_typed_key", ys_track_typed_key_cmd, (ClientData) objects.get(), NULL); @@ -535,6 +631,7 @@ void execute(std::vector args, RTLIL::Design *design) override { if (Tcl_EvalFile(interp, sdc_path.c_str()) != TCL_OK) log_cmd_error("SDC interpreter returned an error: %s\n", Tcl_GetStringResult(interp)); sdc.objects->dump(); + inspect_globals(interp); Tcl_Release(interp); } } SdcPass; diff --git a/passes/cmds/stubs.sdc b/passes/cmds/stubs.sdc index a7845b02d..467ce70c6 100644 --- a/passes/cmds/stubs.sdc +++ b/passes/cmds/stubs.sdc @@ -13,6 +13,7 @@ stub ys_track_untyped_key stub ys_err_key stub ys_err_flag +# TODO move to separate file and tie to graph value mode proc unknown {args} { global sdc_call_index global sdc_calls @@ -24,5 +25,9 @@ proc unknown {args} { } incr sdc_call_index lappend sdc_calls $args - return $sdc_call_index + puts "unknown $args, returning YOSYS_SDC_MAGIC_NODE_$sdc_call_index" + return "YOSYS_SDC_MAGIC_NODE_$sdc_call_index" +} +proc list {args} { + unknown "list" $args } \ No newline at end of file From ae1235210da6f4eb3995c96f8333f259357e38c0 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 1 Aug 2025 16:33:10 +0200 Subject: [PATCH 093/153] sdc: functional graph --- passes/cmds/sdc.cc | 178 ++++++++++++++++++++++++++++++------------ passes/cmds/stubs.sdc | 5 +- 2 files changed, 129 insertions(+), 54 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index cc95302cc..f0d7175c5 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -6,6 +6,7 @@ #include #include #include +#include USING_YOSYS_NAMESPACE @@ -89,7 +90,7 @@ struct SdcObjects { } }; using CellPin = std::pair; - std::vector design_ports; + std::vector> design_ports; std::vector> design_cells; std::vector> design_pins; std::vector> design_nets; @@ -149,7 +150,7 @@ struct SdcObjects { if (!top) log_error("Top module couldn't be determined. Check 'top' attribute usage"); for (auto port : top->ports) { - design_ports.push_back(port.str().substr(1)); + design_ports.push_back(std::make_pair(port.str().substr(1), top->wire(port))); } std::list hierarchy{}; sniff_module(hierarchy, top); @@ -275,36 +276,59 @@ static std::pair matches(std::string name, const static int redirect_unknown(Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { Tcl_Obj *newCmd = Tcl_NewStringObj("unknown", -1); - Tcl_Obj **newObjv = new Tcl_Obj*[objc+1]; + auto newObjc = objc + 1; + Tcl_Obj **newObjv = new Tcl_Obj*[newObjc]; newObjv[0] = newCmd; - for (int i = 1; i < objc + 1; i++) { + for (int i = 1; i < newObjc; i++) { newObjv[i] = objv[i - 1]; + log("newObjv %s\n", Tcl_GetString(newObjv[i])); } - int result = Tcl_EvalObjv(interp, objc, newObjv, 0); + int result = Tcl_EvalObjv(interp, newObjc, newObjv, 0); Tcl_DecrRefCount(newCmd); delete[] newObjv; return result; } -void inspect_globals(Tcl_Interp* interp) { - auto get_var = [&](const char* name) -> const char* { - return Tcl_GetVar(interp, name, TCL_GLOBAL_ONLY); - }; - const char* idx_s = get_var("sdc_call_index"); +struct SdcGraphNode { + using Child = std::variant; + std::vector 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(&child)) + os << *s; + else if (SdcGraphNode*& c = *std::get_if(&child)) { + os << "["; + c->dump(os); + os << "]"; + } else { + log_assert(false); + } + } + } +}; + +std::vector> gather_nested_calls(Tcl_Interp* interp) { + const char* idx_s = Tcl_GetVar(interp, "sdc_call_index", TCL_GLOBAL_ONLY); log_assert(idx_s); size_t node_count = std::stoi(idx_s); - // else - // printf("sdc_call_index: unset\n"); - // if (auto calls = get_var("sdc_calls")) - // printf("sdc_calls: %s\n", calls); - // else - // printf("sdc_calls: unset\n"); Tcl_Obj* listObj = Tcl_GetVar2Ex(interp, "sdc_calls", nullptr, TCL_GLOBAL_ONLY); int listLength; - // Get list length first - std::vector> nestedData; + std::vector> sdc_calls; if (Tcl_ListObjLength(interp, listObj, &listLength) == TCL_OK) { for (int i = 0; i < listLength; i++) { Tcl_Obj* subListObj; @@ -314,7 +338,7 @@ void inspect_globals(Tcl_Interp* interp) { } int subListLength; if (Tcl_ListObjLength(interp, subListObj, &subListLength) == TCL_OK) { - // It's a valid list - extract elements + // Valid list - extract elements for (int j = 0; j < subListLength; j++) { Tcl_Obj* elementObj; if (Tcl_ListObjIndex(interp, subListObj, j, &elementObj) == TCL_OK) { @@ -323,32 +347,70 @@ void inspect_globals(Tcl_Interp* interp) { } } } else { - // It's a single element, not a list + // Single element, not a list const char* elementStr = Tcl_GetString(subListObj); subList.push_back(std::string(elementStr)); } - nestedData.push_back(subList); + sdc_calls.push_back(subList); } } - std::vector has_parent; - log_assert(nestedData.size() == node_count); - has_parent.resize(node_count); + log_assert(sdc_calls.size() == node_count); + return sdc_calls; +} + +std::vector build_graph(const std::vector>& sdc_calls) { + size_t node_count = sdc_calls.size(); + std::vector graph(node_count); for (size_t i = 0; i < node_count; i++) { - for (size_t j = 0; j < nestedData[i].size(); j++) { - auto arg = nestedData[i][j]; - auto pos = arg.find("YOSYS_SDC_MAGIC_NODE_"); - if (pos == std::string::npos) - continue; - std::string rest = arg.substr(pos); - for (auto c : rest) - if (!std::isdigit(c)) - log_error("weird thing %s\n", rest.c_str()); - size_t arg_node_idx = std::stoi(rest); - log("%zu %zu %s IDX %zu\n", i, j, arg.c_str(), arg_node_idx); - // log("%zu %zu %s\n", i, j, arg.c_str()); + 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 node_ownership(const std::vector& graph) { + std::vector has_parent(graph.size()); + for (auto node : graph) { + for (auto child : node.children) { + if (SdcGraphNode** pp = std::get_if(&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& graph, const std::vector& 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) { + std::vector> sdc_calls = gather_nested_calls(interp); + std::vector graph = build_graph(sdc_calls); + dump_sdc_graph(graph, node_ownership(graph)); } static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) @@ -458,31 +520,42 @@ static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_ } } MatchConfig config(regexp_flag, nocase_flag, false); - std::vector> resolved; + std::vector> resolved; for (auto pat : patterns) { bool found = false; - for (auto name : objects->design_ports) { + for (auto [name, wire] : objects->design_ports) { auto [does_match, matching_bits] = matches(name, pat, config); if (does_match) { found = true; - resolved.push_back(std::make_tuple(name, matching_bits)); + resolved.push_back(std::make_tuple(name, wire, matching_bits)); } } if (!found) log_warning("No matches in design for port %s\n", pat.c_str()); } - Tcl_Obj *result = Tcl_NewListObj(resolved.size(), nullptr); - for (auto [obj, matching_bits] : resolved) { - Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(obj.c_str(), obj.size())); + Tcl_Obj *result = nullptr; + for (auto [name, wire, matching_bits] : resolved) { + if (objects->value_mode == SdcObjects::ValueMode::Normal) { + if (!result) + result = Tcl_NewListObj(resolved.size(), nullptr); + size_t width = wire->width; + for (size_t i = 0; i < width; i++) + if (matching_bits.is_set(i)) + Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(name.c_str(), name.size())); + } if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) { - if (objects->constrained_ports.count(obj) == 0) { - objects->constrained_ports[obj] = matching_bits; + if (objects->constrained_ports.count(name) == 0) { + objects->constrained_ports[name] = matching_bits; } else { - objects->constrained_ports[obj].merge(matching_bits); + objects->constrained_ports[name].merge(matching_bits); } } } + if (objects->value_mode == SdcObjects::ValueMode::Graph) { + return redirect_unknown(interp, objc, objv); + } + Tcl_SetObjResult(interp, result); return TCL_OK; } @@ -545,13 +618,14 @@ static int ys_track_typed_key_cmd(ClientData data, Tcl_Interp *interp, int objc, log_error("%s: pin %s not found\n", proc_name.c_str(), str); } else if (key_expect_type == "port") { bool found = false; - for (auto name : objects->design_ports) { - if (name == str) { - found = true; - objects->constrained_ports.insert(name); - break; // resolved, expected unique - } - } + log_error("TODO temporarily disabled due to working on a different flow\n"); + // for (auto [name, ] : objects->design_ports) { + // if (name == str) { + // found = true; + // objects->constrained_ports.insert(name); + // break; // resolved, expected unique + // } + // } if (!found) log_error("%s: port %s not found\n", proc_name.c_str(), str); } else { diff --git a/passes/cmds/stubs.sdc b/passes/cmds/stubs.sdc index 467ce70c6..f89050f6f 100644 --- a/passes/cmds/stubs.sdc +++ b/passes/cmds/stubs.sdc @@ -23,11 +23,12 @@ proc unknown {args} { 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 "YOSYS_SDC_MAGIC_NODE_$sdc_call_index" + return $ret } proc list {args} { - unknown "list" $args + return [unknown "list" {*}$args] } \ No newline at end of file From 48ff9d4950e850632c2987c29aa029a370bb0d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Ji=C5=99=C3=AD=20Tywoniak?= Date: Mon, 4 Aug 2025 13:27:42 +0200 Subject: [PATCH 094/153] sdc: refactor find_matching --- passes/cmds/sdc.cc | 53 ++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index f0d7175c5..0c00a4466 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -412,7 +412,25 @@ void inspect_globals(Tcl_Interp* interp) { std::vector graph = build_graph(sdc_calls); dump_sdc_graph(graph, node_ownership(graph)); } - +template +std::vector> +find_matching(U objects, const MatchConfig& config, const std::vector &patterns, const char* obj_type) +{ + std::vector> 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)); + } + } + if (!found) + log_warning("No matches in design for %s %s\n", obj_type, pat.c_str()); + } + return resolved; +} static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { auto* objects = (SdcObjects*)data; @@ -454,18 +472,8 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O MatchConfig config(regexp_flag, nocase_flag, hierarchical_flag); std::vector> resolved; - for (auto pat : patterns) { - bool found = false; - for (auto [name, pin] : objects->design_pins) { - auto [does_match, matching_bits] = matches(name, pat, config); - if (does_match) { - found = true; - resolved.push_back(std::make_tuple(name, pin, matching_bits)); - } - } - if (!found) - log_warning("No matches in design for pin %s\n", pat.c_str()); - } + const auto& pins = objects->design_pins; + resolved = find_matching(pins, config, patterns, "pin"); if (separator != "/") { Tcl_SetResult(interp, (char *)"Only '/' accepted as separator", TCL_STATIC); @@ -519,20 +527,12 @@ static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_ log_error("get_ports got unexpected number of patterns in simple mode\n"); } } + MatchConfig config(regexp_flag, nocase_flag, false); std::vector> resolved; - for (auto pat : patterns) { - bool found = false; - for (auto [name, wire] : objects->design_ports) { - auto [does_match, matching_bits] = matches(name, pat, config); - if (does_match) { - found = true; - resolved.push_back(std::make_tuple(name, wire, matching_bits)); - } - } - if (!found) - log_warning("No matches in design for port %s\n", pat.c_str()); - } + const auto& ports = objects->design_ports; + resolved = find_matching(ports, config, patterns, "port"); + Tcl_Obj *result = nullptr; for (auto [name, wire, matching_bits] : resolved) { if (objects->value_mode == SdcObjects::ValueMode::Normal) { @@ -676,9 +676,6 @@ struct SdcPass : public Pass { SdcPass() : Pass("sdc", "sniff at some SDC") { } void execute(std::vector args, RTLIL::Design *design) override { log_header(design, "Executing SDC pass.\n"); - // if (args.size() < 2) - // log_cmd_error("Missing SDC file.\n"); - // TODO optional extra stub file size_t argidx; std::vector opensta_stubs_paths; for (argidx = 1; argidx < args.size(); argidx++) { From 075237f73b3cadd4e4e29d18a096bdfbe590c4b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emil=20Ji=C5=99=C3=AD=20Tywoniak?= Date: Mon, 4 Aug 2025 14:22:23 +0200 Subject: [PATCH 095/153] sdc: refactor more --- passes/cmds/sdc.cc | 474 ++++++++++++++++++++++++++------------------- 1 file changed, 270 insertions(+), 204 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index 0c00a4466..9a2a5b1ed 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -1,10 +1,8 @@ #include "kernel/register.h" -#include "kernel/celltypes.h" #include "kernel/rtlil.h" #include "kernel/log.h" #include #include -#include #include #include @@ -12,6 +10,76 @@ 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 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 { @@ -28,74 +96,19 @@ struct SdcObjects { // return a new graph node assuming unknown is overridden Graph, } value_mode; - struct BitSelection { - bool all = false; - std::vector 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) { - 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; - } - }; using CellPin = std::pair; std::vector> design_ports; std::vector> design_cells; std::vector> design_pins; std::vector> design_nets; - dict constrained_ports; + using PortPattern = std::tuple; + using PinPattern = std::tuple; + std::vector> resolved_port_pattern_sets; + std::vector> resolved_pin_pattern_sets; + // TODO + + dict, BitSelection> constrained_ports; pool> constrained_cells; dict, BitSelection> constrained_pins; dict, BitSelection> constrained_nets; @@ -156,6 +169,30 @@ struct SdcObjects { sniff_module(hierarchy, top); } ~SdcObjects() = default; + + template + void build_normal_result(Tcl_Interp* interp, std::vector>&& resolved, U& tgt, std::function 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 + void merge_as_constrained(std::vector>&& 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()); @@ -186,7 +223,9 @@ struct SdcObjects { // } // log("\n"); log("Constrained ports:\n"); - for (auto [name, bits] : constrained_ports) { + for (auto [ref, bits] : constrained_ports) { + auto [name, port] = ref; + (void)port; log("\t%s\n", name.c_str()); bits.dump(); } @@ -213,16 +252,6 @@ struct SdcObjects { } }; -template -static bool parse_flag(char* arg, const char* flag_name, T& flag_var) { - std::string expected = std::string("-") + flag_name; - if (expected == arg) { - flag_var = true; - return true; - } - return false; -} - // TODO vectors // TODO cell arrays? struct MatchConfig { @@ -241,7 +270,7 @@ struct MatchConfig { hier(hierarchical_flag ? FLAT : TREE) { } }; -static std::pair matches(std::string name, const std::string& pat, const MatchConfig& config) { +static std::pair matches(std::string name, const std::string& pat, const MatchConfig& config) { (void)config; bool got_bit_index = false;; int bit_idx; @@ -258,7 +287,7 @@ static std::pair matches(std::string name, const bit_idx = std::stoi(bit_selector); } - SdcObjects::BitSelection bits = {}; + BitSelection bits = {}; if (name == pat_base) { if (got_bit_index) { bits.set(bit_idx); @@ -273,33 +302,38 @@ static std::pair matches(std::string name, const } } +static int graph_node(TclCall call) { + // TODO is that it? + return redirect_unknown(call); +} -static int redirect_unknown(Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { - Tcl_Obj *newCmd = Tcl_NewStringObj("unknown", -1); - auto newObjc = objc + 1; - Tcl_Obj **newObjv = new Tcl_Obj*[newObjc]; - newObjv[0] = newCmd; - for (int i = 1; i < newObjc; i++) { - newObjv[i] = objv[i - 1]; - log("newObjv %s\n", Tcl_GetString(newObjv[i])); - } - int result = Tcl_EvalObjv(interp, newObjc, newObjv, 0); - Tcl_DecrRefCount(newCmd); - delete[] newObjv; +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; - std::vector 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 { + using Child = std::variant; + std::vector 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) { @@ -317,13 +351,20 @@ struct SdcGraphNode { 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> gather_nested_calls(Tcl_Interp* interp) { - const char* idx_s = Tcl_GetVar(interp, "sdc_call_index", TCL_GLOBAL_ONLY); - log_assert(idx_s); - size_t node_count = std::stoi(idx_s); Tcl_Obj* listObj = Tcl_GetVar2Ex(interp, "sdc_calls", nullptr, TCL_GLOBAL_ONLY); int listLength; @@ -354,7 +395,7 @@ std::vector> gather_nested_calls(Tcl_Interp* interp) { sdc_calls.push_back(subList); } } - log_assert(sdc_calls.size() == node_count); + log_assert(sdc_calls.size() == get_node_count(interp)); return sdc_calls; } @@ -412,11 +453,13 @@ void inspect_globals(Tcl_Interp* interp) { std::vector graph = build_graph(sdc_calls); dump_sdc_graph(graph, node_ownership(graph)); } + +// patterns -> (pattern-object-bit)s template -std::vector> +std::vector> find_matching(U objects, const MatchConfig& config, const std::vector &patterns, const char* obj_type) { - std::vector> resolved; + std::vector> resolved; for (auto pat : patterns) { bool found = false; for (auto [name, obj] : objects) { @@ -424,6 +467,7 @@ find_matching(U objects, const MatchConfig& config, const std::vector patterns; - int i = 1; - for (; i < objc; i++) { - if (parse_flag(Tcl_GetString(objv[i]), "hierarchical", hierarchical_flag)) continue; - if (parse_flag(Tcl_GetString(objv[i]), "hier", hierarchical_flag)) continue; - if (parse_flag(Tcl_GetString(objv[i]), "regexp", regexp_flag)) continue; - if (parse_flag(Tcl_GetString(objv[i]), "nocase", nocase_flag)) continue; - if (!strcmp(Tcl_GetString(objv[i]), "-hsc")) { - separator = Tcl_GetString(objv[++i]); - continue; + std::vector patterns = {}; + std::initializer_list legals; + const char* name; + GetterOpts(const char* name, std::initializer_list legals) : legals(legals), name(name) {} + 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; } - if (!strcmp(Tcl_GetString(objv[i]), "-of_objects")) { - of_objects = objv[++i]; - continue; + return false; + } + template + 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; } - // Onto the next loop - break; - } - for (; i < objc; i++) { - patterns.push_back(Tcl_GetString(objv[i])); - } - if (objects->collect_mode == SdcObjects::CollectMode::SimpleGetter) { + 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("get_pins got unexpected flags in simple mode\n"); - } - if (patterns.size() != 1) { - log_error("get_pins got unexpected number of patterns in simple mode\n"); + 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"); + } +}; - MatchConfig config(regexp_flag, nocase_flag, hierarchical_flag); - std::vector> resolved; +// void build_normal_result(Tcl_Interp* interp, size_t list_len, size_t width, const std::string& name, Tcl_Obj*& result, const BitSelection& matching_bits) { +// if (!result) +// result = Tcl_NewListObj(list_len, nullptr); +// for (size_t i = 0; i < width; i++) +// if (matching_bits.is_set(i)) +// Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(name.c_str(), name.size())); +// } + +template +void merge_or_init(const T& key, dict& 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> resolved; const auto& pins = objects->design_pins; - resolved = find_matching(pins, config, patterns, "pin"); - - if (separator != "/") { - Tcl_SetResult(interp, (char *)"Only '/' accepted as separator", TCL_STATIC); - return TCL_ERROR; - } + resolved = find_matching(pins, config, opts.patterns, "pin"); Tcl_Obj *result = nullptr; - for (auto [name, pin, matching_bits] : resolved) { - if (objects->value_mode == SdcObjects::ValueMode::Normal) { - if (!result) - result = Tcl_NewListObj(resolved.size(), nullptr); - size_t width = (size_t)pin.first->getPort(pin.second).size(); - for (size_t i = 0; i < width; i++) - if (matching_bits.is_set(i)) - Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(name.c_str(), name.size())); - } + if (objects->value_mode == SdcObjects::ValueMode::Normal) { + log_error("TODO normal\n"); + auto width = [](SdcObjects::CellPin& pin) -> size_t { + return (size_t)pin.first->getPort(pin.second).size(); + }; + objects->build_normal_resultresolved_pin_pattern_sets)>(interp, std::move(resolved), objects->resolved_pin_pattern_sets, width, result); + } else if (objects->value_mode == SdcObjects::ValueMode::Graph) + return graph_node(TclCall{interp, objc, objv}); - if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) - objects->constrained_pins[std::make_pair(name, pin)].merge(matching_bits); - } + if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) + objects->merge_as_constrained(std::move(resolved)); + // merge_or_init(std::make_pair(name, pin), objects->constrained_pins, matching_bits); + // TODO + // } + + + // return objects->graph_node(interp, objc, objv, std::move(resolved), objects->resolved_pin_pattern_sets); - if (objects->value_mode == SdcObjects::ValueMode::Graph) { - return redirect_unknown(interp, objc, objv); - } if (result) Tcl_SetObjResult(interp, result); return TCL_OK; @@ -506,57 +597,32 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) { auto* objects = (SdcObjects*)data; - bool regexp_flag = false; - bool nocase_flag = false; - std::vector patterns; - int i = 1; - for (; i < objc; i++) { - if (parse_flag(Tcl_GetString(objv[i]), "regexp", regexp_flag)) continue; - if (parse_flag(Tcl_GetString(objv[i]), "nocase", nocase_flag)) continue; - // Onto the next loop - break; - } - for (; i < objc; i++) { - patterns.push_back(Tcl_GetString(objv[i])); - } - if (objects->collect_mode == SdcObjects::CollectMode::SimpleGetter) { - if (regexp_flag || nocase_flag) { - log_error("get_ports got unexpected flags in simple mode\n"); - } - if (patterns.size() != 1) { - log_error("get_ports got unexpected number of patterns in simple mode\n"); - } - } + GetterOpts opts("get_ports", {"regexp", "nocase"}); + opts.parse(objc, objv); + if (objects->collect_mode == SdcObjects::CollectMode::SimpleGetter) + opts.check_simple(); - MatchConfig config(regexp_flag, nocase_flag, false); - std::vector> resolved; + MatchConfig config(opts.regexp_flag, opts.nocase_flag, false); + std::vector> resolved; const auto& ports = objects->design_ports; - resolved = find_matching(ports, config, patterns, "port"); + resolved = find_matching(ports, config, opts.patterns, "port"); Tcl_Obj *result = nullptr; for (auto [name, wire, matching_bits] : resolved) { - if (objects->value_mode == SdcObjects::ValueMode::Normal) { - if (!result) - result = Tcl_NewListObj(resolved.size(), nullptr); - size_t width = wire->width; - for (size_t i = 0; i < width; i++) - if (matching_bits.is_set(i)) - Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(name.c_str(), name.size())); - } - if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) { - if (objects->constrained_ports.count(name) == 0) { - objects->constrained_ports[name] = matching_bits; - } else { - objects->constrained_ports[name].merge(matching_bits); - } - } + if (objects->value_mode == SdcObjects::ValueMode::Normal) + log_error("TODO normal\n"); + // objects->build_normal_result(interp, resolved.size(), wire->width, name, result, matching_bits); + + if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) + merge_or_init(std::make_pair(name, wire), objects->constrained_ports, matching_bits); } if (objects->value_mode == SdcObjects::ValueMode::Graph) { - return redirect_unknown(interp, objc, objv); + return graph_node(TclCall{interp, objc, objv}); + // return objects->graph_node(interp, objc, objv, std::move(resolved), objects->resolved_port_pattern_sets); } - - Tcl_SetObjResult(interp, result); + if (result) + Tcl_SetObjResult(interp, result); return TCL_OK; } @@ -592,10 +658,10 @@ static int ys_track_typed_key_cmd(ClientData data, Tcl_Interp *interp, int objc, if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) return TCL_OK; - std::string key_name = Tcl_GetString(objv[1]); - Tcl_Obj* key_value = objv[2]; + std::string key_name = Tcl_GetString(objv[1]); + Tcl_Obj* key_value = objv[2]; std::string key_expect_type = Tcl_GetString(objv[3]); - std::string proc_name = Tcl_GetString(objv[4]); + std::string proc_name = Tcl_GetString(objv[4]); auto track_typed = [key_expect_type, objects, proc_name, key_name](const char* str) -> void { auto split = split_at(str); @@ -608,7 +674,7 @@ static int ys_track_typed_key_cmd(ClientData data, Tcl_Interp *interp, int objc, bool found = false; for (auto [name, pin] : objects->design_pins) { log_error("TODO temporarily disabled due to working on a different flow\n"); - // if (name + "/" + pin->name.str() == str) { + // if (name + "/" + pin.name.str() == str) { // found = true; // objects->constrained_pins.insert(std::make_pair(name, pin)); // break; // resolved, expected unique @@ -674,7 +740,7 @@ public: struct SdcPass : public Pass { // TODO help SdcPass() : Pass("sdc", "sniff at some SDC") { } -void execute(std::vector args, RTLIL::Design *design) override { + void execute(std::vector args, RTLIL::Design *design) override { log_header(design, "Executing SDC pass.\n"); size_t argidx; std::vector opensta_stubs_paths; From d4228efae84cf0f9bbdab5f1150a07489d1dae19 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 2 Oct 2025 16:45:41 +0200 Subject: [PATCH 096/153] sdc: keep_hiearchy --- passes/cmds/sdc.cc | 70 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index 9a2a5b1ed..c3cd0ed0a 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -97,6 +97,7 @@ struct SdcObjects { Graph, } value_mode; using CellPin = std::pair; + Design* design; std::vector> design_ports; std::vector> design_cells; std::vector> design_pins; @@ -158,7 +159,7 @@ struct SdcObjects { } } } - SdcObjects(Design* design) { + SdcObjects(Design* design) : design(design) { Module* top = design->top_module(); if (!top) log_error("Top module couldn't be determined. Check 'top' attribute usage"); @@ -250,6 +251,47 @@ struct SdcObjects { } log("\n"); } + + class KeepHierarchyWorker { + std::unordered_set tracked_modules = {}; + Design* design = nullptr; + bool mark(Module* mod) { + for (auto* cell : mod->cells()) { + if (auto* submod = design->module(cell->type)) + if (mark(submod)) + return true; + } + + if (tracked_modules.count(mod)) + 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("%s", mod->name); + } + bool mark() { + return mark(design->top_module()); + } + }; + void keep_hierarchy() { + (void)KeepHierarchyWorker(this, design).mark(); + } }; // TODO vectors @@ -448,10 +490,11 @@ void dump_sdc_graph(const std::vector& graph, const std::vector> sdc_calls = gather_nested_calls(interp); std::vector graph = build_graph(sdc_calls); - dump_sdc_graph(graph, node_ownership(graph)); + if (dump_mode) + dump_sdc_graph(graph, node_ownership(graph)); } // patterns -> (pattern-object-bit)s @@ -743,9 +786,21 @@ struct SdcPass : public Pass { void execute(std::vector args, RTLIL::Design *design) override { log_header(design, "Executing SDC pass.\n"); size_t argidx; + bool graph_mode = false; + bool dump_mode = false; + bool keep_hierarchy_mode = false; std::vector opensta_stubs_paths; for (argidx = 1; argidx < args.size(); argidx++) { - if (args[argidx] == "-stubs" && argidx+1 < args.size()) { + if (args[argidx] == "-graph") { + graph_mode = true; + continue; + } else if (args[argidx] == "-dump") { + dump_mode = true; + continue; + } else if (args[argidx] == "-keep_hierarchy") { + keep_hierarchy_mode = true; + continue; + } else if (args[argidx] == "-stubs" && argidx+1 < args.size()) { opensta_stubs_paths.push_back(args[++argidx]); continue; } @@ -767,8 +822,11 @@ struct SdcPass : public Pass { 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)); - sdc.objects->dump(); - inspect_globals(interp); + if (dump_mode) + sdc.objects->dump(); + if (keep_hierarchy_mode) + sdc.objects->keep_hierarchy(); + inspect_globals(interp, graph_mode); Tcl_Release(interp); } } SdcPass; From 8a54e513008e9d6739da1240ca00e19a45a4baf1 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 2 Oct 2025 16:52:46 +0200 Subject: [PATCH 097/153] sdc: add -keep_hierarchy --- passes/cmds/sdc.cc | 43 ++++++++++++++++++++++++++++++++++++++++--- passes/cmds/stubs.sdc | 2 +- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index c3cd0ed0a..719cbef64 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -258,12 +258,16 @@ struct SdcObjects { bool mark(Module* mod) { for (auto* cell : mod->cells()) { if (auto* submod = design->module(cell->type)) - if (mark(submod)) + if (mark(submod)) { + mod->set_bool_attribute(ID::keep_hierarchy); return true; + } } - if (tracked_modules.count(mod)) + if (tracked_modules.count(mod)) { + mod->set_bool_attribute(ID::keep_hierarchy); return true; + } return false; } @@ -283,7 +287,7 @@ struct SdcObjects { } log_debug("keep_hierarchy tracked modules:\n"); for (auto* mod : tracked_modules) - log_debug("%s", mod->name); + log_debug("\t%s\n", mod->name); } bool mark() { return mark(design->top_module()); @@ -669,6 +673,38 @@ static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_ return TCL_OK; } +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> resolved; + const auto& ports = objects->design_nets; + resolved = find_matching(ports, config, opts.patterns, "net"); + + Tcl_Obj *result = nullptr; + for (auto [name, wire, matching_bits] : resolved) { + if (objects->value_mode == SdcObjects::ValueMode::Normal) + log_error("TODO normal\n"); + // objects->build_normal_result(interp, resolved.size(), wire->width, name, result, matching_bits); + + if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) + merge_or_init(std::make_pair(name, wire), objects->constrained_nets, matching_bits); + } + + if (objects->value_mode == SdcObjects::ValueMode::Graph) { + return graph_node(TclCall{interp, objc, objv}); + // return objects->graph_node(interp, objc, objv, std::move(resolved), objects->resolved_port_pattern_sets); + } + if (result) + Tcl_SetObjResult(interp, result); + return TCL_OK; +} + std::optional> split_at(std::string s) { size_t pos = s.find('@'); @@ -773,6 +809,7 @@ public: objects->collect_mode = SdcObjects::CollectMode::SimpleGetter; objects->value_mode = SdcObjects::ValueMode::Graph; 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); Tcl_CreateObjCommand(interp, "ys_track_typed_key", ys_track_typed_key_cmd, (ClientData) objects.get(), NULL); return interp; diff --git a/passes/cmds/stubs.sdc b/passes/cmds/stubs.sdc index f89050f6f..591d4d6cd 100644 --- a/passes/cmds/stubs.sdc +++ b/passes/cmds/stubs.sdc @@ -26,7 +26,7 @@ proc unknown {args} { 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" + # puts "unknown $args, returning YOSYS_SDC_MAGIC_NODE_$sdc_call_index" return $ret } proc list {args} { From 94dd248dfb0ce9d7a022ce4876fba2e5288dd7dd Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Tue, 7 Oct 2025 16:41:31 +0200 Subject: [PATCH 098/153] sdc: graph mode only --- passes/cmds/sdc.cc | 91 ++++++++++++---------------------------------- 1 file changed, 23 insertions(+), 68 deletions(-) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc.cc index 719cbef64..f21973480 100644 --- a/passes/cmds/sdc.cc +++ b/passes/cmds/sdc.cc @@ -90,12 +90,6 @@ struct SdcObjects { // constraint-side tracking FullConstraint, } collect_mode; - enum ValueMode { - // return something sensible and error on unknown - Normal, - // return a new graph node assuming unknown is overridden - Graph, - } value_mode; using CellPin = std::pair; Design* design; std::vector> design_ports; @@ -522,16 +516,11 @@ find_matching(U objects, const MatchConfig& config, const std::vector patterns = {}; - std::initializer_list legals; + +struct TclOpts { const char* name; - GetterOpts(const char* name, std::initializer_list legals) : legals(legals), name(name) {} + std::initializer_list legals; + TclOpts(const char* name, std::initializer_list 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; @@ -543,6 +532,16 @@ struct GetterOpts { } 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 patterns = {}; + GetterOpts(const char* name, std::initializer_list legals) : TclOpts(name, legals) {} template bool parse_flag(Tcl_Obj* obj, const char* flag_name, T& flag_var) { bool ret = parse_opt(obj, flag_name); @@ -617,28 +616,7 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O const auto& pins = objects->design_pins; resolved = find_matching(pins, config, opts.patterns, "pin"); - Tcl_Obj *result = nullptr; - if (objects->value_mode == SdcObjects::ValueMode::Normal) { - log_error("TODO normal\n"); - auto width = [](SdcObjects::CellPin& pin) -> size_t { - return (size_t)pin.first->getPort(pin.second).size(); - }; - objects->build_normal_resultresolved_pin_pattern_sets)>(interp, std::move(resolved), objects->resolved_pin_pattern_sets, width, result); - } else if (objects->value_mode == SdcObjects::ValueMode::Graph) - return graph_node(TclCall{interp, objc, objv}); - - if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) - objects->merge_as_constrained(std::move(resolved)); - // merge_or_init(std::make_pair(name, pin), objects->constrained_pins, matching_bits); - // TODO - // } - - - // return objects->graph_node(interp, objc, objv, std::move(resolved), objects->resolved_pin_pattern_sets); - - if (result) - Tcl_SetObjResult(interp, result); - return TCL_OK; + return graph_node(TclCall{interp, objc, objv}); } static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) @@ -654,23 +632,12 @@ static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_ const auto& ports = objects->design_ports; resolved = find_matching(ports, config, opts.patterns, "port"); - Tcl_Obj *result = nullptr; for (auto [name, wire, matching_bits] : resolved) { - if (objects->value_mode == SdcObjects::ValueMode::Normal) - log_error("TODO normal\n"); - // objects->build_normal_result(interp, resolved.size(), wire->width, name, result, matching_bits); - if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) merge_or_init(std::make_pair(name, wire), objects->constrained_ports, matching_bits); } - if (objects->value_mode == SdcObjects::ValueMode::Graph) { - return graph_node(TclCall{interp, objc, objv}); - // return objects->graph_node(interp, objc, objv, std::move(resolved), objects->resolved_port_pattern_sets); - } - if (result) - Tcl_SetObjResult(interp, result); - return TCL_OK; + return graph_node(TclCall{interp, objc, objv}); } static int sdc_get_nets_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) @@ -686,23 +653,12 @@ static int sdc_get_nets_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O const auto& ports = objects->design_nets; resolved = find_matching(ports, config, opts.patterns, "net"); - Tcl_Obj *result = nullptr; for (auto [name, wire, matching_bits] : resolved) { - if (objects->value_mode == SdcObjects::ValueMode::Normal) - log_error("TODO normal\n"); - // objects->build_normal_result(interp, resolved.size(), wire->width, name, result, matching_bits); - if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) merge_or_init(std::make_pair(name, wire), objects->constrained_nets, matching_bits); } - if (objects->value_mode == SdcObjects::ValueMode::Graph) { - return graph_node(TclCall{interp, objc, objv}); - // return objects->graph_node(interp, objc, objv, std::move(resolved), objects->resolved_port_pattern_sets); - } - if (result) - Tcl_SetObjResult(interp, result); - return TCL_OK; + return graph_node(TclCall{interp, objc, objv}); } std::optional> split_at(std::string s) @@ -807,7 +763,6 @@ public: objects = std::make_unique(design); objects->collect_mode = SdcObjects::CollectMode::SimpleGetter; - objects->value_mode = SdcObjects::ValueMode::Graph; 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); @@ -823,17 +778,17 @@ struct SdcPass : public Pass { void execute(std::vector args, RTLIL::Design *design) override { log_header(design, "Executing SDC pass.\n"); size_t argidx; - bool graph_mode = false; bool dump_mode = false; + bool dump_graph_mode = false; bool keep_hierarchy_mode = false; std::vector opensta_stubs_paths; for (argidx = 1; argidx < args.size(); argidx++) { - if (args[argidx] == "-graph") { - graph_mode = true; - continue; - } else if (args[argidx] == "-dump") { + 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; @@ -863,7 +818,7 @@ struct SdcPass : public Pass { sdc.objects->dump(); if (keep_hierarchy_mode) sdc.objects->keep_hierarchy(); - inspect_globals(interp, graph_mode); + inspect_globals(interp, dump_graph_mode); Tcl_Release(interp); } } SdcPass; From 224ed524fa6effede43b004221f95783faad8446 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Tue, 7 Oct 2025 18:09:12 +0200 Subject: [PATCH 099/153] sdc: move to directory --- passes/cmds/Makefile.inc | 3 +-- passes/cmds/sdc/Makefile.inc | 3 +++ passes/cmds/{ => sdc}/sdc.cc | 0 passes/cmds/{ => sdc}/stubs.sdc | 1 - 4 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 passes/cmds/sdc/Makefile.inc rename passes/cmds/{ => sdc}/sdc.cc (100%) rename passes/cmds/{ => sdc}/stubs.sdc (93%) diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index f64fdea55..c4d43fd70 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -58,6 +58,5 @@ OBJS += passes/cmds/test_select.o OBJS += passes/cmds/timeest.o OBJS += passes/cmds/linecoverage.o OBJS += passes/cmds/sort.o -OBJS += passes/cmds/sdc.o -$(eval $(call add_share_file,share/sdc,passes/cmds/stubs.sdc)) +include $(YOSYS_SRC)/passes/cmds/sdc/Makefile.inc diff --git a/passes/cmds/sdc/Makefile.inc b/passes/cmds/sdc/Makefile.inc new file mode 100644 index 000000000..1962f622f --- /dev/null +++ b/passes/cmds/sdc/Makefile.inc @@ -0,0 +1,3 @@ +OBJS += passes/cmds/sdc/sdc.o + +$(eval $(call add_share_file,share/sdc,passes/cmds/sdc/stubs.sdc)) diff --git a/passes/cmds/sdc.cc b/passes/cmds/sdc/sdc.cc similarity index 100% rename from passes/cmds/sdc.cc rename to passes/cmds/sdc/sdc.cc diff --git a/passes/cmds/stubs.sdc b/passes/cmds/sdc/stubs.sdc similarity index 93% rename from passes/cmds/stubs.sdc rename to passes/cmds/sdc/stubs.sdc index 591d4d6cd..247fe0abd 100644 --- a/passes/cmds/stubs.sdc +++ b/passes/cmds/sdc/stubs.sdc @@ -13,7 +13,6 @@ stub ys_track_untyped_key stub ys_err_key stub ys_err_flag -# TODO move to separate file and tie to graph value mode proc unknown {args} { global sdc_call_index global sdc_calls From 07de7509bf0bff0ec59787235f978fcb134188d0 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 8 Oct 2025 12:51:56 +0200 Subject: [PATCH 100/153] sdc: add -keep_hierarchy test --- Makefile | 1 + tests/sdc/alu_sub.sdc | 70 ++++++++++++++++++++++++++++++++++++++ tests/sdc/alu_sub.v | 62 +++++++++++++++++++++++++++++++++ tests/sdc/alu_sub.ys | 14 ++++++++ tests/sdc/run-test.sh | 4 +++ tests/sdc/side-effects.sdc | 2 ++ tests/sdc/side-effects.sh | 4 +++ tests/various/sdc.sdc | 5 --- 8 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 tests/sdc/alu_sub.sdc create mode 100644 tests/sdc/alu_sub.v create mode 100644 tests/sdc/alu_sub.ys create mode 100755 tests/sdc/run-test.sh create mode 100644 tests/sdc/side-effects.sdc create mode 100755 tests/sdc/side-effects.sh delete mode 100644 tests/various/sdc.sdc diff --git a/Makefile b/Makefile index 6984ba5f1..c16dbf175 100644 --- a/Makefile +++ b/Makefile @@ -902,6 +902,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 diff --git a/tests/sdc/alu_sub.sdc b/tests/sdc/alu_sub.sdc new file mode 100644 index 000000000..f298d20bc --- /dev/null +++ b/tests/sdc/alu_sub.sdc @@ -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 +############################################################################### diff --git a/tests/sdc/alu_sub.v b/tests/sdc/alu_sub.v new file mode 100644 index 000000000..d66cad18e --- /dev/null +++ b/tests/sdc/alu_sub.v @@ -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 + diff --git a/tests/sdc/alu_sub.ys b/tests/sdc/alu_sub.ys new file mode 100644 index 000000000..7e804e7a0 --- /dev/null +++ b/tests/sdc/alu_sub.ys @@ -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 diff --git a/tests/sdc/run-test.sh b/tests/sdc/run-test.sh new file mode 100755 index 000000000..971664bdb --- /dev/null +++ b/tests/sdc/run-test.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -eu +source ../gen-tests-makefile.sh +generate_mk --yosys-scripts --bash \ No newline at end of file diff --git a/tests/sdc/side-effects.sdc b/tests/sdc/side-effects.sdc new file mode 100644 index 000000000..2c2126f84 --- /dev/null +++ b/tests/sdc/side-effects.sdc @@ -0,0 +1,2 @@ +puts "This should print something:" +puts [get_ports {A[0]}] \ No newline at end of file diff --git a/tests/sdc/side-effects.sh b/tests/sdc/side-effects.sh new file mode 100755 index 000000000..88d6154a1 --- /dev/null +++ b/tests/sdc/side-effects.sh @@ -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' diff --git a/tests/various/sdc.sdc b/tests/various/sdc.sdc deleted file mode 100644 index 2143b34d7..000000000 --- a/tests/various/sdc.sdc +++ /dev/null @@ -1,5 +0,0 @@ -puts "SDC constraints file says hello from arbitrary Tcl execution" -set_false_path -from [get_pins s1/sa] -to [get_pins s1/sb] -puts "This should print something:" -puts [get_ports a b] -puts "Did it?" \ No newline at end of file From 650392d4eca33298aa836b2e01360f0f4cc9f3da Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 8 Oct 2025 13:49:53 +0200 Subject: [PATCH 101/153] sdc: specialize stubs for the call graph --- passes/cmds/sdc/Makefile.inc | 2 +- passes/cmds/sdc/{stubs.sdc => graph-stubs.sdc} | 15 --------------- passes/cmds/sdc/sdc.cc | 2 +- 3 files changed, 2 insertions(+), 17 deletions(-) rename passes/cmds/sdc/{stubs.sdc => graph-stubs.sdc} (58%) diff --git a/passes/cmds/sdc/Makefile.inc b/passes/cmds/sdc/Makefile.inc index 1962f622f..7c8036550 100644 --- a/passes/cmds/sdc/Makefile.inc +++ b/passes/cmds/sdc/Makefile.inc @@ -1,3 +1,3 @@ OBJS += passes/cmds/sdc/sdc.o -$(eval $(call add_share_file,share/sdc,passes/cmds/sdc/stubs.sdc)) +$(eval $(call add_share_file,share/sdc,passes/cmds/sdc/graph-stubs.sdc)) diff --git a/passes/cmds/sdc/stubs.sdc b/passes/cmds/sdc/graph-stubs.sdc similarity index 58% rename from passes/cmds/sdc/stubs.sdc rename to passes/cmds/sdc/graph-stubs.sdc index 247fe0abd..b668f4d18 100644 --- a/passes/cmds/sdc/stubs.sdc +++ b/passes/cmds/sdc/graph-stubs.sdc @@ -1,18 +1,3 @@ -# with Tcl's eager evaluation, we will still eval args if they're unused by a stub -proc stub {function_name} { - proc $function_name {args} "puts \"stubbed $function_name\"" -} - -proc is_suppressed {args} { - return 0 -} - -# stub current_design -#stub ys_track_typed_key -stub ys_track_untyped_key -stub ys_err_key -stub ys_err_flag - proc unknown {args} { global sdc_call_index global sdc_calls diff --git a/passes/cmds/sdc/sdc.cc b/passes/cmds/sdc/sdc.cc index f21973480..4df256a77 100644 --- a/passes/cmds/sdc/sdc.cc +++ b/passes/cmds/sdc/sdc.cc @@ -805,7 +805,7 @@ struct SdcPass : public Pass { SDCInterpreter& sdc = SDCInterpreter::get(); Tcl_Interp *interp = sdc.fresh_interp(design); Tcl_Preserve(interp); - std::string stub_path = "+/sdc/stubs.sdc"; + 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)); From f56e121ddbb1ff6a8832fa4d561bfd77ed1213fa Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 8 Oct 2025 14:32:21 +0200 Subject: [PATCH 102/153] sdc: add help --- passes/cmds/sdc/sdc.cc | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/passes/cmds/sdc/sdc.cc b/passes/cmds/sdc/sdc.cc index 4df256a77..b77ee3ebf 100644 --- a/passes/cmds/sdc/sdc.cc +++ b/passes/cmds/sdc/sdc.cc @@ -773,15 +773,33 @@ public: // Also see TclPass struct SdcPass : public Pass { - // TODO help - SdcPass() : Pass("sdc", "sniff at some SDC") { } + 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 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 opensta_stubs_paths; + std::vector stubs_paths; for (argidx = 1; argidx < args.size(); argidx++) { if (args[argidx] == "-dump") { dump_mode = true; @@ -793,7 +811,7 @@ struct SdcPass : public Pass { keep_hierarchy_mode = true; continue; } else if (args[argidx] == "-stubs" && argidx+1 < args.size()) { - opensta_stubs_paths.push_back(args[++argidx]); + stubs_paths.push_back(args[++argidx]); continue; } break; @@ -801,7 +819,9 @@ struct SdcPass : public Pass { if (argidx >= args.size()) log_cmd_error("Missing SDC file.\n"); - std::string sdc_path = args[argidx]; + 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); @@ -809,7 +829,7 @@ struct SdcPass : public Pass { 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 : opensta_stubs_paths) + 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) From 033a2d5a673d33461a497e1ab0496680d16f8717 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 8 Oct 2025 14:45:36 +0200 Subject: [PATCH 103/153] sdc: remove vestigial code for tracked constraint followup work --- passes/cmds/sdc/sdc.cc | 87 ------------------------------------------ 1 file changed, 87 deletions(-) diff --git a/passes/cmds/sdc/sdc.cc b/passes/cmds/sdc/sdc.cc index b77ee3ebf..07040184e 100644 --- a/passes/cmds/sdc/sdc.cc +++ b/passes/cmds/sdc/sdc.cc @@ -585,14 +585,6 @@ struct GetterOpts : TclOpts { } }; -// void build_normal_result(Tcl_Interp* interp, size_t list_len, size_t width, const std::string& name, Tcl_Obj*& result, const BitSelection& matching_bits) { -// if (!result) -// result = Tcl_NewListObj(list_len, nullptr); -// for (size_t i = 0; i < width; i++) -// if (matching_bits.is_set(i)) -// Tcl_ListObjAppendElement(interp, result, Tcl_NewStringObj(name.c_str(), name.size())); -// } - template void merge_or_init(const T& key, dict& dst, const BitSelection& src) { if (dst.count(key) == 0) { @@ -661,84 +653,6 @@ static int sdc_get_nets_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O return graph_node(TclCall{interp, objc, objv}); } -std::optional> split_at(std::string s) -{ - size_t pos = s.find('@'); - if (pos == std::string::npos) - return std::nullopt; - return std::make_tuple(s.substr(0, pos), s.substr(pos + 1)); -} - -// Whether string or list of strings, apply op to each string -void apply_args(Tcl_Interp *interp, std::function op, Tcl_Obj* obj) -{ - int length; - Tcl_Obj **value_list; - if (Tcl_ListObjGetElements(interp, obj, &length, &value_list) == TCL_OK) { - for (int i = 0; i < length; i++) { - op(Tcl_GetString(value_list[i])); - } - } else { - op(Tcl_GetString(obj)); - } -} - -static int ys_track_typed_key_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_Obj* const objv[]) -{ - log("ys_track_typed_key_cmd\n"); - auto* objects = (SdcObjects*)data; - if (objc != 5) - log_error("ys_track_typed_key: Unexpected number of arguments: %d (expected 5)\n", objc); - - if (objects->collect_mode != SdcObjects::CollectMode::FullConstraint) - return TCL_OK; - - std::string key_name = Tcl_GetString(objv[1]); - Tcl_Obj* key_value = objv[2]; - std::string key_expect_type = Tcl_GetString(objv[3]); - std::string proc_name = Tcl_GetString(objv[4]); - - auto track_typed = [key_expect_type, objects, proc_name, key_name](const char* str) -> void { - auto split = split_at(str); - if (!split) - log_error("%s: key %s should be a typed SDC object, but is something weird: %s\n", - proc_name.c_str(), key_name.c_str(), str); - - if (key_expect_type == "pin") { - log("PIN! %s\n", str); - bool found = false; - for (auto [name, pin] : objects->design_pins) { - log_error("TODO temporarily disabled due to working on a different flow\n"); - // if (name + "/" + pin.name.str() == str) { - // found = true; - // objects->constrained_pins.insert(std::make_pair(name, pin)); - // break; // resolved, expected unique - // } - } - if (!found) - log_error("%s: pin %s not found\n", proc_name.c_str(), str); - } else if (key_expect_type == "port") { - bool found = false; - log_error("TODO temporarily disabled due to working on a different flow\n"); - // for (auto [name, ] : objects->design_ports) { - // if (name == str) { - // found = true; - // objects->constrained_ports.insert(name); - // break; // resolved, expected unique - // } - // } - if (!found) - log_error("%s: port %s not found\n", proc_name.c_str(), str); - } else { - // TODO - log_warning("%s: unsupported type %s\n", proc_name.c_str(), key_expect_type.c_str()); - } - }; - apply_args(interp, track_typed, key_value); - return TCL_OK; -} - - class SDCInterpreter { private: @@ -766,7 +680,6 @@ public: 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); - Tcl_CreateObjCommand(interp, "ys_track_typed_key", ys_track_typed_key_cmd, (ClientData) objects.get(), NULL); return interp; } }; From 229123eb87a7ab2aa51ad5f1006a69af4f168f0e Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 8 Oct 2025 14:57:28 +0200 Subject: [PATCH 104/153] sdc: disable without YOSYS_ENABLE_TCL --- passes/cmds/sdc/sdc.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/passes/cmds/sdc/sdc.cc b/passes/cmds/sdc/sdc.cc index 07040184e..37af8a51c 100644 --- a/passes/cmds/sdc/sdc.cc +++ b/passes/cmds/sdc/sdc.cc @@ -1,3 +1,5 @@ +#ifdef YOSYS_ENABLE_TCL + #include "kernel/register.h" #include "kernel/rtlil.h" #include "kernel/log.h" @@ -757,3 +759,4 @@ struct SdcPass : public Pass { } SdcPass; YOSYS_NAMESPACE_END +#endif From 920f4793fb68d6421016d3e0acd0f86f9d3a3597 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 9 Oct 2025 20:55:15 +0200 Subject: [PATCH 105/153] sdc: error on unknown getters --- passes/cmds/sdc/graph-stubs.sdc | 26 +++++++++++++++++++++++- passes/cmds/sdc/sdc.cc | 35 +++++++++++++++++++++++++++------ tests/sdc/get_foo.sdc | 1 + tests/sdc/unknown-getter.sh | 5 +++++ 4 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 tests/sdc/get_foo.sdc create mode 100755 tests/sdc/unknown-getter.sh diff --git a/passes/cmds/sdc/graph-stubs.sdc b/passes/cmds/sdc/graph-stubs.sdc index b668f4d18..a6601f7f8 100644 --- a/passes/cmds/sdc/graph-stubs.sdc +++ b/passes/cmds/sdc/graph-stubs.sdc @@ -1,4 +1,21 @@ 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]} { @@ -15,4 +32,11 @@ proc unknown {args} { } proc list {args} { return [unknown "list" {*}$args] -} \ No newline at end of file +} +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] +} diff --git a/passes/cmds/sdc/sdc.cc b/passes/cmds/sdc/sdc.cc index 37af8a51c..17c0f0be6 100644 --- a/passes/cmds/sdc/sdc.cc +++ b/passes/cmds/sdc/sdc.cc @@ -344,9 +344,32 @@ static std::pair matches(std::string name, const std::string } } -static int graph_node(TclCall call) { - // TODO is that it? - return redirect_unknown(call); +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 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 = new Tcl_Obj*[call.objc + 1]; + 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) { @@ -610,7 +633,7 @@ static int sdc_get_pins_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O const auto& pins = objects->design_pins; resolved = find_matching(pins, config, opts.patterns, "pin"); - return graph_node(TclCall{interp, objc, objv}); + 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[]) @@ -631,7 +654,7 @@ static int sdc_get_ports_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_ merge_or_init(std::make_pair(name, wire), objects->constrained_ports, matching_bits); } - return graph_node(TclCall{interp, objc, objv}); + 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[]) @@ -652,7 +675,7 @@ static int sdc_get_nets_cmd(ClientData data, Tcl_Interp *interp, int objc, Tcl_O merge_or_init(std::make_pair(name, wire), objects->constrained_nets, matching_bits); } - return graph_node(TclCall{interp, objc, objv}); + return getter_graph_node(TclCall{interp, objc, objv}); } class SDCInterpreter diff --git a/tests/sdc/get_foo.sdc b/tests/sdc/get_foo.sdc new file mode 100644 index 000000000..0ea5ea34a --- /dev/null +++ b/tests/sdc/get_foo.sdc @@ -0,0 +1 @@ +get_foo -bar 1 diff --git a/tests/sdc/unknown-getter.sh b/tests/sdc/unknown-getter.sh new file mode 100755 index 000000000..9038834c6 --- /dev/null +++ b/tests/sdc/unknown-getter.sh @@ -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' From 1edc32dcd03a6c20ef2389aa3591cfbda8b1080f Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Wed, 19 Nov 2025 15:31:17 +0100 Subject: [PATCH 106/153] opensta, sdc_expand: mark as experimental --- techlibs/common/opensta.cc | 1 + techlibs/common/sdc_expand.cc | 1 + 2 files changed, 2 insertions(+) diff --git a/techlibs/common/opensta.cc b/techlibs/common/opensta.cc index 282481953..655fdbf2d 100644 --- a/techlibs/common/opensta.cc +++ b/techlibs/common/opensta.cc @@ -44,6 +44,7 @@ struct OpenstaPass : public Pass bool cleanup = design->scratchpad_get_bool("opensta.cleanup", true); log_header(design, "Executing OPENSTA pass.\n"); + log_experimental("opensta"); log_push(); size_t argidx; diff --git a/techlibs/common/sdc_expand.cc b/techlibs/common/sdc_expand.cc index fe8f20aa9..39c50875f 100644 --- a/techlibs/common/sdc_expand.cc +++ b/techlibs/common/sdc_expand.cc @@ -37,6 +37,7 @@ struct SdcExpandPass : public ScriptPass void execute(std::vector 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); From 58e831486d3a4e2193a3d1b7ad3c3f4870767871 Mon Sep 17 00:00:00 2001 From: Mohamed Gaber Date: Wed, 19 Nov 2025 03:42:32 +0200 Subject: [PATCH 107/153] pyosys: __getitem__ for supported classes - functions that have a const `[]` operator method now support `__getitem__` in Python - fields of a pointer type now return a `reference_internal` instead of a `copy` because classes referenced to by pointers typically aren't copyable (e.g. RTLIL::Wire, RTLIL::Module, etc) - removed duplicate of test_script.py --- pyosys/generator.py | 11 ++++++-- tests/arch/ecp5/add_sub.py | 20 ------------- .../{test_script.py => test_ecp5_addsub.py} | 1 - tests/pyosys/test_sigspec_it.py | 28 +++++++++++++++++++ 4 files changed, 36 insertions(+), 24 deletions(-) delete mode 100644 tests/arch/ecp5/add_sub.py rename tests/pyosys/{test_script.py => test_ecp5_addsub.py} (99%) create mode 100644 tests/pyosys/test_sigspec_it.py diff --git a/pyosys/generator.py b/pyosys/generator.py index c0ccdac5a..25f87d570 100644 --- a/pyosys/generator.py +++ b/pyosys/generator.py @@ -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 @@ -592,7 +594,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: @@ -604,7 +609,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, diff --git a/tests/arch/ecp5/add_sub.py b/tests/arch/ecp5/add_sub.py deleted file mode 100644 index 0232ac1db..000000000 --- a/tests/arch/ecp5/add_sub.py +++ /dev/null @@ -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) diff --git a/tests/pyosys/test_script.py b/tests/pyosys/test_ecp5_addsub.py similarity index 99% rename from tests/pyosys/test_script.py rename to tests/pyosys/test_ecp5_addsub.py index 7c3ec96ef..ddc50b775 100644 --- a/tests/pyosys/test_script.py +++ b/tests/pyosys/test_ecp5_addsub.py @@ -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" diff --git a/tests/pyosys/test_sigspec_it.py b/tests/pyosys/test_sigspec_it.py new file mode 100644 index 000000000..2876e7725 --- /dev/null +++ b/tests/pyosys/test_sigspec_it.py @@ -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)};") + From 6eb9e823e0e080624bc261382418dd3880b0ee45 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 20 Nov 2025 00:21:15 +0100 Subject: [PATCH 108/153] sdc: use Tcl memory management functionality --- passes/cmds/sdc/sdc.cc | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/passes/cmds/sdc/sdc.cc b/passes/cmds/sdc/sdc.cc index 17c0f0be6..eb1951665 100644 --- a/passes/cmds/sdc/sdc.cc +++ b/passes/cmds/sdc/sdc.cc @@ -349,20 +349,20 @@ static int getter_graph_node(TclCall call) { // 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); + Tcl_Obj* validity_tag = Tcl_NewStringObj("-getter-validated", -1); + Tcl_IncrRefCount(validity_tag); std::vector newObjv(call.objc + 1); - log_assert(call.objc > 0); + 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]; - } + 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 = new Tcl_Obj*[call.objc + 1]; - for (int i = 0; i < call.objc + 1; ++i) { - allocatedObjv[i] = newObjv[i]; - } + 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, @@ -376,14 +376,14 @@ 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]; + 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); - delete[] newObjv; + Tcl_Free((char*) newObjv); return result; } @@ -551,7 +551,7 @@ struct TclOpts { 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; })) + [&opt_name](const char* str) { return opt_name == str; })) log_cmd_error("Illegal argument %s for %s.\n", expected.c_str(), name); return true; } From 25ba41f42419c2ee6f0499d5c74bf0c6c56e0b63 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 00:24:25 +0000 Subject: [PATCH 109/153] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c16dbf175..0c673cdf8 100644 --- a/Makefile +++ b/Makefile @@ -161,7 +161,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 | cut -d'+' -f1) YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'+' -f2) From 302643330ce0f9aff4c9439d35dff693c5564f2e Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 20 Nov 2025 13:06:01 +0100 Subject: [PATCH 110/153] read_liberty: add cell context to more errors, remove log_id --- frontends/liberty/liberty.cc | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/frontends/liberty/liberty.cc b/frontends/liberty/liberty.cc index 80553347c..aa9d0f044 100644 --- a/frontends/liberty/liberty.cc +++ b/frontends/liberty/liberty.cc @@ -40,14 +40,14 @@ static RTLIL::SigSpec parse_func_identifier(RTLIL::Module *module, const char *& expr[id_len] == '_' || expr[id_len] == '[' || expr[id_len] == ']') id_len++; if (id_len == 0) - log_error("Expected identifier at `%s'.\n", expr); + log_error("Expected identifier at `%s' in %s.\n", expr, RTLIL::unescape_id(module->name)); if (id_len == 1 && (*expr == '0' || *expr == '1')) return *(expr++) == '0' ? RTLIL::State::S0 : RTLIL::State::S1; std::string id = RTLIL::escape_id(std::string(expr, id_len)); if (!module->wires_.count(id)) - log_error("Can't resolve wire name %s.\n", RTLIL::unescape_id(id)); + log_error("Can't resolve wire name %s in %s.\n", RTLIL::unescape_id(id), RTLIL::unescape_id(module->name)); expr += id_len; return module->wires_.at(id); @@ -174,7 +174,7 @@ static RTLIL::SigSpec parse_func_expr(RTLIL::Module *module, const char *expr) #endif if (stack.size() != 1 || stack.back().type != 3) - log_error("Parser error in function expr `%s'.\n", orig_expr); + log_error("Parser error in function expr `%s'in %s.\n", orig_expr, RTLIL::unescape_id(module->name)); return stack.back().sig; } @@ -211,7 +211,7 @@ static void create_ff(RTLIL::Module *module, const LibertyAst *node) } if (clk_sig.size() == 0 || data_sig.size() == 0) - log_error("FF cell %s has no next_state and/or clocked_on attribute.\n", log_id(module->name)); + log_error("FF cell %s has no next_state and/or clocked_on attribute.\n", RTLIL::unescape_id(module->name)); for (bool rerun_invert_rollback = true; rerun_invert_rollback;) { @@ -289,9 +289,9 @@ static bool create_latch(RTLIL::Module *module, const LibertyAst *node, bool fla if (enable_sig.size() == 0 || data_sig.size() == 0) { if (!flag_ignore_miss_data_latch) - log_error("Latch cell %s has no data_in and/or enable attribute.\n", log_id(module->name)); + log_error("Latch cell %s has no data_in and/or enable attribute.\n", RTLIL::unescape_id(module->name)); else - log("Ignored latch cell %s with no data_in and/or enable attribute.\n", log_id(module->name)); + log("Ignored latch cell %s with no data_in and/or enable attribute.\n", RTLIL::unescape_id(module->name)); return false; } @@ -582,9 +582,9 @@ struct LibertyFrontend : public Frontend { { if (!flag_ignore_miss_dir) { - log_error("Missing or invalid direction for pin %s on cell %s.\n", node->args.at(0), log_id(module->name)); + log_error("Missing or invalid direction for pin %s on cell %s.\n", node->args.at(0), RTLIL::unescape_id(module->name)); } else { - log("Ignoring cell %s with missing or invalid direction for pin %s.\n", log_id(module->name), node->args.at(0)); + log("Ignoring cell %s with missing or invalid direction for pin %s.\n", RTLIL::unescape_id(module->name), node->args.at(0)); delete module; goto skip_cell; } @@ -596,13 +596,13 @@ struct LibertyFrontend : public Frontend { if (node->id == "bus" && node->args.size() == 1) { if (flag_ignore_buses) { - log("Ignoring cell %s with a bus interface %s.\n", log_id(module->name), node->args.at(0)); + log("Ignoring cell %s with a bus interface %s.\n", RTLIL::unescape_id(module->name), node->args.at(0)); delete module; goto skip_cell; } if (!flag_lib) - log_error("Error in cell %s: bus interfaces are only supported in -lib mode.\n", log_id(cell_name)); + log_error("Error in cell %s: bus interfaces are only supported in -lib mode.\n", RTLIL::unescape_id(cell_name)); const LibertyAst *dir = node->find("direction"); @@ -613,7 +613,7 @@ struct LibertyFrontend : public Frontend { } if (!dir || (dir->value != "input" && dir->value != "output" && dir->value != "inout" && dir->value != "internal")) - log_error("Missing or invalid direction for bus %s on cell %s.\n", node->args.at(0), log_id(module->name)); + log_error("Missing or invalid direction for bus %s on cell %s.\n", node->args.at(0), RTLIL::unescape_id(module->name)); simple_comb_cell = false; @@ -624,7 +624,7 @@ struct LibertyFrontend : public Frontend { if (!bus_type_node || !type_map.count(bus_type_node->value)) log_error("Unknown or unsupported type for bus interface %s on cell %s.\n", - node->args.at(0).c_str(), log_id(cell_name)); + node->args.at(0).c_str(), RTLIL::unescape_id(cell_name)); int bus_type_width = std::get<0>(type_map.at(bus_type_node->value)); int bus_type_offset = std::get<1>(type_map.at(bus_type_node->value)); @@ -701,9 +701,9 @@ struct LibertyFrontend : public Frontend { if (dir->value != "inout") { // allow inout with missing function, can be used for power pins if (!flag_ignore_miss_func) { - log_error("Missing function on output %s of cell %s.\n", log_id(wire->name), log_id(module->name)); + log_error("Missing function on output %s of cell %s.\n", RTLIL::unescape_id(wire->name), RTLIL::unescape_id(module->name)); } else { - log("Ignoring cell %s with missing function on output %s.\n", log_id(module->name), log_id(wire->name)); + log("Ignoring cell %s with missing function on output %s.\n", RTLIL::unescape_id(module->name), RTLIL::unescape_id(wire->name)); delete module; goto skip_cell; } @@ -757,13 +757,13 @@ struct LibertyFrontend : public Frontend { if (design->has(cell_name)) { Module *existing_mod = design->module(cell_name); if (!flag_nooverwrite && !flag_overwrite && !existing_mod->get_bool_attribute(ID::blackbox)) { - log_error("Re-definition of cell/module %s!\n", log_id(cell_name)); + log_error("Re-definition of cell/module %s!\n", RTLIL::unescape_id(cell_name)); } else if (flag_nooverwrite) { - log("Ignoring re-definition of module %s.\n", log_id(cell_name)); + log("Ignoring re-definition of module %s.\n", RTLIL::unescape_id(cell_name)); delete module; goto skip_cell; } else { - log("Replacing existing%s module %s.\n", existing_mod->get_bool_attribute(ID::blackbox) ? " blackbox" : "", log_id(cell_name)); + log("Replacing existing%s module %s.\n", existing_mod->get_bool_attribute(ID::blackbox) ? " blackbox" : "", RTLIL::unescape_id(cell_name)); design->remove(existing_mod); } } From d5c1cd8fc0b0ada1a01f6141d729db5b38465c65 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 20 Nov 2025 13:21:17 +0100 Subject: [PATCH 111/153] read_liberty: support loopy retention cells --- frontends/liberty/liberty.cc | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/frontends/liberty/liberty.cc b/frontends/liberty/liberty.cc index aa9d0f044..0aa1cee09 100644 --- a/frontends/liberty/liberty.cc +++ b/frontends/liberty/liberty.cc @@ -191,11 +191,23 @@ static RTLIL::SigSpec create_tristate(RTLIL::Module *module, RTLIL::SigSpec func return cell->getPort(ID::Y); } +static void create_latch_ff_wires(RTLIL::Module *module, const LibertyAst *node) +{ + module->addWire(RTLIL::escape_id(node->args.at(0))); + module->addWire(RTLIL::escape_id(node->args.at(1))); +} + +static std::pair find_latch_ff_wires(RTLIL::Module *module, const LibertyAst *node) +{ + auto* iq_wire = module->wire(RTLIL::escape_id(node->args.at(0))); + auto* iqn_wire = module->wire(RTLIL::escape_id(node->args.at(1))); + log_assert(iq_wire && iqn_wire); + return std::make_pair(iq_wire, iqn_wire); +} + static void create_ff(RTLIL::Module *module, const LibertyAst *node) { - RTLIL::SigSpec iq_sig(module->addWire(RTLIL::escape_id(node->args.at(0)))); - RTLIL::SigSpec iqn_sig(module->addWire(RTLIL::escape_id(node->args.at(1)))); - + auto [iq_sig, iqn_sig] = find_latch_ff_wires(module, node); RTLIL::SigSpec clk_sig, data_sig, clear_sig, preset_sig; bool clk_polarity = true, clear_polarity = true, preset_polarity = true; @@ -270,9 +282,7 @@ static void create_ff(RTLIL::Module *module, const LibertyAst *node) static bool create_latch(RTLIL::Module *module, const LibertyAst *node, bool flag_ignore_miss_data_latch) { - RTLIL::SigSpec iq_sig(module->addWire(RTLIL::escape_id(node->args.at(0)))); - RTLIL::SigSpec iqn_sig(module->addWire(RTLIL::escape_id(node->args.at(1)))); - + auto [iq_sig, iqn_sig] = find_latch_ff_wires(module, node); RTLIL::SigSpec enable_sig, data_sig, clear_sig, preset_sig; bool enable_polarity = true, clear_polarity = true, preset_polarity = true; @@ -646,6 +656,13 @@ struct LibertyFrontend : public Frontend { { // some liberty files do not put ff/latch at the beginning of a cell // try to find "ff" or "latch" and create FF/latch _before_ processing all other nodes + // but first, in case of balloon retention cells, we need all ff/latch output wires + // defined before we add ff/latch cells + for (auto node : cell->children) + { + if ((node->id == "ff" && node->args.size() == 2) || (node->id == "latch" && node->args.size() == 2)) + create_latch_ff_wires(module, node); + } for (auto node : cell->children) { if (node->id == "ff" && node->args.size() == 2) From b3112bf025a04c95a3140a769379545e99f391fd Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 21 Nov 2025 00:43:54 +0100 Subject: [PATCH 112/153] filterlib: prefer using precedence over unsynthesizable verilog --- passes/techmap/libparse.cc | 258 ++++++++++++++----- tests/liberty/dff.lib.verilogsim.ok | 5 +- tests/liberty/normal.lib.verilogsim.ok | 28 +- tests/liberty/semicolextra.lib.verilogsim.ok | 5 +- tests/liberty/unquoted.lib.verilogsim.ok | 15 +- 5 files changed, 235 insertions(+), 76 deletions(-) diff --git a/passes/techmap/libparse.cc b/passes/techmap/libparse.cc index a9ae75c01..274a51d54 100644 --- a/passes/techmap/libparse.cc +++ b/passes/techmap/libparse.cc @@ -827,6 +827,17 @@ std::string vlog_identifier(std::string str) return str; } +void event2vl_wire(std::string &edge, LibertyExpression& parsed, const std::string& wire) +{ + edge.clear(); + if (parsed.kind == LibertyExpression::Kind::NOT) { + edge = "negedge " + wire; + // parsed = parsed.children[0]; + } else { + edge = "posedge " + wire; + } +} + void event2vl(const LibertyAst *ast, std::string &edge, std::string &expr) { edge.clear(); @@ -843,33 +854,160 @@ void event2vl(const LibertyAst *ast, std::string &edge, std::string &expr) } } -void clear_preset_var(std::string var, std::string type) +enum ClearPresetVar { + Error, + L, + H, + T, + X, +}; + +ClearPresetVar clear_preset_var(std::string type) { if (type.find('L') != std::string::npos) { + return ClearPresetVar::L; + } + if (type.find('H') != std::string::npos) { + return ClearPresetVar::H; + } + if (type.find('T') != std::string::npos) { + return ClearPresetVar::T; + } + if (type.find('X') != std::string::npos) { + return ClearPresetVar::X; + } + return ClearPresetVar::X; +} + +void print_clear_preset_var(std::string var, ClearPresetVar type) +{ + if (type == ClearPresetVar::L) { printf(" %s <= 0;\n", var.c_str()); return; } - if (type.find('H') != std::string::npos) { + if (type == ClearPresetVar::H) { printf(" %s <= 1;\n", var.c_str()); return; } - if (type.find('T') != std::string::npos) { + if (type == ClearPresetVar::T) { printf(" %s <= ~%s;\n", var.c_str(), var.c_str()); return; } - if (type.find('X') != std::string::npos) { + if (type == ClearPresetVar::X) { printf(" %s <= 'bx;\n", var.c_str()); return; } } +struct FfEdge { + std::string edge; + std::string expr; +}; +struct FfEdges { + FfEdge clock; + FfEdge clear; + FfEdge preset; + std::string edge; + void wired(FfEdge& edge, const LibertyAst* ast, const std::string& wire, const char* tag) { + auto* found = ast->find(tag); + if (!found) + return; + auto lexer = LibertyExpression::Lexer(found->value); + auto expr = LibertyExpression::parse(lexer); + event2vl_wire(edge.edge, expr, wire); + edge.expr = expr.vlog_str(); + } + FfEdges(LibertyAst* child, const std::string& clear_wire, const std::string& preset_wire) { + wired(clear, child, clear_wire, "clear"); + wired(preset, child, preset_wire, "preset"); + event2vl(child->find("clocked_on"), clock.edge, clock.expr); + edge = ""; + if (!clock.edge.empty()) + edge += (edge.empty() ? "" : ", ") + clock.edge; + if (!clear.edge.empty()) + edge += (edge.empty() ? "" : ", ") + clear.edge; + if (!preset.edge.empty()) + edge += (edge.empty() ? "" : ", ") + preset.edge; + } +}; + +struct FfVar { + std::string var; + std::string edge; + FfEdge clear; + FfEdge preset; + // Value for both asserted + const char* clear_preset_var_name; + std::string next_state; + const char* else_prefix = ""; +public: + void proc_header() { + printf(" always @(%s) begin\n", edge.c_str()); + } + void proc_footer() { + if (*else_prefix) + printf(" end\n"); + + printf(" end\n"); + } + void proc_cond(FfEdge& edge, const char* value) { + printf(" %sif (%s) begin\n", else_prefix, edge.expr.c_str()); + printf(" %s <= %s;\n", var.c_str(), value); + printf(" end\n"); + else_prefix = "else "; + } + void proc_cond_clear() { proc_cond(clear, "0"); } + void proc_cond_preset() { proc_cond(preset, "1"); } + void proc_next_state() { + if (*else_prefix) + printf(" %sbegin\n", else_prefix); + printf(" %s <= %s;\n", var.c_str(), next_state.c_str()); + } + void proc(LibertyAst* child) { + else_prefix = ""; + proc_header(); + if (!clear.expr.empty() && !preset.expr.empty()) { + ClearPresetVar clear_preset = clear_preset_var(find_non_null(child, clear_preset_var_name)->value); + if (clear_preset == ClearPresetVar::L) { + proc_cond_clear(); + proc_cond_preset(); + proc_next_state(); + proc_footer(); + return; + } else if (clear_preset == ClearPresetVar::H) { + // Notice that preset and clear are swapped + proc_cond_preset(); + proc_cond_clear(); + proc_next_state(); + proc_footer(); + return; + } else { + // Boo, we have to emit non-synthesizable verilog + printf(" %sif ((%s) && (%s)) begin\n", else_prefix, clear.expr.c_str(), preset.expr.c_str()); + print_clear_preset_var(var, clear_preset); + printf(" end\n"); + else_prefix = "else "; + } + } + if (!clear.expr.empty()) { + proc_cond_clear(); + } + if (!preset.expr.empty()) { + proc_cond_preset(); + } + proc_next_state(); + proc_footer(); + } +}; + void gen_verilogsim_cell(const LibertyAst *ast) { if (ast->find("statetable") != NULL) return; CHECK_NV(ast->args.size(), == 1); - printf("module %s (", vlog_identifier(ast->args[0]).c_str()); + auto module_name = vlog_identifier(ast->args[0]); + printf("module %s (", module_name.c_str()); bool first = true; for (auto child : ast->children) { if (child->id != "pin") @@ -883,13 +1021,29 @@ void gen_verilogsim_cell(const LibertyAst *ast) for (auto child : ast->children) { if (child->id != "ff" && child->id != "latch") continue; - printf(" reg "); first = true; + std::string iq = ""; for (auto arg : child->args) { + if (first) + printf(" reg "); printf("%s%s", first ? "" : ", ", vlog_identifier(arg).c_str()); + if (first) + iq = vlog_identifier(arg); first = false; } - printf(";\n"); + if (!first) + printf(";\n"); + first = true; + for (auto gchild : child->children) { + if (gchild->id == "clear" || gchild->id == "preset") { + if (first) + printf(" wire "); + printf("%s%s_%s", first ? "" : ", ", iq.c_str(), gchild->id.c_str()); + first = false; + } + } + if (!first) + printf(";\n"); } for (auto child : ast->children) { @@ -909,63 +1063,45 @@ void gen_verilogsim_cell(const LibertyAst *ast) if (child->id != "ff" || child->args.size() != 2) continue; - std::string iq_var = vlog_identifier(child->args[0]); - std::string iqn_var = vlog_identifier(child->args[1]); + auto iq_name = vlog_identifier(child->args[0]); + auto clear_wire = iq_name + "_clear"; + auto preset_wire = iq_name + "_preset"; + FfEdges edges(child, clear_wire, preset_wire); - std::string clock_edge, clock_expr; - event2vl(child->find("clocked_on"), clock_edge, clock_expr); - - std::string clear_edge, clear_expr; - event2vl(child->find("clear"), clear_edge, clear_expr); - - std::string preset_edge, preset_expr; - event2vl(child->find("preset"), preset_edge, preset_expr); - - std::string edge = ""; - if (!clock_edge.empty()) - edge += (edge.empty() ? "" : ", ") + clock_edge; - if (!clear_edge.empty()) - edge += (edge.empty() ? "" : ", ") + clear_edge; - if (!preset_edge.empty()) - edge += (edge.empty() ? "" : ", ") + preset_edge; - - if (edge.empty()) + if (edges.edge.empty()) continue; - printf(" always @(%s) begin\n", edge.c_str()); + std::string next_state = func2vl(find_non_null(child, "next_state")->value); + std::string not_next_state = std::string("~(") + next_state + ")"; - const char *else_prefix = ""; - if (!clear_expr.empty() && !preset_expr.empty()) { - printf(" %sif ((%s) && (%s)) begin\n", else_prefix, clear_expr.c_str(), preset_expr.c_str()); - clear_preset_var(iq_var, find_non_null(child, "clear_preset_var1")->value); - clear_preset_var(iqn_var, find_non_null(child, "clear_preset_var2")->value); - printf(" end\n"); - else_prefix = "else "; - } - if (!clear_expr.empty()) { - printf(" %sif (%s) begin\n", else_prefix, clear_expr.c_str()); - printf(" %s <= 0;\n", iq_var.c_str()); - printf(" %s <= 1;\n", iqn_var.c_str()); - printf(" end\n"); - else_prefix = "else "; - } - if (!preset_expr.empty()) { - printf(" %sif (%s) begin\n", else_prefix, preset_expr.c_str()); - printf(" %s <= 1;\n", iq_var.c_str()); - printf(" %s <= 0;\n", iqn_var.c_str()); - printf(" end\n"); - else_prefix = "else "; - } - if (*else_prefix) - printf(" %sbegin\n", else_prefix); - std::string expr = find_non_null(child, "next_state")->value; - printf(" // %s\n", expr.c_str()); - printf(" %s <= %s;\n", iq_var.c_str(), func2vl(expr).c_str()); - printf(" %s <= ~(%s);\n", iqn_var.c_str(), func2vl(expr).c_str()); - if (*else_prefix) - printf(" end\n"); - printf(" end\n"); + if (edges.clear.expr.length()) + std::swap(clear_wire, edges.clear.expr); + if (edges.preset.expr.length()) + std::swap(preset_wire, edges.preset.expr); + auto iq = FfVar { + .var = vlog_identifier(child->args[0]), + .edge = edges.edge, + .clear = edges.clear, + .preset = edges.preset, + .clear_preset_var_name = "clear_preset_var1", + .next_state = next_state, + }; + auto iqn = FfVar { + .var = vlog_identifier(child->args[1]), + .edge = edges.edge, + // Swapped clear and preset + .clear = edges.preset, + .preset = edges.clear, + .clear_preset_var_name = "clear_preset_var2", + .next_state = not_next_state, + }; + iq.proc(child); + iqn.proc(child); + if (edges.clear.expr.length()) + printf(" assign %s = %s;\n", edges.clear.expr.c_str(), clear_wire.c_str()); + if (edges.preset.expr.length()) + printf(" assign %s = %s;\n", edges.preset.expr.c_str(), preset_wire.c_str()); } for (auto child : ast->children) @@ -990,8 +1126,8 @@ void gen_verilogsim_cell(const LibertyAst *ast) const char *else_prefix = ""; if (!clear_expr.empty() && !preset_expr.empty()) { printf(" %sif ((%s) && (%s)) begin\n", else_prefix, clear_expr.c_str(), preset_expr.c_str()); - clear_preset_var(iq_var, find_non_null(child, "clear_preset_var1")->value); - clear_preset_var(iqn_var, find_non_null(child, "clear_preset_var2")->value); + print_clear_preset_var(iq_var, clear_preset_var(find_non_null(child, "clear_preset_var1")->value)); + print_clear_preset_var(iqn_var, clear_preset_var(find_non_null(child, "clear_preset_var2")->value)); printf(" end\n"); else_prefix = "else "; } diff --git a/tests/liberty/dff.lib.verilogsim.ok b/tests/liberty/dff.lib.verilogsim.ok index e560df539..52303e6fb 100644 --- a/tests/liberty/dff.lib.verilogsim.ok +++ b/tests/liberty/dff.lib.verilogsim.ok @@ -5,8 +5,11 @@ module dff (D, CLK, Q); output Q; assign Q = IQ; // IQ always @(posedge CLK) begin - // "(D)" + // D IQ <= D; + end + always @(posedge CLK) begin + // ~(D) IQN <= ~(D); end endmodule diff --git a/tests/liberty/normal.lib.verilogsim.ok b/tests/liberty/normal.lib.verilogsim.ok index 92efbf8aa..b4ad605e0 100644 --- a/tests/liberty/normal.lib.verilogsim.ok +++ b/tests/liberty/normal.lib.verilogsim.ok @@ -41,6 +41,7 @@ module imux2 (A, B, S, Y); endmodule module dff (D, CLK, RESET, PRESET, Q, QN); reg IQ, IQN; + wire IQ_clear, IQ_preset; input D; input CLK; input RESET; @@ -49,25 +50,32 @@ module dff (D, CLK, RESET, PRESET, Q, QN); assign Q = IQ; // "IQ" output QN; assign QN = IQN; // "IQN" - always @(posedge CLK, posedge RESET, posedge PRESET) begin - if ((RESET) && (PRESET)) begin + always @(posedge CLK, posedge IQ_clear, posedge IQ_preset) begin + if (IQ_clear) begin IQ <= 0; - IQN <= 0; end - else if (RESET) begin - IQ <= 0; - IQN <= 1; - end - else if (PRESET) begin + else if (IQ_preset) begin IQ <= 1; - IQN <= 0; end else begin - // "D" + // D IQ <= D; + end + end + always @(posedge CLK, posedge IQ_clear, posedge IQ_preset) begin + if (IQ_preset) begin + IQN <= 0; + end + else if (IQ_clear) begin + IQN <= 1; + end + else begin + // ~(D) IQN <= ~(D); end end + assign IQ_clear = RESET; + assign IQ_preset = PRESET; endmodule module latch (D, G, Q, QN); reg IQ, IQN; diff --git a/tests/liberty/semicolextra.lib.verilogsim.ok b/tests/liberty/semicolextra.lib.verilogsim.ok index e3b14dbd2..a11cba6e0 100644 --- a/tests/liberty/semicolextra.lib.verilogsim.ok +++ b/tests/liberty/semicolextra.lib.verilogsim.ok @@ -4,8 +4,11 @@ module DFF (D, CK, Q); input CK; output Q; always @(posedge CK) begin - // "D" + // D IQ <= D; + end + always @(posedge CK) begin + // ~(D) IQN <= ~(D); end endmodule diff --git a/tests/liberty/unquoted.lib.verilogsim.ok b/tests/liberty/unquoted.lib.verilogsim.ok index 2a2f1d173..3e0dfd3ff 100644 --- a/tests/liberty/unquoted.lib.verilogsim.ok +++ b/tests/liberty/unquoted.lib.verilogsim.ok @@ -5,8 +5,11 @@ module dff1 (D, CLK, Q); output Q; assign Q = IQ; // IQ always @(posedge CLK) begin - // !D + // (~D) IQ <= (~D); + end + always @(posedge CLK) begin + // ~((~D)) IQN <= ~((~D)); end endmodule @@ -17,8 +20,11 @@ module dff2 (D, CLK, Q); output Q; assign Q = IQ; // "IQ" always @(posedge CLK) begin - // D ' + // (~D) IQ <= (~D); + end + always @(posedge CLK) begin + // ~((~D)) IQN <= ~((~D)); end endmodule @@ -32,8 +38,11 @@ module dffe (D, EN, CLK, Q, QN); output QN; assign QN = IQN; // "IQN" always @(negedge CLK) begin - // ( D & EN ) | ( IQ & ! EN ) + // ((D&EN)|(IQ&(~EN))) IQ <= ((D&EN)|(IQ&(~EN))); + end + always @(negedge CLK) begin + // ~(((D&EN)|(IQ&(~EN)))) IQN <= ~(((D&EN)|(IQ&(~EN)))); end endmodule From bfc957ee2d201f04eb8f1b2eb9c8f98b0afbfbb1 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Fri, 21 Nov 2025 00:44:24 +0100 Subject: [PATCH 113/153] filterlib, read_liberty: add loopy retention cell formal equivalence test --- tests/liberty/dff.lib.verilogsim.ok | 2 - tests/liberty/normal.lib.verilogsim.ok | 2 - tests/liberty/read_liberty.ys | 9 ++++ tests/liberty/retention.lib | 57 ++++++++++++++++++++ tests/liberty/retention.lib.filtered.ok | 42 +++++++++++++++ tests/liberty/retention.lib.verilogsim.ok | 44 +++++++++++++++ tests/liberty/semicolextra.lib.verilogsim.ok | 2 - tests/liberty/unquoted.lib.verilogsim.ok | 6 --- 8 files changed, 152 insertions(+), 12 deletions(-) create mode 100644 tests/liberty/read_liberty.ys create mode 100644 tests/liberty/retention.lib create mode 100644 tests/liberty/retention.lib.filtered.ok create mode 100644 tests/liberty/retention.lib.verilogsim.ok diff --git a/tests/liberty/dff.lib.verilogsim.ok b/tests/liberty/dff.lib.verilogsim.ok index 52303e6fb..5f991541f 100644 --- a/tests/liberty/dff.lib.verilogsim.ok +++ b/tests/liberty/dff.lib.verilogsim.ok @@ -5,11 +5,9 @@ module dff (D, CLK, Q); output Q; assign Q = IQ; // IQ always @(posedge CLK) begin - // D IQ <= D; end always @(posedge CLK) begin - // ~(D) IQN <= ~(D); end endmodule diff --git a/tests/liberty/normal.lib.verilogsim.ok b/tests/liberty/normal.lib.verilogsim.ok index b4ad605e0..9e8beb2e1 100644 --- a/tests/liberty/normal.lib.verilogsim.ok +++ b/tests/liberty/normal.lib.verilogsim.ok @@ -58,7 +58,6 @@ module dff (D, CLK, RESET, PRESET, Q, QN); IQ <= 1; end else begin - // D IQ <= D; end end @@ -70,7 +69,6 @@ module dff (D, CLK, RESET, PRESET, Q, QN); IQN <= 1; end else begin - // ~(D) IQN <= ~(D); end end diff --git a/tests/liberty/read_liberty.ys b/tests/liberty/read_liberty.ys new file mode 100644 index 000000000..7cbb0a19d --- /dev/null +++ b/tests/liberty/read_liberty.ys @@ -0,0 +1,9 @@ +read_liberty retention.lib +rename retention_cell retention_cell_lib +read_verilog retention.lib.verilogsim +proc +rename retention_cell retention_cell_vlog +async2sync +equiv_make retention_cell_lib retention_cell_vlog equiv +equiv_induct equiv +equiv_status -assert equiv diff --git a/tests/liberty/retention.lib b/tests/liberty/retention.lib new file mode 100644 index 000000000..d2f1aa325 --- /dev/null +++ b/tests/liberty/retention.lib @@ -0,0 +1,57 @@ +library (retention) { + delay_model : table_lookup; + voltage_unit : 1V; + current_unit : 1mA; + leakage_power_unit : 1nW; + time_unit : 1ns; + capacitive_load_unit (1, pf); + pulling_resistance_unit : 1kohm; + input_threshold_pct_rise : 50; + input_threshold_pct_fall : 50; + output_threshold_pct_rise : 50; + output_threshold_pct_fall : 50; + slew_lower_threshold_pct_rise : 30; + slew_upper_threshold_pct_rise : 70; + slew_upper_threshold_pct_fall : 70; + slew_lower_threshold_pct_fall : 30; + cell ("retention_cell") { + ff (Q1,QN1) { + clocked_on : "CK"; + next_state : "(D * !SE + SI * SE)"; + clear : "(((!B2B) * !Q2) + !RD)"; + preset : "((!B2B) * Q2)"; + clear_preset_var1 : "L"; + clear_preset_var2 : "H"; + } + latch (Q2,QN2) { + enable : "B1"; + data_in : "Q1"; + } + pin (B1) { + direction : input; + } + pin (B2B) { + direction : input; + } + pin (CK) { + clock : true; + direction : input; + } + pin (D) { + direction : input; + } + pin (Q) { + direction : output; + function : "Q1"; + } + pin (RD) { + direction : input; + } + pin (SE) { + direction : input; + } + pin (SI) { + direction : input; + } + } +} \ No newline at end of file diff --git a/tests/liberty/retention.lib.filtered.ok b/tests/liberty/retention.lib.filtered.ok new file mode 100644 index 000000000..0cfc7edfc --- /dev/null +++ b/tests/liberty/retention.lib.filtered.ok @@ -0,0 +1,42 @@ +library(retention) { + cell("retention_cell") { + ff(Q1, QN1) { + clocked_on : "CK" ; + next_state : "(D * !SE + SI * SE)" ; + clear : "(((!B2B) * !Q2) + !RD)" ; + preset : "((!B2B) * Q2)" ; + clear_preset_var1 : "L" ; + clear_preset_var2 : "H" ; + } + latch(Q2, QN2) { + enable : "B1" ; + data_in : "Q1" ; + } + pin(B1) { + direction : input ; + } + pin(B2B) { + direction : input ; + } + pin(CK) { + clock : true ; + direction : input ; + } + pin(D) { + direction : input ; + } + pin(Q) { + direction : output ; + function : "Q1" ; + } + pin(RD) { + direction : input ; + } + pin(SE) { + direction : input ; + } + pin(SI) { + direction : input ; + } + } +} diff --git a/tests/liberty/retention.lib.verilogsim.ok b/tests/liberty/retention.lib.verilogsim.ok new file mode 100644 index 000000000..f264e58cd --- /dev/null +++ b/tests/liberty/retention.lib.verilogsim.ok @@ -0,0 +1,44 @@ +module retention_cell (B1, B2B, CK, D, Q, RD, SE, SI); + reg Q1, QN1; + wire Q1_clear, Q1_preset; + reg Q2, QN2; + input B1; + input B2B; + input CK; + input D; + output Q; + assign Q = Q1; // "Q1" + input RD; + input SE; + input SI; + always @(posedge CK, posedge Q1_clear, posedge Q1_preset) begin + if (Q1_clear) begin + Q1 <= 0; + end + else if (Q1_preset) begin + Q1 <= 1; + end + else begin + Q1 <= ((D&(~SE))|(SI&SE)); + end + end + always @(posedge CK, posedge Q1_clear, posedge Q1_preset) begin + if (Q1_clear) begin + QN1 <= 1; + end + else if (Q1_preset) begin + QN1 <= 0; + end + else begin + QN1 <= ~(((D&(~SE))|(SI&SE))); + end + end + assign Q1_clear = (((~B2B)&(~Q2))|(~RD)); + assign Q1_preset = ((~B2B)&Q2); + always @* begin + if (B1) begin + Q2 <= Q1; + QN2 <= ~(Q1); + end + end +endmodule diff --git a/tests/liberty/semicolextra.lib.verilogsim.ok b/tests/liberty/semicolextra.lib.verilogsim.ok index a11cba6e0..1efbf6cf0 100644 --- a/tests/liberty/semicolextra.lib.verilogsim.ok +++ b/tests/liberty/semicolextra.lib.verilogsim.ok @@ -4,11 +4,9 @@ module DFF (D, CK, Q); input CK; output Q; always @(posedge CK) begin - // D IQ <= D; end always @(posedge CK) begin - // ~(D) IQN <= ~(D); end endmodule diff --git a/tests/liberty/unquoted.lib.verilogsim.ok b/tests/liberty/unquoted.lib.verilogsim.ok index 3e0dfd3ff..f6b022194 100644 --- a/tests/liberty/unquoted.lib.verilogsim.ok +++ b/tests/liberty/unquoted.lib.verilogsim.ok @@ -5,11 +5,9 @@ module dff1 (D, CLK, Q); output Q; assign Q = IQ; // IQ always @(posedge CLK) begin - // (~D) IQ <= (~D); end always @(posedge CLK) begin - // ~((~D)) IQN <= ~((~D)); end endmodule @@ -20,11 +18,9 @@ module dff2 (D, CLK, Q); output Q; assign Q = IQ; // "IQ" always @(posedge CLK) begin - // (~D) IQ <= (~D); end always @(posedge CLK) begin - // ~((~D)) IQN <= ~((~D)); end endmodule @@ -38,11 +34,9 @@ module dffe (D, EN, CLK, Q, QN); output QN; assign QN = IQN; // "IQN" always @(negedge CLK) begin - // ((D&EN)|(IQ&(~EN))) IQ <= ((D&EN)|(IQ&(~EN))); end always @(negedge CLK) begin - // ~(((D&EN)|(IQ&(~EN)))) IQN <= ~(((D&EN)|(IQ&(~EN)))); end endmodule From 4d1b68871746e469a71eaeab3036a71ca04bdc00 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 21 Nov 2025 14:46:01 +1300 Subject: [PATCH 114/153] Tests: Add testcase for problematic ABC DONE check --- tests/techmap/bug5495.abc | 2 ++ tests/techmap/bug5495.sh | 7 +++++++ tests/techmap/bug5495.v | 7 +++++++ 3 files changed, 16 insertions(+) create mode 100644 tests/techmap/bug5495.abc create mode 100755 tests/techmap/bug5495.sh create mode 100644 tests/techmap/bug5495.v diff --git a/tests/techmap/bug5495.abc b/tests/techmap/bug5495.abc new file mode 100644 index 000000000..60a29a58a --- /dev/null +++ b/tests/techmap/bug5495.abc @@ -0,0 +1,2 @@ + +fraig_store; fraig_restore diff --git a/tests/techmap/bug5495.sh b/tests/techmap/bug5495.sh new file mode 100755 index 000000000..181797e32 --- /dev/null +++ b/tests/techmap/bug5495.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +if ! timeout 5 ../../yosys bug5495.v -p 'hierarchy; techmap; abc -script bug5495.abc' ; then + echo "Yosys failed to complete" + exit 1 +fi + diff --git a/tests/techmap/bug5495.v b/tests/techmap/bug5495.v new file mode 100644 index 000000000..37ce73ec8 --- /dev/null +++ b/tests/techmap/bug5495.v @@ -0,0 +1,7 @@ +module simple(I1, I2, O); + input wire I1; + input wire I2; + output wire O; + + assign O = I1 | I2; +endmodule From e33ca173889a9a7791f3bae94cc88aa38c053c79 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Fri, 21 Nov 2025 03:50:07 +0000 Subject: [PATCH 115/153] Force a newline to appear before YOSYS_ABC_DONE --- passes/techmap/abc.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/passes/techmap/abc.cc b/passes/techmap/abc.cc index 3a814d0b7..0963ecfde 100644 --- a/passes/techmap/abc.cc +++ b/passes/techmap/abc.cc @@ -1064,7 +1064,7 @@ void AbcModuleState::prepare_module(RTLIL::Design *design, RTLIL::Module *module abc_script += stringf("; write_blif %s/output.blif", run_abc.tempdir_name); abc_script = add_echos_to_abc_cmd(abc_script); #if defined(__linux__) && !defined(YOSYS_DISABLE_SPAWN) - abc_script += "; echo \"YOSYS_ABC_DONE\"\n"; + abc_script += "; echo; echo \"YOSYS_ABC_DONE\"\n"; #endif for (size_t i = 0; i+1 < abc_script.size(); i++) From 44ab884b062a38385d91b893390413cad6fdea82 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 21 Nov 2025 16:30:27 +1300 Subject: [PATCH 116/153] bug5495.sh: Skip test if timeout isn't available --- tests/techmap/bug5495.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/techmap/bug5495.sh b/tests/techmap/bug5495.sh index 181797e32..64bf2ca99 100755 --- a/tests/techmap/bug5495.sh +++ b/tests/techmap/bug5495.sh @@ -1,5 +1,10 @@ #!/usr/bin/env bash +if ! which timeout ; then + echo "No 'timeout', skipping test" + exit 0 +fi + if ! timeout 5 ../../yosys bug5495.v -p 'hierarchy; techmap; abc -script bug5495.abc' ; then echo "Yosys failed to complete" exit 1 From 542723d12179186305a3f6dc25f3d7a6a5056d3c Mon Sep 17 00:00:00 2001 From: KrystalDelusion <93062060+KrystalDelusion@users.noreply.github.com> Date: Sat, 22 Nov 2025 09:51:07 +1300 Subject: [PATCH 117/153] Check ENABLE_ABC validity From https://github.com/YosysHQ/yosys/pull/5497#issuecomment-3561398279, for ENABLE_ABC=1 to be valid, either ABC must be linked (LINK_ABC=1), or it must be possible to spawn executables (DISABLE_SPAWN=0). This configuration (ENABLE_ABC=1 LINK_ABC=0 DISABLE_SPAWN=1) already fails compilation in `abc.cc` trying to call `run_command()` which doesn't exist if DISABLE_SPAWN=1. All we are doing here is catching the known bad configuration and providing an explanation for why it isn't working. --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 0c673cdf8..b45b46fbf 100644 --- a/Makefile +++ b/Makefile @@ -474,6 +474,9 @@ else ifeq ($(ABCEXTERNAL),) TARGETS := $(PROGRAM_PREFIX)yosys-abc$(EXE) $(TARGETS) endif +ifeq ($(DISABLE_SPAWN),1) +$(error ENABLE_ABC=1 requires either LINK_ABC=1 or DISABLE_SPAWN=0) +endif endif endif From 615e338acd75eb43a50fd276203763d0d55b7592 Mon Sep 17 00:00:00 2001 From: Mike Inouye Date: Fri, 21 Nov 2025 14:10:05 -0800 Subject: [PATCH 118/153] Fix abc_new pass when not in NDEBUG --- passes/techmap/abc_new.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/passes/techmap/abc_new.cc b/passes/techmap/abc_new.cc index 21ffa075b..4a4f3cee8 100644 --- a/passes/techmap/abc_new.cc +++ b/passes/techmap/abc_new.cc @@ -38,7 +38,8 @@ std::vector order_modules(Design *design, std::vector modules sort.edge(submodule, m); } } - log_assert(sort.sort()); + bool sorted = sort.sort(); + log_assert(sorted); return sort.sorted; } From f098352ae6d0a8947df74f6a714954bb7f105f9f Mon Sep 17 00:00:00 2001 From: Mike Inouye Date: Fri, 21 Nov 2025 14:23:32 -0800 Subject: [PATCH 119/153] Enable xaiger2 pass when not in NDEBUG --- frontends/aiger2/xaiger.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontends/aiger2/xaiger.cc b/frontends/aiger2/xaiger.cc index d983f8c41..510da0be8 100644 --- a/frontends/aiger2/xaiger.cc +++ b/frontends/aiger2/xaiger.cc @@ -110,7 +110,8 @@ struct Xaiger2Frontend : public Frontend { for (int i = 0; i < (int) O; i++) { int po; *f >> po; - log_assert(f->get() == '\n'); + int c = f->get(); + log_assert(c == '\n'); outputs.push_back(po); } From 33a49452d9da3aa1c6a7949b7cdf1c744cca4c7d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 22 Nov 2025 00:23:19 +0000 Subject: [PATCH 120/153] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0c673cdf8..3c53c877d 100644 --- a/Makefile +++ b/Makefile @@ -161,7 +161,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.59+110 +YOSYS_VER := 0.59+117 YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2 | cut -d'+' -f1) YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'+' -f2) From 53614a37a13088333e8192ef57dd614aa4ff5cd6 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Mon, 24 Nov 2025 18:28:36 +1300 Subject: [PATCH 121/153] Use `Tcl_Size` instead of `int` to fix build errors Fixes these build errors I'm getting locally with `tcl-devel-9.0.0-7.fc42.x86_64`. I guess Tcl 9 broke this. ``` passes/cmds/sdc/sdc.cc:438:6: error: no matching function for call to 'Tcl_ListObjLength' 438 | if (Tcl_ListObjLength(interp, listObj, &listLength) == TCL_OK) { | ^~~~~~~~~~~~~~~~~ /usr/include/tclDecls.h:1788:13: note: candidate function not viable: no known conversion from 'int *' to 'Tcl_Size *' (aka 'long *') for 3rd argument 1788 | EXTERN int Tcl_ListObjLength(Tcl_Interp *interp, | ^ 1789 | Tcl_Obj *listPtr, Tcl_Size *lengthPtr); | ~~~~~~~~~~~~~~~~~~~ passes/cmds/sdc/sdc.cc:446:8: error: no matching function for call to 'Tcl_ListObjLength' 446 | if (Tcl_ListObjLength(interp, subListObj, &subListLength) == TCL_OK) { | ^~~~~~~~~~~~~~~~~ /usr/include/tclDecls.h:1788:13: note: candidate function not viable: no known conversion from 'int *' to 'Tcl_Size *' (aka 'long *') for 3rd argument 1788 | EXTERN int Tcl_ListObjLength(Tcl_Interp *interp, | ^ 1789 | Tcl_Obj *listPtr, Tcl_Size *lengthPtr); | ~~~~~~~~~~~~~~~~~~~ ``` --- passes/cmds/sdc/sdc.cc | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/passes/cmds/sdc/sdc.cc b/passes/cmds/sdc/sdc.cc index eb1951665..fad001e50 100644 --- a/passes/cmds/sdc/sdc.cc +++ b/passes/cmds/sdc/sdc.cc @@ -8,6 +8,11 @@ #include #include +#if TCL_MAJOR_VERSION < 9 +typedef int YS_Tcl_Size; +#else +typedef Tcl_Size YS_Tcl_Size; +#endif USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -432,7 +437,7 @@ static size_t get_node_count(Tcl_Interp* interp) { std::vector> gather_nested_calls(Tcl_Interp* interp) { Tcl_Obj* listObj = Tcl_GetVar2Ex(interp, "sdc_calls", nullptr, TCL_GLOBAL_ONLY); - int listLength; + YS_Tcl_Size listLength; std::vector> sdc_calls; if (Tcl_ListObjLength(interp, listObj, &listLength) == TCL_OK) { @@ -442,7 +447,7 @@ std::vector> gather_nested_calls(Tcl_Interp* interp) { if (Tcl_ListObjIndex(interp, listObj, i, &subListObj) != TCL_OK) { log_error("broken list of lists\n"); } - int subListLength; + YS_Tcl_Size subListLength; if (Tcl_ListObjLength(interp, subListObj, &subListLength) == TCL_OK) { // Valid list - extract elements for (int j = 0; j < subListLength; j++) { From e8cbc92462088abead8776a7494af0bc24f9e702 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Mon, 24 Nov 2025 11:46:09 +0100 Subject: [PATCH 122/153] abc_new: sorted -> is_sorted --- passes/techmap/abc_new.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/passes/techmap/abc_new.cc b/passes/techmap/abc_new.cc index 4a4f3cee8..4e279c577 100644 --- a/passes/techmap/abc_new.cc +++ b/passes/techmap/abc_new.cc @@ -38,8 +38,8 @@ std::vector order_modules(Design *design, std::vector modules sort.edge(submodule, m); } } - bool sorted = sort.sort(); - log_assert(sorted); + bool is_sorted = sort.sort(); + log_assert(is_sorted); return sort.sorted; } From 38a1e6614594af5437f8e486517e993941369680 Mon Sep 17 00:00:00 2001 From: De hekkende krekker <8060851+dehekkendekrekker@users.noreply.github.com> Date: Tue, 18 Oct 2022 17:36:25 -0400 Subject: [PATCH 123/153] Fixes #3515 --- libs/subcircuit/subcircuit.cc | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/libs/subcircuit/subcircuit.cc b/libs/subcircuit/subcircuit.cc index 60f27fd55..dfec80254 100644 --- a/libs/subcircuit/subcircuit.cc +++ b/libs/subcircuit/subcircuit.cc @@ -912,6 +912,10 @@ class SubCircuit::SolverWorker bool pruneEnumerationMatrix(std::vector> &enumerationMatrix, const GraphData &needle, const GraphData &haystack, int &nextRow, bool allowOverlap) { bool didSomething = true; + + // Map of j:[i where j is used] + std::map> usedNodes; + while (didSomething) { nextRow = -1; @@ -923,13 +927,23 @@ class SubCircuit::SolverWorker didSomething = true; else if (!allowOverlap && haystack.usedNodes[j]) didSomething = true; - else + else { newRow.insert(j); + usedNodes[j].insert(i); // Store the needle index by haystack node index + } } + + // This indicates there are no available haystack nodes to assign to the needle if (newRow.size() == 0) return false; + + // If there are multiple needles assigned to the haystack node, the solution is invalid + if (newRow.size() == 1 && usedNodes[*newRow.begin()].size() > 1) + return false; + if (newRow.size() >= 2 && (nextRow < 0 || needle.adjMatrix.at(nextRow).size() < needle.adjMatrix.at(i).size())) nextRow = i; + enumerationMatrix[i].swap(newRow); } } From ba31a02578a5a2decaa333fb93d80bb17014cc00 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Tue, 25 Nov 2025 07:04:34 +1300 Subject: [PATCH 124/153] tests: Add bug3515 --- tests/various/bug3515.v | 26 ++++++++++++++++++++++++++ tests/various/bug3515.ys | 21 +++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 tests/various/bug3515.v create mode 100644 tests/various/bug3515.ys diff --git a/tests/various/bug3515.v b/tests/various/bug3515.v new file mode 100644 index 000000000..220ae4ad6 --- /dev/null +++ b/tests/various/bug3515.v @@ -0,0 +1,26 @@ +// Triple AND GATE +module mod_74x08_3 ( + input A_1, + input B_1, + input A_2, + input B_2, + input A_3, + input B_3, + output Y_1, + output Y_2, + output Y_3); + +assign Y_1 = A_1 & B_1; +assign Y_2 = A_2 & B_2; +assign Y_3 = A_3 & B_3; + +endmodule + +// OR GATE +module mod_74x32_1 ( + input A_1, + input B_1, + output Y_1); + +assign Y_1 = A_1 | B_1; +endmodule diff --git a/tests/various/bug3515.ys b/tests/various/bug3515.ys new file mode 100644 index 000000000..63c2ccde2 --- /dev/null +++ b/tests/various/bug3515.ys @@ -0,0 +1,21 @@ +read_verilog << EOF +module mod_and_or ( + input a, + input b, + input c, + input d, + output reg y +); + +always @(a,b,c,d) begin + y <= (a&b)|(c&d); +end +endmodule +EOF +hierarchy -top mod_and_or +proc +opt +techmap -map ./bug3515.v +proc +extract -map ./bug3515.v -verbose +proc From a8e8746fc0f0ce32e8d08ccd9886911a0288ed97 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Tue, 25 Nov 2025 07:35:19 +1300 Subject: [PATCH 125/153] tests: Tidy up bug3515 Add base case where mapping is possible for sanity checking. --- tests/various/bug3515.ys | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/tests/various/bug3515.ys b/tests/various/bug3515.ys index 63c2ccde2..783a75bb4 100644 --- a/tests/various/bug3515.ys +++ b/tests/various/bug3515.ys @@ -1,21 +1,31 @@ +# base case is able to map read_verilog << EOF -module mod_and_or ( - input a, - input b, - input c, - input d, +module and_x3 ( + input a, b, c, d, output reg y ); -always @(a,b,c,d) begin - y <= (a&b)|(c&d); -end +assign y = (a&b)&(c&d); +endmodule +EOF +hierarchy -top and_x3 +opt +extract -map ./bug3515.v +select -assert-count 1 t:mod_74x08_3 + +# more needles than haystacks; not able to map +design -reset +read_verilog << EOF +module mod_and_or ( + input a, b, c, d, + output reg y +); + +assign y = (a&b)|(c&d); endmodule EOF hierarchy -top mod_and_or -proc opt -techmap -map ./bug3515.v -proc -extract -map ./bug3515.v -verbose -proc +extract -map ./bug3515.v +select -assert-count 2 t:$and + From 5d3599a78c6de99afc29ca92a203e2c007eb824c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 00:23:19 +0000 Subject: [PATCH 126/153] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6f3e4fa13..15cffdb90 100644 --- a/Makefile +++ b/Makefile @@ -161,7 +161,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.59+117 +YOSYS_VER := 0.59+127 YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2 | cut -d'+' -f1) YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'+' -f2) From cae020a5810a02b65c5d62d9c8ff795715715873 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Tue, 25 Nov 2025 11:29:48 +0100 Subject: [PATCH 127/153] Update ABC as per 2025-11-24 --- abc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/abc b/abc index 1c5ed1ce3..131a50dd7 160000 --- a/abc +++ b/abc @@ -1 +1 @@ -Subproject commit 1c5ed1ce378cc04beac30bb31abc4c37c8467042 +Subproject commit 131a50dd773f21ebbfc51da1d182438382a04209 From 7f9de6e48fcbbf359992423ad1c93dbe37d65584 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Tue, 25 Nov 2025 02:19:51 +0000 Subject: [PATCH 128/153] Make coverage data thread-safe --- kernel/log.cc | 2 +- kernel/log.h | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/kernel/log.cc b/kernel/log.cc index 34e56f8ac..d712eda2c 100644 --- a/kernel/log.cc +++ b/kernel/log.cc @@ -720,7 +720,7 @@ dict> get_coverage_data() if (coverage_data.count(p->id)) log_warning("found duplicate coverage id \"%s\".\n", p->id); coverage_data[p->id].first = stringf("%s:%d:%s", p->file, p->line, p->func); - coverage_data[p->id].second += p->counter; + coverage_data[p->id].second += p->counter.load(std::memory_order_relaxed); } for (auto &it : coverage_data) diff --git a/kernel/log.h b/kernel/log.h index 144570026..197cfab8d 100644 --- a/kernel/log.h +++ b/kernel/log.h @@ -24,6 +24,7 @@ #include +#include #include #define YS_REGEX_COMPILE(param) std::regex(param, \ std::regex_constants::nosubs | \ @@ -298,15 +299,16 @@ void log_abort_internal(const char *file, int line); #define cover(_id) do { \ static CoverData __d __attribute__((section("yosys_cover_list"), aligned(1), used)) = { __FILE__, __FUNCTION__, _id, __LINE__, 0 }; \ - __d.counter++; \ + __d.counter.fetch_add(1, std::memory_order_relaxed); \ } while (0) struct CoverData { const char *file, *func, *id; - int line, counter; -} YS_ATTRIBUTE(packed); + int line; + std::atomic counter; +}; -// this two symbols are created by the linker for the "yosys_cover_list" ELF section +// this two symbols are created by the linker __start_yosys_cover_listfor the "yosys_cover_list" ELF section extern "C" struct CoverData __start_yosys_cover_list[]; extern "C" struct CoverData __stop_yosys_cover_list[]; From 4c8b537d7194bbb300c23162d07b9cf25b8d7447 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Fri, 14 Nov 2025 01:52:22 +0000 Subject: [PATCH 129/153] Remove YOSYS_NO_IDS_REFCNT Refcounting is hardly used at all so this option is not that useful. We might want to have a different option that disables GC if that becomes a performance issue, but that should be a different option. --- kernel/rtlil.cc | 9 --------- kernel/rtlil.h | 6 ------ 2 files changed, 15 deletions(-) diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index d18a709c9..dc851c019 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -40,10 +40,8 @@ std::vector RTLIL::IdString::global_id_storage_; std::unordered_map RTLIL::IdString::global_id_index_; std::unordered_map RTLIL::IdString::global_autoidx_id_prefix_storage_; std::unordered_map RTLIL::IdString::global_autoidx_id_storage_; -#ifndef YOSYS_NO_IDS_REFCNT std::unordered_map RTLIL::IdString::global_refcount_storage_; std::vector RTLIL::IdString::global_free_idx_list_; -#endif static void populate(std::string_view name) { @@ -104,7 +102,6 @@ int RTLIL::IdString::really_insert(std::string_view p, std::unordered_map(malloc(p.size() + 1)); memcpy(buf, p.data(), p.size()); buf[p.size()] = 0; @@ -249,7 +242,6 @@ int RTLIL::OwningIdString::gc_count; void RTLIL::OwningIdString::collect_garbage() { int64_t start = PerformanceTimer::query(); -#ifndef YOSYS_NO_IDS_REFCNT IdStringCollector collector; for (auto &[idx, design] : *RTLIL::Design::get_all_designs()) { collector.trace(*design); @@ -291,7 +283,6 @@ void RTLIL::OwningIdString::collect_garbage() } it = global_autoidx_id_prefix_storage_.erase(it); } -#endif int64_t time_ns = PerformanceTimer::query() - start; Pass::subtract_from_current_runtime_ns(time_ns); gc_ns += time_ns; diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 4da030e8d..a674d87e9 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -153,11 +153,9 @@ struct RTLIL::IdString static std::unordered_map global_autoidx_id_prefix_storage_; // Explicit string storage for autoidx IDs static std::unordered_map global_autoidx_id_storage_; -#ifndef YOSYS_NO_IDS_REFCNT // All (index, refcount) pairs in this map have refcount > 0. static std::unordered_map global_refcount_storage_; static std::vector global_free_idx_list_; -#endif static int refcount(int idx) { auto it = global_refcount_storage_.find(idx); @@ -597,7 +595,6 @@ private: } static void get_reference(int idx) { - #ifndef YOSYS_NO_IDS_REFCNT if (idx < static_cast(StaticId::STATIC_ID_END)) return; auto it = global_refcount_storage_.find(idx); @@ -605,7 +602,6 @@ private: global_refcount_storage_.insert(it, {idx, 1}); else ++it->second; - #endif #ifdef YOSYS_XTRACE_GET_PUT if (yosys_xtrace && idx >= static_cast(StaticId::STATIC_ID_END)) log("#X# GET-BY-INDEX '%s' (index %d, refcount %u)\n", from_index(idx), idx, refcount(idx)); @@ -614,7 +610,6 @@ private: void put_reference() { - #ifndef YOSYS_NO_IDS_REFCNT // put_reference() may be called from destructors after the destructor of // global_refcount_storage_ has been run. in this case we simply do nothing. if (index_ < static_cast(StaticId::STATIC_ID_END) || !destruct_guard_ok) @@ -628,7 +623,6 @@ private: if (--it->second == 0) { global_refcount_storage_.erase(it); } - #endif } }; From 8f0ecce53f4293e324dfe6cefbed26b6c86d6ade Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Mon, 24 Nov 2025 22:29:06 +0000 Subject: [PATCH 130/153] Forbid creating IdStrings and incrementing autoidx during multithreaded phases, and add dynamic checks for that We could make it safe to increment autoidx during multithreaded passes, but that's actually undesirable because it would lead to nondeterminism. If/when we need new IDs during parallel passes, we'll have to figure out how to allocate them in a deterministic way, and that will depend on the details of what the pass does. So don't try to tackle that now. --- frontends/blif/blifparse.cc | 2 +- kernel/rtlil.cc | 2 +- kernel/rtlil.h | 6 ++++++ kernel/yosys.cc | 24 +++++++++++++++++++++++- kernel/yosys_common.h | 25 ++++++++++++++++++++++++- 5 files changed, 55 insertions(+), 4 deletions(-) diff --git a/frontends/blif/blifparse.cc b/frontends/blif/blifparse.cc index 0aa3a173d..bff347ea2 100644 --- a/frontends/blif/blifparse.cc +++ b/frontends/blif/blifparse.cc @@ -245,7 +245,7 @@ void parse_blif(RTLIL::Design *design, std::istream &f, IdString dff_name, bool if (undef_wire != nullptr) module->rename(undef_wire, stringf("$undef$%d", ++blif_maxnum)); - autoidx = std::max(autoidx, blif_maxnum+1); + autoidx.ensure_at_least(blif_maxnum+1); blif_maxnum = 0; } diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index dc851c019..9477b04a8 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -98,7 +98,7 @@ int RTLIL::IdString::really_insert(std::string_view p, std::unordered_map(StaticId::STATIC_ID_END)) return; auto it = global_refcount_storage_.find(idx); @@ -610,6 +614,8 @@ private: void put_reference() { + log_assert(!Multithreading::active()); + // put_reference() may be called from destructors after the destructor of // global_refcount_storage_ has been run. in this case we simply do nothing. if (index_ < static_cast(StaticId::STATIC_ID_END) || !destruct_guard_ok) diff --git a/kernel/yosys.cc b/kernel/yosys.cc index 009da56b9..2c9b8304d 100644 --- a/kernel/yosys.cc +++ b/kernel/yosys.cc @@ -19,6 +19,7 @@ #include "kernel/yosys.h" #include "kernel/celltypes.h" +#include "kernel/log.h" #ifdef YOSYS_ENABLE_READLINE # include @@ -80,7 +81,7 @@ extern "C" PyObject* PyInit_pyosys(); YOSYS_NAMESPACE_BEGIN -int autoidx = 1; +Autoidx autoidx(1); int yosys_xtrace = 0; bool yosys_write_versions = true; const char* yosys_maybe_version() { @@ -108,9 +109,30 @@ uint32_t Hasher::fudge = 0; std::string yosys_share_dirname; std::string yosys_abc_executable; +bool Multithreading::active_ = false; + void init_share_dirname(); void init_abc_executable_name(); +Multithreading::Multithreading() { + log_assert(!active_); + active_ = true; +} + +Multithreading::~Multithreading() { + log_assert(active_); + active_ = false; +} + +void Autoidx::ensure_at_least(int v) { + value = std::max(value, v); +} + +int Autoidx::operator++(int) { + log_assert(!Multithreading::active()); + return value++; +} + void memhasher_on() { #if defined(__linux__) || defined(__FreeBSD__) diff --git a/kernel/yosys_common.h b/kernel/yosys_common.h index 2374182ee..55e7b71eb 100644 --- a/kernel/yosys_common.h +++ b/kernel/yosys_common.h @@ -267,7 +267,30 @@ int ceil_log2(int x) YS_ATTRIBUTE(const); template int GetSize(const T &obj) { return obj.size(); } inline int GetSize(RTLIL::Wire *wire); -extern int autoidx; +// When multiple threads are accessing RTLIL, one of these guard objects +// must exist. +struct Multithreading +{ + Multithreading(); + ~Multithreading(); + // Returns true when multiple threads are accessing RTLIL. + // autoidx cannot be used during such times. + // IdStrings cannot be created during such times. + static bool active() { return active_; } +private: + static bool active_; +}; + +struct Autoidx { + Autoidx(int value) : value(value) {} + operator int() const { return value; } + void ensure_at_least(int v); + int operator++(int); +private: + int value; +}; + +extern Autoidx autoidx; extern int yosys_xtrace; extern bool yosys_write_versions; From 948001f39f016de043e8cb59165015347b1a34bc Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Tue, 25 Nov 2025 00:47:11 +0000 Subject: [PATCH 131/153] Merge the two autoidx hashtables into one When something calls `IdString::c_str()` on an autoidx ID, we need to cache the full string in a thread-safe way. If we need to allocate an entry in some data structure to do that, it's difficult to do in a thread-safe no-performance-hazard way. So instead, store the cached string pointer in the same hashtable as the prefix pointer. --- kernel/rtlil.cc | 19 ++++++++----------- kernel/rtlil.h | 36 ++++++++++++++++++++---------------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 9477b04a8..6077237ac 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -38,8 +38,7 @@ bool RTLIL::IdString::destruct_guard_ok = false; RTLIL::IdString::destruct_guard_t RTLIL::IdString::destruct_guard; std::vector RTLIL::IdString::global_id_storage_; std::unordered_map RTLIL::IdString::global_id_index_; -std::unordered_map RTLIL::IdString::global_autoidx_id_prefix_storage_; -std::unordered_map RTLIL::IdString::global_autoidx_id_storage_; +std::unordered_map RTLIL::IdString::global_autoidx_id_storage_; std::unordered_map RTLIL::IdString::global_refcount_storage_; std::vector RTLIL::IdString::global_free_idx_list_; @@ -93,8 +92,9 @@ int RTLIL::IdString::really_insert(std::string_view p, std::unordered_map p_autoidx = parse_autoidx(p.substr(autoidx_pos)); if (p_autoidx.has_value()) { - auto prefix_it = global_autoidx_id_prefix_storage_.find(-*p_autoidx); - if (prefix_it != global_autoidx_id_prefix_storage_.end() && p.substr(0, autoidx_pos) == *prefix_it->second) + auto autoidx_it = global_autoidx_id_storage_.find(-*p_autoidx); + if (autoidx_it != global_autoidx_id_storage_.end() && + p.substr(0, autoidx_pos) == *autoidx_it->second.prefix) return -*p_autoidx; // Ensure NEW_ID/NEW_ID_SUFFIX will not create collisions with the ID // we're about to create. @@ -267,7 +267,7 @@ void RTLIL::OwningIdString::collect_garbage() global_free_idx_list_.push_back(i); } - for (auto it = global_autoidx_id_prefix_storage_.begin(); it != global_autoidx_id_prefix_storage_.end();) { + for (auto it = global_autoidx_id_storage_.begin(); it != global_autoidx_id_storage_.end();) { if (collector.live.find(it->first) != collector.live.end()) { ++it; continue; @@ -276,13 +276,10 @@ void RTLIL::OwningIdString::collect_garbage() ++it; continue; } - auto str_it = global_autoidx_id_storage_.find(it->first); - if (str_it != global_autoidx_id_storage_.end()) { - delete[] str_it->second; - global_autoidx_id_storage_.erase(str_it); - } - it = global_autoidx_id_prefix_storage_.erase(it); + delete[] it->second.full_str; + it = global_autoidx_id_storage_.erase(it); } + int64_t time_ns = PerformanceTimer::query() - start; Pass::subtract_from_current_runtime_ns(time_ns); gc_ns += time_ns; diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 29eac294f..06c38c52e 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -134,6 +134,13 @@ struct RTLIL::IdString std::string_view str_view() const { return {buf, static_cast(size)}; } }; + struct AutoidxStorage { + // Append the negated (i.e. positive) ID to this string to get + // the real string. The prefix strings must live forever. + const std::string *prefix; + // Cache of the full string, or nullptr if not cached yet. + char *full_str; + }; // the global id string cache @@ -147,12 +154,9 @@ struct RTLIL::IdString static std::vector global_id_storage_; // Lookup table for non-autoidx IDs static std::unordered_map global_id_index_; - // Shared prefix string storage for autoidx IDs, which have negative - // indices. Append the negated (i.e. positive) ID to this string to get - // the real string. The prefix strings must live forever. - static std::unordered_map global_autoidx_id_prefix_storage_; - // Explicit string storage for autoidx IDs - static std::unordered_map global_autoidx_id_storage_; + // Storage for autoidx IDs, which have negative indices, i.e. all entries in this + // map have negative keys. + static std::unordered_map global_autoidx_id_storage_; // All (index, refcount) pairs in this map have refcount > 0. static std::unordered_map global_refcount_storage_; static std::vector global_free_idx_list_; @@ -205,7 +209,7 @@ struct RTLIL::IdString static IdString new_autoidx_with_prefix(const std::string *prefix) { log_assert(!Multithreading::active()); int index = -(autoidx++); - global_autoidx_id_prefix_storage_.insert({index, prefix}); + global_autoidx_id_storage_.insert({index, {prefix, nullptr}}); return from_index(index); } @@ -238,16 +242,16 @@ struct RTLIL::IdString inline const char *c_str() const { if (index_ >= 0) return global_id_storage_.at(index_).buf; - auto it = global_autoidx_id_storage_.find(index_); - if (it != global_autoidx_id_storage_.end()) - return it->second; - const std::string &prefix = *global_autoidx_id_prefix_storage_.at(index_); + AutoidxStorage &s = global_autoidx_id_storage_.at(index_); + if (s.full_str != nullptr) + return s.full_str; + const std::string &prefix = *s.prefix; std::string suffix = std::to_string(-index_); char *c = new char[prefix.size() + suffix.size() + 1]; memcpy(c, prefix.data(), prefix.size()); memcpy(c + prefix.size(), suffix.c_str(), suffix.size() + 1); - global_autoidx_id_storage_.insert(it, {index_, c}); + s.full_str = c; return c; } @@ -262,7 +266,7 @@ struct RTLIL::IdString *out += global_id_storage_.at(index_).str_view(); return; } - *out += *global_autoidx_id_prefix_storage_.at(index_); + *out += *global_autoidx_id_storage_.at(index_).prefix; *out += std::to_string(-index_); } @@ -348,7 +352,7 @@ struct RTLIL::IdString if (index_ >= 0) { return const_iterator(global_id_storage_.at(index_)); } - return const_iterator(global_autoidx_id_prefix_storage_.at(index_), -index_); + return const_iterator(global_autoidx_id_storage_.at(index_).prefix, -index_); } const_iterator end() const { return const_iterator(); @@ -358,7 +362,7 @@ struct RTLIL::IdString if (index_ >= 0) { return Substrings(global_id_storage_.at(index_)); } - return Substrings(global_autoidx_id_prefix_storage_.at(index_), -index_); + return Substrings(global_autoidx_id_storage_.at(index_).prefix, -index_); } inline bool lt_by_name(const IdString &rhs) const { @@ -411,7 +415,7 @@ struct RTLIL::IdString #endif return *(storage.buf + i); } - const std::string &id_start = *global_autoidx_id_prefix_storage_.at(index_); + const std::string &id_start = *global_autoidx_id_storage_.at(index_).prefix; if (i < id_start.size()) return id_start[i]; i -= id_start.size(); From b23dc345ae915f5ffad10fc8ebbdd3668dc6c849 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Tue, 25 Nov 2025 01:02:01 +0000 Subject: [PATCH 132/153] Make it safe to access .c_str() for autoidx IDs in a multithreaded context --- kernel/rtlil.cc | 1 - kernel/rtlil.h | 19 +++++++++++++------ pyosys/generator.py | 3 +-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 6077237ac..6960b7620 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -276,7 +276,6 @@ void RTLIL::OwningIdString::collect_garbage() ++it; continue; } - delete[] it->second.full_str; it = global_autoidx_id_storage_.erase(it); } diff --git a/kernel/rtlil.h b/kernel/rtlil.h index 06c38c52e..f841df1ed 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -139,7 +139,11 @@ struct RTLIL::IdString // the real string. The prefix strings must live forever. const std::string *prefix; // Cache of the full string, or nullptr if not cached yet. - char *full_str; + std::atomic full_str; + + AutoidxStorage(const std::string *prefix) : prefix(prefix), full_str(nullptr) {} + AutoidxStorage(AutoidxStorage&& other) : prefix(other.prefix), full_str(other.full_str.exchange(nullptr, std::memory_order_relaxed)) {} + ~AutoidxStorage() { delete[] full_str.load(std::memory_order_acquire); } }; // the global id string cache @@ -209,7 +213,7 @@ struct RTLIL::IdString static IdString new_autoidx_with_prefix(const std::string *prefix) { log_assert(!Multithreading::active()); int index = -(autoidx++); - global_autoidx_id_storage_.insert({index, {prefix, nullptr}}); + global_autoidx_id_storage_.insert({index, prefix}); return from_index(index); } @@ -244,15 +248,18 @@ struct RTLIL::IdString return global_id_storage_.at(index_).buf; AutoidxStorage &s = global_autoidx_id_storage_.at(index_); - if (s.full_str != nullptr) - return s.full_str; + char *full_str = s.full_str.load(std::memory_order_acquire); + if (full_str != nullptr) + return full_str; const std::string &prefix = *s.prefix; std::string suffix = std::to_string(-index_); char *c = new char[prefix.size() + suffix.size() + 1]; memcpy(c, prefix.data(), prefix.size()); memcpy(c + prefix.size(), suffix.c_str(), suffix.size() + 1); - s.full_str = c; - return c; + if (s.full_str.compare_exchange_strong(full_str, c, std::memory_order_acq_rel)) + return c; + delete[] c; + return full_str; } inline std::string str() const { diff --git a/pyosys/generator.py b/pyosys/generator.py index 25f87d570..7d4293abd 100644 --- a/pyosys/generator.py +++ b/pyosys/generator.py @@ -164,8 +164,7 @@ pyosys_headers = [ { "global_id_storage_", "global_id_index_", - "global_negative_id_storage_", - "global_negative_id_prefix_storage_", + "global_autoidx_id_storage_", "global_refcount_storage_", "global_free_idx_list_", "builtin_ff_cell_types", From 752d24c0a8f5f41e6d0700ef8807dc853729ec7c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 00:24:41 +0000 Subject: [PATCH 133/153] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 15cffdb90..52f5c31ac 100644 --- a/Makefile +++ b/Makefile @@ -161,7 +161,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.59+127 +YOSYS_VER := 0.59+130 YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2 | cut -d'+' -f1) YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'+' -f2) From 7cb3a0f83000b80f17df9a95aca4666464db1740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Wed, 26 Nov 2025 12:34:02 +0100 Subject: [PATCH 134/153] Add static library option --- Makefile | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 52f5c31ac..f919e31b6 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,7 @@ ENABLE_VERIFIC_EDIF := 0 ENABLE_VERIFIC_LIBERTY := 0 ENABLE_COVER := 1 ENABLE_LIBYOSYS := 0 +ENABLE_LIBYOSYS_STATIC := 0 ENABLE_ZLIB := 1 ENABLE_HELP_SOURCE := 0 @@ -342,6 +343,9 @@ endif ifeq ($(ENABLE_LIBYOSYS),1) TARGETS += libyosys.so +ifeq ($(ENABLE_LIBYOSYS_STATIC),1) +TARGETS += libyosys.a +endif endif PY_WRAPPER_FILE = pyosys/wrappers @@ -775,6 +779,9 @@ else $(P) $(CXX) -o libyosys.so -shared -Wl,-soname,libyosys.so $(LINKFLAGS) $^ $(LIBS) $(LIBS_VERIFIC) endif +libyosys.a: $(filter-out kernel/driver.o,$(OBJS)) + $(P) ar rcs $@ $^ + %.o: %.cc $(Q) mkdir -p $(dir $@) $(P) $(CXX) -o $@ -c $(CPPFLAGS) $(CXXFLAGS) $< @@ -1026,7 +1033,7 @@ install-dev: $(PROGRAM_PREFIX)yosys-config share install: $(TARGETS) $(EXTRA_TARGETS) $(INSTALL_SUDO) mkdir -p $(DESTDIR)$(BINDIR) - $(INSTALL_SUDO) cp $(filter-out libyosys.so,$(TARGETS)) $(DESTDIR)$(BINDIR) + $(INSTALL_SUDO) cp $(filter-out libyosys.so libyosys.a,$(TARGETS)) $(DESTDIR)$(BINDIR) ifneq ($(filter $(PROGRAM_PREFIX)yosys,$(TARGETS)),) if [ -n "$(STRIP)" ]; then $(INSTALL_SUDO) $(STRIP) -S $(DESTDIR)$(BINDIR)/$(PROGRAM_PREFIX)yosys; fi endif @@ -1042,6 +1049,9 @@ ifeq ($(ENABLE_LIBYOSYS),1) $(INSTALL_SUDO) mkdir -p $(DESTDIR)$(LIBDIR) $(INSTALL_SUDO) cp libyosys.so $(DESTDIR)$(LIBDIR)/ if [ -n "$(STRIP)" ]; then $(INSTALL_SUDO) $(STRIP) -S $(DESTDIR)$(LIBDIR)/libyosys.so; fi +ifeq ($(ENABLE_LIBYOSYS_STATIC),1) + $(INSTALL_SUDO) cp libyosys.a $(DESTDIR)$(LIBDIR)/ +endif ifeq ($(ENABLE_PYOSYS),1) $(INSTALL_SUDO) mkdir -p $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys $(INSTALL_SUDO) cp $(YOSYS_SRC)/pyosys/__init__.py $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/__init__.py @@ -1064,6 +1074,9 @@ uninstall: $(INSTALL_SUDO) rm -rvf $(DESTDIR)$(DATDIR) ifeq ($(ENABLE_LIBYOSYS),1) $(INSTALL_SUDO) rm -vf $(DESTDIR)$(LIBDIR)/libyosys.so +ifeq ($(ENABLE_LIBYOSYS_STATIC),1) + $(INSTALL_SUDO) rm -vf $(DESTDIR)$(LIBDIR)/libyosys.a +endif ifeq ($(ENABLE_PYOSYS),1) $(INSTALL_SUDO) rm -vf $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/libyosys.so $(INSTALL_SUDO) rm -vf $(DESTDIR)$(PYTHON_DESTDIR)/$(subst -,_,$(PROGRAM_PREFIX))pyosys/__init__.py @@ -1162,7 +1175,7 @@ clean-py: rm -f $(PY_WRAPPER_FILE).inc.cc $(PY_WRAPPER_FILE).cc rm -f $(PYTHON_OBJECTS) rm -f *.whl - rm -f libyosys.so + rm -f libyosys.so libyosys.a rm -rf kernel/*.pyh clean-abc: From c1e40e113c24c1279a6d592c96c8a34fcbe072d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Povi=C5=A1er?= Date: Wed, 26 Nov 2025 13:02:44 +0100 Subject: [PATCH 135/153] Use `$(AR)` --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f919e31b6..8959eacf9 100644 --- a/Makefile +++ b/Makefile @@ -780,7 +780,7 @@ else endif libyosys.a: $(filter-out kernel/driver.o,$(OBJS)) - $(P) ar rcs $@ $^ + $(P) $(AR) rcs $@ $^ %.o: %.cc $(Q) mkdir -p $(dir $@) From e4044e1b4a28e4412a168d6ed3fe7c4192145814 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 00:24:24 +0000 Subject: [PATCH 136/153] Bump version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8959eacf9..ccb1be1b9 100644 --- a/Makefile +++ b/Makefile @@ -162,7 +162,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.59+130 +YOSYS_VER := 0.59+134 YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2 | cut -d'+' -f1) YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'+' -f2) From d603b7bb58fc85d43ef1a49bb50bd086ebb52d0e Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sat, 29 Nov 2025 14:23:51 -0800 Subject: [PATCH 137/153] Update ABC --- abc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/abc b/abc index 1c5ed1ce3..131a50dd7 160000 --- a/abc +++ b/abc @@ -1 +1 @@ -Subproject commit 1c5ed1ce378cc04beac30bb31abc4c37c8467042 +Subproject commit 131a50dd773f21ebbfc51da1d182438382a04209 From 9909049d2a5b2fb0c5ca633b87db6f3890797cb7 Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sat, 29 Nov 2025 14:55:55 -0800 Subject: [PATCH 138/153] Undo formatting changes --- backends/functional/smtlib_rosette.cc | 110 ++++++++++++-------------- 1 file changed, 52 insertions(+), 58 deletions(-) diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc index d46104d9c..ee5fb8f54 100644 --- a/backends/functional/smtlib_rosette.cc +++ b/backends/functional/smtlib_rosette.cc @@ -19,8 +19,8 @@ */ #include "kernel/functional.h" -#include "kernel/sexpr.h" #include "kernel/yosys.h" +#include "kernel/sexpr.h" #include USING_YOSYS_NAMESPACE @@ -29,24 +29,26 @@ PRIVATE_NAMESPACE_BEGIN using SExprUtil::list; const char *reserved_keywords[] = { - // reserved keywords from the racket spec - "struct", "lambda", "values", "extract", "concat", "bv", "let", "define", "cons", "list", "read", "write", "stream", "error", "raise", "exit", - "for", "begin", "when", "unless", "module", "require", "provide", "apply", "if", "cond", "even", "odd", "any", "and", "or", "match", "command-line", - "ffi-lib", "thread", "kill", "sync", "future", "touch", "subprocess", "make-custodian", "custodian-shutdown-all", "current-custodian", "make", - "tcp", "connect", "prepare", "malloc", "free", "_fun", "_cprocedure", "build", "path", "file", "peek", "bytes", "flush", "with", "lexer", "parser", - "syntax", "interface", "send", "make-object", "new", "instantiate", "define-generics", "set", + // reserved keywords from the racket spec + "struct", "lambda", "values", "extract", "concat", "bv", "let", "define", "cons", "list", "read", "write", + "stream", "error", "raise", "exit", "for", "begin", "when", "unless", "module", "require", "provide", "apply", + "if", "cond", "even", "odd", "any", "and", "or", "match", "command-line", "ffi-lib", "thread", "kill", "sync", + "future", "touch", "subprocess", "make-custodian", "custodian-shutdown-all", "current-custodian", "make", "tcp", + "connect", "prepare", "malloc", "free", "_fun", "_cprocedure", "build", "path", "file", "peek", "bytes", + "flush", "with", "lexer", "parser", "syntax", "interface", "send", "make-object", "new", "instantiate", + "define-generics", "set", - // reserved for our own purposes - "inputs", "state", "name", nullptr}; + // reserved for our own purposes + "inputs", "state", "name", + nullptr +}; struct SmtrScope : public Functional::Scope { - SmtrScope() - { - for (const char **p = reserved_keywords; *p != nullptr; p++) + SmtrScope() { + for(const char **p = reserved_keywords; *p != nullptr; p++) reserve(*p); } - bool is_character_legal(char c, int index) override - { + bool is_character_legal(char c, int index) override { return isascii(c) && (isalpha(c) || (isdigit(c) && index > 0) || strchr("@$%^&_+=.", c)); } }; @@ -54,11 +56,10 @@ struct SmtrScope : public Functional::Scope { struct SmtrSort { Functional::Sort sort; SmtrSort(Functional::Sort sort) : sort(sort) {} - SExpr to_sexpr() const - { - if (sort.is_memory()) { + SExpr to_sexpr() const { + if(sort.is_memory()) { return list("list", list("bitvector", sort.addr_width()), list("bitvector", sort.data_width())); - } else if (sort.is_signal()) { + } else if(sort.is_signal()) { return list("bitvector", sort.width()); } else { log_error("unknown sort"); @@ -66,8 +67,7 @@ struct SmtrSort { } }; -class SmtrStruct -{ +class SmtrStruct { struct Field { SmtrSort sort; std::string accessor; @@ -77,22 +77,19 @@ class SmtrStruct vector fields; SmtrScope &global_scope; SmtrScope local_scope; - - public: +public: std::string name; SmtrStruct(std::string name, SmtrScope &scope) : global_scope(scope), local_scope(), name(name) {} - void insert(IdString field_name, SmtrSort sort) - { + void insert(IdString field_name, SmtrSort sort) { field_names(field_name); auto base_name = local_scope.unique_name(field_name); auto accessor = name + "-" + base_name; global_scope.reserve(accessor); fields.emplace_back(Field{sort, accessor, base_name}); } - void write_definition(SExprWriter &w) - { + void write_definition(SExprWriter &w) { vector field_list; - for (const auto &field : fields) { + for(const auto &field : fields) { field_list.emplace_back(field.name); } w.push(); @@ -105,26 +102,23 @@ class SmtrStruct } w.pop(); } - template void write_value(SExprWriter &w, Fn fn) - { + template void write_value(SExprWriter &w, Fn fn) { w.open(list(name)); - for (auto field_name : field_names) { + for(auto field_name : field_names) { w << fn(field_name); w.comment(RTLIL::unescape_id(field_name), true); } w.close(); } - SExpr access(SExpr record, IdString name) - { + SExpr access(SExpr record, IdString name) { size_t i = field_names.at(name); return list(fields[i].accessor, std::move(record)); } }; -std::string smt_const(RTLIL::Const const &c) -{ +std::string smt_const(RTLIL::Const const &c) { std::string s = "#b"; - for (int i = c.size(); i-- > 0;) + for(int i = c.size(); i-- > 0; ) s += c[i] == State::S1 ? '1' : '0'; return s; } @@ -137,9 +131,15 @@ struct SmtrPrintVisitor : public Functional::AbstractVisitor { SmtrPrintVisitor(SmtrStruct &input_struct, SmtrStruct &state_struct) : input_struct(input_struct), state_struct(state_struct) {} - SExpr from_bool(SExpr &&arg) { return list("bool->bitvector", std::move(arg)); } - SExpr to_bool(SExpr &&arg) { return list("bitvector->bool", std::move(arg)); } - SExpr to_list(SExpr &&arg) { return list("bitvector->bits", std::move(arg)); } + SExpr from_bool(SExpr &&arg) { + return list("bool->bitvector", std::move(arg)); + } + SExpr to_bool(SExpr &&arg) { + return list("bitvector->bool", std::move(arg)); + } + SExpr to_list(SExpr &&arg) { + return list("bitvector->bits", std::move(arg)); + } SExpr buf(Node, Node a) override { return n(a); } SExpr slice(Node, Node a, int offset, int out_width) override { return list("extract", offset + out_width - 1, offset, n(a)); } @@ -166,9 +166,8 @@ struct SmtrPrintVisitor : public Functional::AbstractVisitor { SExpr unsigned_greater_than(Node, Node a, Node b) override { return from_bool(list("bvugt", n(a), n(b))); } SExpr unsigned_greater_equal(Node, Node a, Node b) override { return from_bool(list("bvuge", n(a), n(b))); } - SExpr extend(SExpr &&a, int in_width, int out_width) - { - if (in_width < out_width) + SExpr extend(SExpr &&a, int in_width, int out_width) { + if(in_width < out_width) return list("zero-extend", std::move(a), list("bitvector", out_width)); else return std::move(a); @@ -177,20 +176,12 @@ struct SmtrPrintVisitor : public Functional::AbstractVisitor { SExpr logical_shift_right(Node, Node a, Node b) override { return list("bvlshr", n(a), extend(n(b), b.width(), a.width())); } SExpr arithmetic_shift_right(Node, Node a, Node b) override { return list("bvashr", n(a), extend(n(b), b.width(), a.width())); } SExpr mux(Node, Node a, Node b, Node s) override { return list("if", to_bool(n(s)), n(b), n(a)); } - SExpr constant(Node, RTLIL::Const const &value) override { return list("bv", smt_const(value), value.size()); } + SExpr constant(Node, RTLIL::Const const& value) override { return list("bv", smt_const(value), value.size()); } SExpr memory_read(Node, Node mem, Node addr) override { return list("list-ref-bv", n(mem), n(addr)); } SExpr memory_write(Node, Node mem, Node addr, Node data) override { return list("list-set-bv", n(mem), n(addr), n(data)); } - SExpr input(Node, IdString name, IdString kind) override - { - log_assert(kind == ID($input)); - return input_struct.access("inputs", name); - } - SExpr state(Node, IdString name, IdString kind) override - { - log_assert(kind == ID($state)); - return state_struct.access("state", name); - } + SExpr input(Node, IdString name, IdString kind) override { log_assert(kind == ID($input)); return input_struct.access("inputs", name); } + SExpr state(Node, IdString name, IdString kind) override { log_assert(kind == ID($state)); return state_struct.access("state", name); } }; struct SmtrModule { @@ -229,17 +220,19 @@ struct SmtrModule { { w.push(); w.open(list("define", list(name, "inputs", "state"))); - auto inlined = [&](Functional::Node n) { return n.fn() == Functional::Fn::constant; }; + auto inlined = [&](Functional::Node n) { + return n.fn() == Functional::Fn::constant; + }; SmtrPrintVisitor visitor(input_struct, state_struct); auto node_to_sexpr = [&](Functional::Node n) -> SExpr { - if (inlined(n)) + if(inlined(n)) return n.visit(visitor); else return scope(n.id(), n.name()); }; visitor.n = node_to_sexpr; - for (auto n : ir) - if (!inlined(n)) { + for(auto n : ir) + if(!inlined(n)) { w.open(list("let", list(list(node_to_sexpr(n), n.visit(visitor)))), false); w.comment(SmtrSort(n.sort()).to_sexpr().to_string(), true); } @@ -261,7 +254,7 @@ struct SmtrModule { else if (state->sort.is_memory()) { const auto &contents = state->initial_value_memory(); w.open(list("list")); - for (int i = 0; i < 1 << state->sort.addr_width(); i++) { + for(int i = 0; i < 1<sort.addr_width(); i++) { w << list("bv", smt_const(contents[i]), state->sort.data_width()); } w.close(); @@ -353,7 +346,8 @@ struct FunctionalSmtrBackend : public Backend { log_header(design, "Executing Functional Rosette Backend.\n"); size_t argidx; - for (argidx = 1; argidx < args.size(); argidx++) { + for (argidx = 1; argidx < args.size(); argidx++) + { if (args[argidx] == "-provides") provides = true; else if (args[argidx] == "-assoc-list-helpers") From ded7c9cb0302134af1b0283d94e2a92ce71fd700 Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sat, 29 Nov 2025 14:59:04 -0800 Subject: [PATCH 139/153] More formatting undos' --- backends/functional/smtlib_rosette.cc | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc index ee5fb8f54..1924418b6 100644 --- a/backends/functional/smtlib_rosette.cc +++ b/backends/functional/smtlib_rosette.cc @@ -301,7 +301,7 @@ struct SmtrModule { } void write(std::ostream &out) - { + { SExprWriter w(out); input_struct.write_definition(w); @@ -322,9 +322,8 @@ struct SmtrModule { struct FunctionalSmtrBackend : public Backend { FunctionalSmtrBackend() : Backend("functional_rosette", "Generate Rosette compatible Racket from Functional IR") {} - void help() override - { - // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + void help() override { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" write_functional_rosette [options] [filename]\n"); log("\n"); From 403740428c822111be568cb8e2ef3f369b66491f Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sat, 29 Nov 2025 15:01:17 -0800 Subject: [PATCH 140/153] Remove unknown change --- tests/functional/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py index fb00d4f22..a9fbb3c59 100644 --- a/tests/functional/conftest.py +++ b/tests/functional/conftest.py @@ -31,4 +31,4 @@ def pytest_generate_tests(metafunc): seed1 = metafunc.config.getoption("seed") rnd = lambda seed2: random.Random('{}-{}'.format(seed1, seed2)) names, cases = generate_test_cases(per_cell, rnd) - metafunc.parametrize("name,cell,parameters", cases, ids=names) + metafunc.parametrize("cell,parameters", cases, ids=names) From 473edd19edb0d95735ecf01bd0a21d6737be0d40 Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sat, 29 Nov 2025 15:06:46 -0800 Subject: [PATCH 141/153] Undo formatting --- tests/functional/rkt_vcd.py | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/tests/functional/rkt_vcd.py b/tests/functional/rkt_vcd.py index f06c2dc27..c114c0e19 100644 --- a/tests/functional/rkt_vcd.py +++ b/tests/functional/rkt_vcd.py @@ -62,18 +62,15 @@ def simulate_rosette( outputs: SignalWidthMap = {} current_struct_name: str = "" - with open(rkt_file_path, "r") as rkt_file: + with open(rkt_file_path, 'r') as rkt_file: for line in rkt_file: - m = re.search(r"gold_(Inputs|Outputs|State)", line) + m = re.search(r'gold_(Inputs|Outputs|State)', line) if m: current_struct_name = m.group(1) - if current_struct_name == "State": - break - elif not current_struct_name: - continue # skip lines before structs - m = re.search(r"; (.+?)\b \(bitvector (\d+)\)", line) - if not m: - continue # skip non matching lines (probably closing the struct) + if current_struct_name == "State": break + elif not current_struct_name: continue # skip lines before structs + m = re.search(r'; (.+?)\b \(bitvector (\d+)\)', line) + if not m: continue # skip non matching lines (probably closing the struct) signal = m.group(1) width = int(m.group(2)) if current_struct_name == "Inputs": @@ -85,18 +82,16 @@ def simulate_rosette( step_list: list[int] = [] for step in range(num_steps): value = rnd.getrandbits(width) - binary_string = format(value, "0{}b".format(width)) + binary_string = format(value, '0{}b'.format(width)) step_list.append(binary_string) signals[signal] = step_list - test_rkt_file_path = rkt_file_path.with_suffix(".tst.rkt") - with open(test_rkt_file_path, "w") as test_rkt_file: - test_rkt_file.writelines( - [ - "#lang rosette\n", - f'(require "{rkt_file_path.name}")\n', - ] - ) + test_rkt_file_path = rkt_file_path.with_suffix('.tst.rkt') + with open(test_rkt_file_path, 'w') as test_rkt_file: + test_rkt_file.writelines([ + '#lang rosette\n', + f'(require "{rkt_file_path.name}")\n', + ]) for step in range(num_steps): this_step = f"step_{step}" From 5d5a7ab443958155079e50ca217f710f7b2fc5fa Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sat, 29 Nov 2025 15:08:57 -0800 Subject: [PATCH 142/153] remove unused --- tests/functional/simulate_rosette.py | 1 - 1 file changed, 1 deletion(-) delete mode 100644 tests/functional/simulate_rosette.py diff --git a/tests/functional/simulate_rosette.py b/tests/functional/simulate_rosette.py deleted file mode 100644 index 9400a575a..000000000 --- a/tests/functional/simulate_rosette.py +++ /dev/null @@ -1 +0,0 @@ -"""Python utilities for simulating Rosette code.""" \ No newline at end of file From ddcd93024fc63824294e36aa91f5c6f2edc191b6 Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sat, 29 Nov 2025 15:20:37 -0800 Subject: [PATCH 143/153] Capture error case more correctly --- backends/functional/smtlib_rosette.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc index 1924418b6..9a64c203a 100644 --- a/backends/functional/smtlib_rosette.cc +++ b/backends/functional/smtlib_rosette.cc @@ -308,8 +308,8 @@ struct SmtrModule { output_struct.write_definition(w); state_struct.write_definition(w); - if (input_helper_name) { - if (!output_helper_name) + if (input_helper_name || output_helper_name) { + if (!output_helper_name || !input_helper_name) log_error("if keyword helpers are enabled, both input and output helper names are expected"); write_assoc_list_helpers(w); } From ade6379345d0bdfb912e2db067d2af9197205bf5 Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sat, 29 Nov 2025 15:24:56 -0800 Subject: [PATCH 144/153] Explicitly store whether to use association lists Instead of checking for the presence of helper names each time we need to determine whether to use association lists, explicitly store a boolean flag indicating whether association list helpers are being used. --- backends/functional/smtlib_rosette.cc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc index 9a64c203a..4396e6714 100644 --- a/backends/functional/smtlib_rosette.cc +++ b/backends/functional/smtlib_rosette.cc @@ -188,6 +188,7 @@ struct SmtrModule { Functional::IR ir; SmtrScope scope; std::string name; + bool use_assoc_list_helpers; std::optional input_helper_name; std::optional output_helper_name; @@ -196,7 +197,7 @@ struct SmtrModule { SmtrStruct state_struct; SmtrModule(Module *module, bool assoc_list_helpers) - : ir(Functional::IR::from_module(module)), scope(), name(scope.unique_name(module->name)), + : ir(Functional::IR::from_module(module)), scope(), name(scope.unique_name(module->name)), use_assoc_list_helpers(assoc_list_helpers), input_struct(scope.unique_name(module->name.str() + "_Inputs"), scope), output_struct(scope.unique_name(module->name.str() + "_Outputs"), scope), state_struct(scope.unique_name(module->name.str() + "_State"), scope) @@ -265,6 +266,9 @@ struct SmtrModule { void write_assoc_list_helpers(SExprWriter &w) { + if (!output_helper_name || !input_helper_name) + log_error("if using keyword helpers, both input and output helper names are expected"); + // Input struct keyword-based constructor. w.push(); w.open(list("define")); @@ -308,9 +312,7 @@ struct SmtrModule { output_struct.write_definition(w); state_struct.write_definition(w); - if (input_helper_name || output_helper_name) { - if (!output_helper_name || !input_helper_name) - log_error("if keyword helpers are enabled, both input and output helper names are expected"); + if (use_assoc_list_helpers) { write_assoc_list_helpers(w); } From e2230875787de8dac7f95546cd5fbff1a83712fe Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sat, 29 Nov 2025 15:28:34 -0800 Subject: [PATCH 145/153] Undo more changes that slipped in from somewhere? a merge maybe? --- .../extending_yosys/test_suites.rst | 20 ------------------- tests/functional/run-test.sh | 2 +- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/docs/source/yosys_internals/extending_yosys/test_suites.rst b/docs/source/yosys_internals/extending_yosys/test_suites.rst index eeee863aa..81a79e77f 100644 --- a/docs/source/yosys_internals/extending_yosys/test_suites.rst +++ b/docs/source/yosys_internals/extending_yosys/test_suites.rst @@ -79,26 +79,6 @@ compiler versions. For up to date information, including OS versions, refer to .. _Yosys Git repo: https://github.com/YosysHQ/yosys .. _the git actions page: https://github.com/YosysHQ/yosys/actions -Functional backend testing --------------------------- - -Testing of the functional backend is controlled by the -``ENABLE_FUNCTIONAL_TESTS`` make variable. Setting it to a value of ``1``, -either when calling ``make test`` or in your ``Makefile.conf`` file, will enable -these additional tests. - -.. note:: - - The functional backend tests requires additional prerequisites to be - installed: - - - racket and z3, available via ``apt-get`` or similar. - - pytest and pytest-xdist, available via ``pip``; pytest-xdist-gnumake is - also recommended. - - rosette, available via ``raco`` (after installing racket). - -.. todo:: are unit tests currently working - .. How to add a unit test ---------------------- diff --git a/tests/functional/run-test.sh b/tests/functional/run-test.sh index 03e3b60f8..9f70462ee 100755 --- a/tests/functional/run-test.sh +++ b/tests/functional/run-test.sh @@ -1,2 +1,2 @@ #!/usr/bin/env bash -pytest -v -n auto "$@" --steps 100 +pytest -v -m "not smt and not rkt" "$@" From 5f84b8b3395c64e6a1927025a98ee12d7dc2e2ca Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sat, 29 Nov 2025 15:32:19 -0800 Subject: [PATCH 146/153] Undo some other changes --- tests/functional/test_functional.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 24975fb51..6dc7909e5 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -40,12 +40,12 @@ def yosys_sim(rtlil_file, vcd_reference_file, vcd_out_file, preprocessing = ""): capture_output=True, check=False) raise -def test_cxx(name, cell, parameters, tmp_path, num_steps, rnd): +def test_cxx(cell, parameters, tmp_path, num_steps, rnd): rtlil_file = tmp_path / 'rtlil.il' vcdharness_cc_file = base_path / 'tests/functional/vcd_harness.cc' cc_file = tmp_path / 'my_module_functional_cxx.cc' vcdharness_exe_file = tmp_path / 'a.out' - vcd_functional_file = tmp_path / f'{name}.vcd' + vcd_functional_file = tmp_path / 'functional.vcd' vcd_yosys_sim_file = tmp_path / 'yosys.vcd' cell.write_rtlil_file(rtlil_file, parameters) @@ -56,12 +56,12 @@ def test_cxx(name, cell, parameters, tmp_path, num_steps, rnd): yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', '')) @pytest.mark.smt -def test_smt(name, cell, parameters, tmp_path, num_steps, rnd): +def test_smt(cell, parameters, tmp_path, num_steps, rnd): import smt_vcd rtlil_file = tmp_path / 'rtlil.il' smt_file = tmp_path / 'smtlib.smt' - vcd_functional_file = tmp_path / f'{name}.vcd' + vcd_functional_file = tmp_path / 'functional.vcd' vcd_yosys_sim_file = tmp_path / 'yosys.vcd' if hasattr(cell, 'smt_max_steps'): @@ -75,12 +75,12 @@ def test_smt(name, cell, parameters, tmp_path, num_steps, rnd): @pytest.mark.rkt @pytest.mark.parametrize("use_assoc_list_helpers", [True, False]) -def test_rkt(name, cell, parameters, tmp_path, num_steps, rnd, use_assoc_list_helpers): +def test_rkt(cell, parameters, tmp_path, num_steps, rnd): import rkt_vcd rtlil_file = tmp_path / 'rtlil.il' rkt_file = tmp_path / 'smtlib.rkt' - vcd_functional_file = tmp_path / f'{name}.vcd' + vcd_functional_file = tmp_path / 'functional.vcd' vcd_yosys_sim_file = tmp_path / 'yosys.vcd' cell.write_rtlil_file(rtlil_file, parameters) From 0f8e1e3bf7223c16f1a1ccc38d1bb4a3dad9ce4f Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sat, 29 Nov 2025 16:06:18 -0800 Subject: [PATCH 147/153] Undo more changes --- tests/functional/rtlil_cells.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/functional/rtlil_cells.py b/tests/functional/rtlil_cells.py index ab7cd4e0c..964d81ddf 100644 --- a/tests/functional/rtlil_cells.py +++ b/tests/functional/rtlil_cells.py @@ -374,9 +374,8 @@ def generate_test_cases(per_cell, rnd): for (name, parameters) in cell.generate_tests(rnd): if not name in seen_names: seen_names.add(name) - full_name = f'{cell.name}-{name}' if name != '' else cell.name - tests.append((full_name, cell, parameters)) - names.append(full_name) + tests.append((cell, parameters)) + names.append(f'{cell.name}-{name}' if name != '' else cell.name) if per_cell is not None and len(seen_names) >= per_cell: break return (names, tests) \ No newline at end of file From fb8a1ad3bcbc7fff2bb51c61f3fa127a26fcbf06 Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sat, 29 Nov 2025 16:07:18 -0800 Subject: [PATCH 148/153] Add back param --- tests/functional/test_functional.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 6dc7909e5..aa7500f8b 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -75,7 +75,7 @@ def test_smt(cell, parameters, tmp_path, num_steps, rnd): @pytest.mark.rkt @pytest.mark.parametrize("use_assoc_list_helpers", [True, False]) -def test_rkt(cell, parameters, tmp_path, num_steps, rnd): +def test_rkt(cell, parameters, tmp_path, num_steps, rnd, use_assoc_list_helpers): import rkt_vcd rtlil_file = tmp_path / 'rtlil.il' From 62e666c2edc888a1fbf53320f728d7e4fdce4946 Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sat, 29 Nov 2025 16:08:42 -0800 Subject: [PATCH 149/153] Make run-test work from anywhere --- tests/functional/run-test.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/functional/run-test.sh b/tests/functional/run-test.sh index 9f70462ee..e0bedf8d4 100755 --- a/tests/functional/run-test.sh +++ b/tests/functional/run-test.sh @@ -1,2 +1,5 @@ #!/usr/bin/env bash -pytest -v -m "not smt and not rkt" "$@" + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +pytest -v -m "not smt and not rkt" "$SCRIPT_DIR" "$@" From 38ee4fc7303a3e81a1752f376ec03cd159e91d2c Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Sat, 29 Nov 2025 16:17:27 -0800 Subject: [PATCH 150/153] Undo more unnecessary changes --- tests/functional/rkt_vcd.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/tests/functional/rkt_vcd.py b/tests/functional/rkt_vcd.py index c114c0e19..548a4ba74 100644 --- a/tests/functional/rkt_vcd.py +++ b/tests/functional/rkt_vcd.py @@ -124,13 +124,8 @@ def simulate_rosette( cmd = ["racket", test_rkt_file_path] - try: - status = subprocess.run(cmd, capture_output=True, check=True) - except subprocess.CalledProcessError as e: - raise RuntimeError( - f"Racket simulation failed with command: {cmd}\n" - f"Error: {e.stderr.decode()}" - ) from e + status = subprocess.run(cmd, capture_output=True) + assert status.returncode == 0, f"{cmd[0]} failed" for signal in outputs.keys(): signals[signal] = [] @@ -154,12 +149,10 @@ def simulate_rosette( ) for output, (value, width) in outputs_values_and_widths: assert isinstance(value, str), f"Bad value {value!r}" - assert value.startswith(("#b", "#x")), f"Non-binary value {value!r}" - assert ( - int(width) == outputs[output] - ), f"Width mismatch for output {output!r} (got {width}, expected {outputs[output]})" - int_value = int(value[2:], 16 if value.startswith("#x") else 2) - binary_string = format(int_value, "0{}b".format(width)) + assert value.startswith(('#b', '#x')), f"Non-binary value {value!r}" + assert int(width) == outputs[output], f"Width mismatch for output {output!r} (got {width}, expected {outputs[output]})" + int_value = int(value[2:], 16 if value.startswith('#x') else 2) + binary_string = format(int_value, '0{}b'.format(width)) signals[output].append(binary_string) vcd_signals: SignalStepMap = {} From dd65dd610d9e1fa9522721c4c90057f0b538f924 Mon Sep 17 00:00:00 2001 From: Gus Smith Date: Tue, 2 Dec 2025 11:17:21 -0800 Subject: [PATCH 151/153] Fixes --- backends/functional/smtlib_rosette.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc index 4396e6714..73e1b48c6 100644 --- a/backends/functional/smtlib_rosette.cc +++ b/backends/functional/smtlib_rosette.cc @@ -266,8 +266,7 @@ struct SmtrModule { void write_assoc_list_helpers(SExprWriter &w) { - if (!output_helper_name || !input_helper_name) - log_error("if using keyword helpers, both input and output helper names are expected"); + log_assert(output_helper_name && input_helper_name); // Input struct keyword-based constructor. w.push(); @@ -292,7 +291,7 @@ struct SmtrModule { w.pop(); } w.pop(); - // Output struct keyword-based destructor. + // Output struct keyword-based destructuring w.push(); w.open(list("define")); const auto outputs_name = "outputs"; From 5bafeb77dc71e054fa331ab9efa613e6fb0a1c49 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Wed, 3 Dec 2025 07:19:42 +0100 Subject: [PATCH 152/153] Release version 0.60 --- CHANGELOG | 10 +++++++++- Makefile | 4 ++-- docs/source/conf.py | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6cefcc3ac..29c8b97ba 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,8 +2,16 @@ List of major changes and improvements between releases ======================================================= -Yosys 0.59 .. Yosys 0.60-dev +Yosys 0.59 .. Yosys 0.60 -------------------------- + * Various + - read_verilog: suport unsized parameters. + - Added static library compile option. + + * New commands and options + - Added "sdc" pass for reading SDC files. + - Added experimental "sdc_expand" and "opensta" for OpenSTA integration. + - Added "icell_liberty" pass for used internal cells. Yosys 0.58 .. Yosys 0.59 -------------------------- diff --git a/Makefile b/Makefile index ccb1be1b9..1c1e19f5f 100644 --- a/Makefile +++ b/Makefile @@ -162,7 +162,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.59+134 +YOSYS_VER := 0.60 YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2 | cut -d'+' -f1) YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'+' -f2) @@ -185,7 +185,7 @@ endif OBJS = kernel/version_$(GIT_REV).o bumpversion: - sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 03eb220.. | wc -l`/;" Makefile +# sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 03eb220.. | wc -l`/;" Makefile ABCMKARGS = CC="$(CXX)" CXX="$(CXX)" ABC_USE_LIBSTDCXX=1 ABC_USE_NAMESPACE=abc VERBOSE=$(Q) diff --git a/docs/source/conf.py b/docs/source/conf.py index fb106bddb..01bb620ea 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -6,7 +6,7 @@ import os project = 'YosysHQ Yosys' author = 'YosysHQ GmbH' copyright ='2025 YosysHQ GmbH' -yosys_ver = "0.59" +yosys_ver = "0.60" # select HTML theme html_theme = 'furo-ys' From 58c7dc7cc2d8f16946a96df43e0705308532fb21 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Wed, 3 Dec 2025 07:23:34 +0100 Subject: [PATCH 153/153] Next dev cycle --- CHANGELOG | 3 +++ Makefile | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 29c8b97ba..69f8ab1ce 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,9 @@ List of major changes and improvements between releases ======================================================= +Yosys 0.60 .. Yosys 0.61-dev +-------------------------- + Yosys 0.59 .. Yosys 0.60 -------------------------- * Various diff --git a/Makefile b/Makefile index 1c1e19f5f..15aaf870c 100644 --- a/Makefile +++ b/Makefile @@ -162,7 +162,7 @@ ifeq ($(OS), Haiku) CXXFLAGS += -D_DEFAULT_SOURCE endif -YOSYS_VER := 0.60 +YOSYS_VER := 0.60+0 YOSYS_MAJOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f1) YOSYS_MINOR := $(shell echo $(YOSYS_VER) | cut -d'.' -f2 | cut -d'+' -f1) YOSYS_COMMIT := $(shell echo $(YOSYS_VER) | cut -d'+' -f2) @@ -185,7 +185,7 @@ endif OBJS = kernel/version_$(GIT_REV).o bumpversion: -# sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 03eb220.. | wc -l`/;" Makefile + sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 5bafeb7.. | wc -l`/;" Makefile ABCMKARGS = CC="$(CXX)" CXX="$(CXX)" ABC_USE_LIBSTDCXX=1 ABC_USE_NAMESPACE=abc VERBOSE=$(Q)