Skip to content

[STA] Generating SDC Commands Post-Implementation #3016

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions doc/src/vpr/command_line_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1830,6 +1830,16 @@ Analysis Options

**Default:** ``off``

.. option:: --gen_post_implementation_sdc { on | off }

Generates an SDC file including a list of constraints that would
replicate the timing constraints that the timing analysis within
VPR followed during the flow. This can be helpful for flows that
use external timing analysis tools that have additional capabilities
or more detailed delay models than what VPR uses.

**Default:** ``off``

.. option:: --post_synth_netlist_unconn_inputs { unconnected | nets | gnd | vcc }

Controls how unconnected input cell ports are handled in the post-synthesis netlist
Expand Down
1 change: 1 addition & 0 deletions vpr/src/base/SetupVPR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,7 @@ static void SetupAnalysisOpts(const t_options& Options, t_analysis_opts& analysi

analysis_opts.gen_post_synthesis_netlist = Options.Generate_Post_Synthesis_Netlist;
analysis_opts.gen_post_implementation_merged_netlist = Options.Generate_Post_Implementation_Merged_Netlist;
analysis_opts.gen_post_implementation_sdc = Options.generate_post_implementation_sdc.value();

analysis_opts.timing_report_npaths = Options.timing_report_npaths;
analysis_opts.timing_report_detail = Options.timing_report_detail;
Expand Down
2 changes: 2 additions & 0 deletions vpr/src/base/ShowSetup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,8 @@ static void ShowNetlistOpts(const t_netlist_opts& NetlistOpts) {

static void ShowAnalysisOpts(const t_analysis_opts& AnalysisOpts) {
VTR_LOG("AnalysisOpts.gen_post_synthesis_netlist: %s\n", (AnalysisOpts.gen_post_synthesis_netlist) ? "true" : "false");
VTR_LOG("AnalysisOpts.gen_post_implementation_merged_netlist: %s\n", AnalysisOpts.gen_post_implementation_merged_netlist ? "true" : "false");
VTR_LOG("AnalysisOpts.gen_post_implementation_sdc: %s\n", AnalysisOpts.gen_post_implementation_sdc ? "true" : "false");
VTR_LOG("AnalysisOpts.timing_report_npaths: %d\n", AnalysisOpts.timing_report_npaths);
VTR_LOG("AnalysisOpts.timing_report_skew: %s\n", AnalysisOpts.timing_report_skew ? "true" : "false");
VTR_LOG("AnalysisOpts.echo_dot_timing_graph_node: %s\n", AnalysisOpts.echo_dot_timing_graph_node.c_str());
Expand Down
160 changes: 158 additions & 2 deletions vpr/src/base/netlist_writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
#include <vector>
#include "atom_netlist.h"
#include "atom_netlist_utils.h"
#include "clock_modeling.h"
#include "globals.h"
#include "logic_vec.h"
#include "netlist_walker.h"
Expand Down Expand Up @@ -2644,22 +2645,167 @@ std::string join_identifier(std::string lhs, std::string rhs) {
return lhs + '_' + rhs;
}

/**
* @brief Add the original SDC constraints that VPR used during its flow to the
* given SDC file.
*
* @param sdc_os
* Output stream for the target SDC file. The original SDC file passed into
* VPR will be appended to this file.
* @param timing_info
* Information on the timing within VPR. This is used to get the file path
* to the original SDC file.
*/
void add_original_sdc_to_post_implemented_sdc_file(std::ofstream& sdc_os,
const t_timing_inf& timing_info) {
// Open the original SDC file provided to VPR.
std::ifstream original_sdc_file;
original_sdc_file.open(timing_info.SDCFile);
if (!original_sdc_file.is_open()) {
// TODO: VPR automatically creates SDC constraints by default if no SDC
// file is provided. These can be replicated here if needed.
VPR_FATAL_ERROR(VPR_ERROR_IMPL_NETLIST_WRITER,
"No SDC files provided to VPR; currently cannot generate "
"post-implementation SDC file without it");
}

// Write a header to declare where these commands came from.
sdc_os << "\n";
sdc_os << "#******************************************************************************#\n";
sdc_os << "# The following SDC commands were provided to VPR from the given SDC file:\n";
sdc_os << "# \t" << timing_info.SDCFile << "\n";
sdc_os << "#******************************************************************************#\n";

// Append the original SDC file to the post-implementation SDC file.
sdc_os << original_sdc_file.rdbuf();
}

/**
* @brief Add propagated clock commands to the given SDC file based on the set
* clock modeling.
*
* This is necessary since VPR decides if clocks are routed or not, which has
* affects on how timing analysis is performed on the clocks.
*
* @param sdc_os
* The file stream to add the propagated clock commands to.
* @param clock_modeling
* The type of clock modeling used by VPR during the CAD flow.
*/
void add_propagated_clocks_to_sdc_file(std::ofstream& sdc_os,
e_clock_modeling clock_modeling) {

// Ideal and routed clocks are handled by the code below. Other clock models
// like dedicated routing are not supported yet.
// TODO: Supporting dedicated routing should be simple; however it should
// be investigated. Tried quickly but found that the delays produced
// were off by 0.003 ns. Need to investigate why.
if (clock_modeling != e_clock_modeling::ROUTED_CLOCK && clock_modeling != e_clock_modeling::IDEAL_CLOCK) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should work. We should already extract the delays and your code path presumably already writes those out as propagated clock delays.
Mustafa Abbas has some tests for routing dedicated clocks; you could find one of those and use it to test this functionality. If it works, remove the if.

Copy link
Contributor Author

@AlexandreSinger AlexandreSinger May 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, I tried this and it mostly worked by the CPD was off by 0.003 ns for the test circuit I used (the CLMA example with an H-tree clock network).

I am not sure where this discrepancy is coming from, the timing graph in open STA seems sounds; I think it may be an issue with the API calls used to parse the timing data from Tatum into the SDF file. Left a TODO to look into this so that this does not block this PR from getting merged.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. I'd enable it then. For the small difference: I agree it's not a blocking issue. I'd look closely at the clock path delays. Perhaps we have a min/max spread and openSTA is doing some clock pessimism removal or some such? If you get listings of the critical paths (including clock delays) in both flows I can go over it with you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. I will raise an issue today to track this. The issue seemed to be caused by the edge delays of clocks reported by Tatum not matching what was written in the SDF file. All paths reported by OpenSTA were off by 0.003 ns.

VPR_FATAL_ERROR(VPR_ERROR_IMPL_NETLIST_WRITER,
"Only ideal and routed clock modeling are currentlt "
"supported for post-implementation SDC file generation");
}

// The timing constraints contain information on all the clocks in the circuit
// (provided by the user-provided SDC file).
const auto timing_constraints = g_vpr_ctx.timing().constraints;

// Collect the non-virtual clocks. Virtual clocks are not routed and
// do not get propageted.
std::vector<tatum::DomainId> non_virtual_clocks;
for (tatum::DomainId clock_domain_id : timing_constraints->clock_domains()) {
if (!timing_constraints->is_virtual_clock(clock_domain_id)) {
non_virtual_clocks.push_back(clock_domain_id);
}
}

// If there are no non-virtual clocks, no extra commands needed. Virtual
// clocks are ideal.
if (non_virtual_clocks.empty()) {
return;
}

// Append a header to explain why these commands are added.
sdc_os << "\n";
sdc_os << "#******************************************************************************#\n";
sdc_os << "# The following are clock domains in VPR which have delays on their edges.\n";
sdc_os << "#\n";
sdc_os << "# Any non-virtual clock has its delay determined and written out as part of a";
sdc_os << "# propagated clock command. If VPR was instructed not to route the clock, this";
sdc_os << "# delay will be an underestimate.\n";
sdc_os << "#\n";
sdc_os << "# Note: Virtual clocks do not get routed and are treated as ideal.\n";
sdc_os << "#******************************************************************************#\n";

// Add the SDC commands to set the non-virtual clocks as propagated (non-ideal);
// Note: It was decided that "ideal" (dont route) clock modeling in VPR should still
// set the clocks as propagated to allow for the input pad delays of
// clocks to be included. The SDF delay annotations on clock signals
// should make this safe to do.
for (tatum::DomainId clock_domain_id : non_virtual_clocks) {
sdc_os << "set_propagated_clock ";
sdc_os << timing_constraints->clock_domain_name(clock_domain_id);
sdc_os << "\n";
}
}

/**
* @brief Generates a post-implementation SDC file with the given file name
* based on the timing info and clock modeling set for VPR.
*
* @param sdc_filename
* The file name of the SDC file to generate.
* @param timing_info
* Information on the timing used in the VPR flow.
* @param clock_modeling
* The type of clock modeling used by VPR during its flow.
*/
void generate_post_implementation_sdc(const std::string& sdc_filename,
const t_timing_inf& timing_info,
e_clock_modeling clock_modeling) {
if (!timing_info.timing_analysis_enabled) {
VTR_LOG_WARN("Timing analysis is disabled. Post-implementation SDC file "
"will not be generated.\n");
return;
}

// Begin writing the post-implementation SDC file.
std::ofstream sdc_os(sdc_filename);

// Print a header declaring that this file is auto-generated and what version
// of VTR produced it.
sdc_os << "#******************************************************************************#\n";
sdc_os << "# SDC automatically generated by VPR from a post-place-and-route implementation.\n";
sdc_os << "#\tVersion: " << vtr::VERSION << "\n";
sdc_os << "#******************************************************************************#\n";

// Add the original SDC that VPR used during its flow.
add_original_sdc_to_post_implemented_sdc_file(sdc_os, timing_info);

// Add propagated clocks to SDC file if needed.
add_propagated_clocks_to_sdc_file(sdc_os, clock_modeling);
}

} // namespace

//
// Externally Accessible Functions
//

///@brief Main routine for this file. See netlist_writer.h for details.
void netlist_writer(const std::string basename, std::shared_ptr<const AnalysisDelayCalculator> delay_calc, const LogicalModels& models, t_analysis_opts opts) {
void netlist_writer(const std::string basename,
std::shared_ptr<const AnalysisDelayCalculator> delay_calc,
const LogicalModels& models,
const t_timing_inf& timing_info,
e_clock_modeling clock_modeling,
t_analysis_opts opts) {
std::string verilog_filename = basename + "_post_synthesis.v";
std::string blif_filename = basename + "_post_synthesis.blif";
std::string sdf_filename = basename + "_post_synthesis.sdf";

VTR_LOG("Writing Implementation Netlist: %s\n", verilog_filename.c_str());
VTR_LOG("Writing Implementation Netlist: %s\n", blif_filename.c_str());
VTR_LOG("Writing Implementation SDF : %s\n", sdf_filename.c_str());

std::ofstream verilog_os(verilog_filename);
std::ofstream blif_os(blif_filename);
std::ofstream sdf_os(sdf_filename);
Expand All @@ -2669,6 +2815,16 @@ void netlist_writer(const std::string basename, std::shared_ptr<const AnalysisDe
NetlistWalker nl_walker(visitor);

nl_walker.walk();

if (opts.gen_post_implementation_sdc) {
std::string sdc_filename = basename + "_post_synthesis.sdc";

VTR_LOG("Writing Implementation SDC : %s\n", sdc_filename.c_str());

generate_post_implementation_sdc(sdc_filename,
timing_info,
clock_modeling);
}
}

///@brief Main routine for this file. See netlist_writer.h for details.
Expand Down
20 changes: 19 additions & 1 deletion vpr/src/base/netlist_writer.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,26 @@ class LogicalModels;
*
* All written filenames end in {basename}_post_synthesis.{fmt} where {basename} is the
* basename argument and {fmt} is the file format (e.g. v, blif, sdf)
*
* @param basename
* The basename prefix used for the generated files.
* @param delay_calc
* The delay calculator used to get the timing of edges in the timing graph.
* @param models
* The logical models in the architecture.
* @param timing_info
* Information on the timing used in the VPR flow.
* @param clock_modeling
* The type of clock modeling used in the VPR flow.
* @param opts
* The analysis options.
*/
void netlist_writer(const std::string basename, std::shared_ptr<const AnalysisDelayCalculator> delay_calc, const LogicalModels& models, t_analysis_opts opts);
void netlist_writer(const std::string basename,
std::shared_ptr<const AnalysisDelayCalculator> delay_calc,
const LogicalModels& models,
const t_timing_inf& timing_info,
e_clock_modeling clock_modeling,
t_analysis_opts opts);

/**
* @brief Writes out the post implementation netlist in Verilog format.
Expand Down
10 changes: 10 additions & 0 deletions vpr/src/base/read_options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3012,6 +3012,16 @@ argparse::ArgumentParser create_arg_parser(const std::string& prog_name, t_optio
.default_value("off")
.show_in(argparse::ShowIn::HELP_ONLY);

analysis_grp.add_argument<bool, ParseOnOff>(args.generate_post_implementation_sdc, "--gen_post_implementation_sdc")
.help(
"Generates an SDC file including a list of constraints that would "
"replicate the timing constraints that the timing analysis within "
"VPR followed during the flow. This can be helpful for flows that "
"use external timing analysis tools that have additional capabilities "
"or more detailed delay models than what VPR uses")
.default_value("off")
.show_in(argparse::ShowIn::HELP_ONLY);

analysis_grp.add_argument(args.timing_report_npaths, "--timing_report_npaths")
.help("Controls how many timing paths are reported.")
.default_value("100")
Expand Down
1 change: 1 addition & 0 deletions vpr/src/base/read_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ struct t_options {
argparse::ArgValue<bool> full_stats;
argparse::ArgValue<bool> Generate_Post_Synthesis_Netlist;
argparse::ArgValue<bool> Generate_Post_Implementation_Merged_Netlist;
argparse::ArgValue<bool> generate_post_implementation_sdc;
argparse::ArgValue<int> timing_report_npaths;
argparse::ArgValue<e_timing_report_detail> timing_report_detail;
argparse::ArgValue<bool> timing_report_skew;
Expand Down
2 changes: 1 addition & 1 deletion vpr/src/base/vpr_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1467,7 +1467,7 @@ void vpr_analysis(const Netlist<>& net_list,
//Write the post-synthesis netlist
if (vpr_setup.AnalysisOpts.gen_post_synthesis_netlist) {
netlist_writer(atom_ctx.netlist().netlist_name(), analysis_delay_calc,
Arch.models, vpr_setup.AnalysisOpts);
Arch.models, vpr_setup.Timing, vpr_setup.clock_modeling, vpr_setup.AnalysisOpts);
}

//Write the post-implementation merged netlist
Expand Down
1 change: 1 addition & 0 deletions vpr/src/base/vpr_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -1342,6 +1342,7 @@ struct t_analysis_opts {

bool gen_post_synthesis_netlist;
bool gen_post_implementation_merged_netlist;
bool gen_post_implementation_sdc;
e_post_synth_netlist_unconn_handling post_synth_netlist_unconn_input_handling;
e_post_synth_netlist_unconn_handling post_synth_netlist_unconn_output_handling;
bool post_synth_netlist_module_parameters;
Expand Down