diff --git a/include/sta/Sta.hh b/include/sta/Sta.hh index 6591be01..8d1765f4 100644 --- a/include/sta/Sta.hh +++ b/include/sta/Sta.hh @@ -966,6 +966,7 @@ public: bool unconstrained_endpoints, bool loops, bool generated_clks); + void reportCheckTimingJson(const char *filename); // Path from/thrus/to filter. // from/thrus/to are owned and deleted by Search. // PathEnds in the returned PathEndSeq are owned by Search PathGroups diff --git a/search/CheckTiming.cc b/search/CheckTiming.cc index f64975e5..87a57933 100644 --- a/search/CheckTiming.cc +++ b/search/CheckTiming.cc @@ -24,7 +24,11 @@ #include "CheckTiming.hh" +#include +#include + #include "ClkNetwork.hh" +#include "Error.hh" #include "ExceptionPath.hh" #include "Format.hh" #include "Genclks.hh" @@ -69,6 +73,7 @@ CheckTiming::clear() { deleteErrors(); errors_.clear(); + json_results_.clear(); } CheckErrorSeq & @@ -120,7 +125,8 @@ CheckTiming::checkNoInputDelay() } } delete pin_iter; - pushPinErrors("Warning: There {} {} input port{} missing set_input_delay.",no_arrival); + pushPinErrors("Warning: There {} {} input port{} missing set_input_delay.", + "no_input_delay", no_arrival); } void @@ -129,7 +135,7 @@ CheckTiming::checkNoOutputDelay() PinSet no_departure(network_); checkNoOutputDelay(no_departure); pushPinErrors("Warning: There {} {} output port{} missing set_output_delay.", - no_departure); + "no_output_delay", no_departure); } void @@ -176,9 +182,9 @@ CheckTiming::checkRegClks(bool reg_multiple_clks, multiple_clk_pins.insert(pin); } pushPinErrors("Warning: There {} {} unclocked register/latch pin{}.", - no_clk_pins); + "unclocked", no_clk_pins); pushPinErrors("Warning: There {} {} register/latch pin{} with multiple clocks.", - multiple_clk_pins); + "multiple_clock", multiple_clk_pins); } static const char * @@ -241,7 +247,7 @@ CheckTiming::checkUnconstrainedEndpoints() checkUnconstrainedOutputs(unconstrained_ends); checkUnconstrainedSetups(unconstrained_ends); pushPinErrors("Warning: There {} {} unconstrained endpoint{}.", - unconstrained_ends); + "unconstrained", unconstrained_ends); } void @@ -347,12 +353,13 @@ CheckTiming::checkGeneratedClocks() } } pushClkErrors("Warning: There {} {} generated clock{} not connected to a clock source.", - gen_clk_errors); + "generated_clock", gen_clk_errors); } // Report the "msg" error for each pin in "pins". void CheckTiming::pushPinErrors(std::string_view msg, + const char *json_key, PinSet &pins) { if (!pins.empty()) { @@ -368,11 +375,13 @@ CheckTiming::pushPinErrors(std::string_view msg, error->push_back(sdc_network_->pathName(pin)); } errors_.push_back(error); + json_results_.emplace_back(json_key, StringSeq(error->begin() + 1, error->end())); } } void CheckTiming::pushClkErrors(const char *msg, + const char *json_key, ClockSet &clks) { if (!clks.empty()) { @@ -388,7 +397,55 @@ CheckTiming::pushClkErrors(const char *msg, error->push_back(clk->name()); } errors_.push_back(error); + json_results_.emplace_back(json_key, StringSeq(error->begin() + 1, error->end())); + } +} + +static std::string +jsonEscape(const std::string &s) +{ + std::string out; + for (char c : s) { + switch (c) { + case '"': out += "\\\""; break; + case '\\': out += "\\\\"; break; + default: out += c; + } + } + return out; +} + +void +CheckTiming::reportJson(const char *filename) const +{ + std::ofstream stream(filename); + if (!stream.is_open()) + throw FileNotWritable(filename); + + // Start JSON object. + stream << "{\n"; + size_t n = json_results_.size(); + for (size_t i = 0; i < n; i++) { + const std::string &key = json_results_[i].first; + const StringSeq &names = json_results_[i].second; + stream << " \"" << key << "\": ["; + for (size_t j = 0; j < names.size(); j++) { + stream << "\n \"" << jsonEscape(names[j]) << "\""; + // Comma between elements only; the last element must not be followed by one. + if (j + 1 < names.size()) + stream << ","; + } + // Close the array on its own indented line when it has contents. + if (!names.empty()) + stream << "\n "; + stream << "]"; + // Trailing commas are invalid JSON. + if (i + 1 < n) + stream << ","; + stream << "\n"; } + // Close JSON object. + stream << "}\n"; } } // namespace sta diff --git a/search/CheckTiming.hh b/search/CheckTiming.hh index ca7b9886..471831e0 100644 --- a/search/CheckTiming.hh +++ b/search/CheckTiming.hh @@ -24,7 +24,9 @@ #pragma once +#include #include +#include #include #include "GraphClass.hh" @@ -54,6 +56,7 @@ public: bool unconstrained_endpoints, bool loops, bool generated_clks); + void reportJson(const char *filename) const; protected: void clear(); @@ -73,11 +76,14 @@ protected: bool hasMaxDelay(Pin *pin); void checkGeneratedClocks(); void pushPinErrors(std::string_view msg, + const char *json_key, PinSet &pins); void pushClkErrors(const char *msg, + const char *json_key, ClockSet &clks); CheckErrorSeq errors_; + std::vector> json_results_; const Mode *mode_{nullptr}; const Sdc *sdc_{nullptr}; const Sim *sim_{nullptr}; diff --git a/search/Search.i b/search/Search.i index 892c8337..fd54fee8 100644 --- a/search/Search.i +++ b/search/Search.i @@ -906,6 +906,12 @@ check_timing_cmd(bool no_input_delay, loops, generated_clks); } +void +report_check_timing_json(const char *filename) +{ + Sta::sta()->reportCheckTimingJson(filename); +} + //////////////////////////////////////////////////////////////// PinSet diff --git a/search/Search.tcl b/search/Search.tcl index 98df684c..6024af3d 100644 --- a/search/Search.tcl +++ b/search/Search.tcl @@ -34,14 +34,14 @@ define_cmd_args "check_setup" \ { [-verbose] [-no_input_delay] [-no_output_delay]\ [-multiple_clock] [-no_clock]\ [-unconstrained_endpoints] [-loops] [-generated_clocks]\ - [> filename] [>> filename] } + [-json filename] [> filename] [>> filename] } proc_redirect check_setup { check_setup_cmd "check_setup" $args } proc check_setup_cmd { cmd cmd_args } { - parse_key_args $cmd cmd_args keys {} flags {-verbose} 0 + parse_key_args $cmd cmd_args keys {-json} flags {-verbose} 0 # When nothing is everything. if { $cmd_args == {} } { set unconstrained_endpoints 1 @@ -77,6 +77,15 @@ proc check_setup_cmd { cmd cmd_args } { } } } + if { [info exists keys(-json)] } { + if { $keys(-json) == "" } { + sta_error 517 "$cmd -json requires an output file location." + } + if { $loops } { + sta_warn 518 "$cmd -json does not support -loops. Loops are not emitted." + } + report_check_timing_json $keys(-json) + } # return value expr [llength $errors] == 0 } diff --git a/search/Sta.cc b/search/Sta.cc index 4748c684..5dc7d3e3 100644 --- a/search/Sta.cc +++ b/search/Sta.cc @@ -2294,6 +2294,12 @@ Sta::checkTiming(const Mode *mode, unconstrained_endpoints, loops, generated_clks); } +void +Sta::reportCheckTimingJson(const char *filename) +{ + check_timing_->reportJson(filename); +} + //////////////////////////////////////////////////////////////// bool diff --git a/test/check_setup_json.ok b/test/check_setup_json.ok new file mode 100644 index 00000000..2c0274ed --- /dev/null +++ b/test/check_setup_json.ok @@ -0,0 +1,22 @@ +Warning: There are 4 input ports missing set_input_delay. +Warning: There are 2 unclocked register/latch pins. +Warning: There are 4 unconstrained endpoints. +Warning 518: check_setup_json.tcl line 16, check_setup -json does not support -loops. Loops are not emitted. +{ + "no_input_delay": [ + "clk2", + "clk3", + "in1", + "in2" + ], + "unclocked": [ + "r2/CLK", + "r3/CLK" + ], + "unconstrained": [ + "out", + "r1/D", + "r2/D", + "r3/D" + ] +} diff --git a/test/check_setup_json.tcl b/test/check_setup_json.tcl new file mode 100644 index 00000000..5ccd6c4c --- /dev/null +++ b/test/check_setup_json.tcl @@ -0,0 +1,18 @@ +# check_setup -json +source helpers.tcl +read_liberty asap7_small.lib.gz +read_verilog reg1_asap7.v +link_design top + +# Only constrain clk1; leave clk2/clk3, inputs, and outputs unconstrained +create_clock -name clk1 -period 500 clk1 + +set json_file [make_result_file "check_setup_json.json"] + +# Dump JSON, should warn that loops aren't supported. +check_setup -unconstrained_endpoints -multiple_clock -no_clock \ + -no_input_delay -generated_clocks \ + -loop \ + -json $json_file + +report_file $json_file diff --git a/test/regression_vars.tcl b/test/regression_vars.tcl index 79371c16..13368145 100644 --- a/test/regression_vars.tcl +++ b/test/regression_vars.tcl @@ -142,6 +142,7 @@ record_example_tests { record_public_tests { collections + check_setup_json delay_calc_no_inv disable_clock_gating_check disconnect_mcp_pin