| /* |
| * Copyright (C) 2019-2022 The SymbiFlow Authors |
| * |
| * Use of this source code is governed by a ISC-style |
| * license that can be found in the LICENSE file or at |
| * https://opensource.org/licenses/ISC |
| * |
| * SPDX-License-Identifier: ISC |
| */ |
| #include <algorithm> |
| #include <array> |
| |
| #include "clocks.h" |
| #include "kernel/log.h" |
| #include "kernel/register.h" |
| #include "kernel/rtlil.h" |
| #include "propagation.h" |
| #include "sdc_writer.h" |
| #include "set_clock_groups.h" |
| #include "set_false_path.h" |
| #include "set_max_delay.h" |
| |
| USING_YOSYS_NAMESPACE |
| |
| PRIVATE_NAMESPACE_BEGIN |
| |
| struct ReadSdcCmd : public Frontend { |
| ReadSdcCmd() : Frontend("sdc", "Read SDC file") {} |
| |
| void help() override |
| { |
| log("\n"); |
| log(" read_sdc <filename>\n"); |
| log("\n"); |
| log("Read SDC file.\n"); |
| log("\n"); |
| } |
| |
| void execute(std::istream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *) override |
| { |
| if (args.size() < 2) { |
| log_cmd_error("Missing script file.\n"); |
| } |
| log("\nReading clock constraints file(SDC)\n\n"); |
| size_t argidx = 1; |
| extra_args(f, filename, args, argidx); |
| std::string content{std::istreambuf_iterator<char>(*f), std::istreambuf_iterator<char>()}; |
| log("%s\n", content.c_str()); |
| Tcl_Interp *interp = yosys_get_tcl_interp(); |
| if (Tcl_EvalFile(interp, args[argidx].c_str()) != TCL_OK) { |
| log_cmd_error("TCL interpreter returned an error: %s\n", Tcl_GetStringResult(interp)); |
| } |
| } |
| }; |
| |
| struct WriteSdcCmd : public Backend { |
| WriteSdcCmd(SdcWriter &sdc_writer) : Backend("sdc", "Write SDC file"), sdc_writer_(sdc_writer) {} |
| |
| void help() override |
| { |
| log("\n"); |
| log(" write_sdc [-include_propagated_clocks] <filename>\n"); |
| log("\n"); |
| log("Write SDC file.\n"); |
| log("\n"); |
| log(" -include_propagated_clocks\n"); |
| log(" Write out all propagated clocks"); |
| log("\n"); |
| } |
| |
| void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override |
| { |
| size_t argidx; |
| bool include_propagated = false; |
| if (args.size() < 2) { |
| log_cmd_error("Missing output file.\n"); |
| } |
| for (argidx = 1; argidx < args.size(); argidx++) { |
| std::string arg = args[argidx]; |
| if (arg == "-include_propagated_clocks" && argidx + 1 < args.size()) { |
| include_propagated = true; |
| continue; |
| } |
| break; |
| } |
| log("\nWriting out clock constraints file(SDC)\n"); |
| extra_args(f, filename, args, argidx); |
| sdc_writer_.WriteSdc(design, *f, include_propagated); |
| } |
| |
| SdcWriter &sdc_writer_; |
| }; |
| |
| struct CreateClockCmd : public Pass { |
| CreateClockCmd() : Pass("create_clock", "Create clock object") {} |
| |
| void help() override |
| { |
| log("\n"); |
| log(" create_clock [ -name clock_name ] -period period_value " |
| "[-waveform <edge_list>] <target>\n"); |
| log("Define a clock.\n"); |
| log("If name is not specified then the name of the first target is " |
| "selected as the clock's name.\n"); |
| log("Period is expressed in nanoseconds.\n"); |
| log("The waveform option specifies the duty cycle (the rising a " |
| "falling edges) of the clock.\n"); |
| log("It is specified as a list of two elements/time values: the first " |
| "rising edge and the next falling edge.\n"); |
| log("\n"); |
| } |
| |
| void execute(std::vector<std::string> args, RTLIL::Design *design) override |
| { |
| size_t argidx; |
| std::string name; |
| bool is_waveform_specified(false); |
| float rising_edge(0); |
| float falling_edge(0); |
| float period(0); |
| if (args.size() < 4) { |
| log_cmd_error("Incorrect number of arguments\n"); |
| } |
| for (argidx = 1; argidx < args.size(); argidx++) { |
| std::string arg = args[argidx]; |
| if (arg == "-add" && argidx + 1 < args.size()) { |
| continue; |
| } |
| if (arg == "-name" && argidx + 1 < args.size()) { |
| name = args[++argidx]; |
| continue; |
| } |
| if (arg == "-period" && argidx + 1 < args.size()) { |
| period = std::stof(args[++argidx]); |
| continue; |
| } |
| if (arg == "-waveform" && argidx + 1 < args.size()) { |
| std::string edges(args[++argidx]); |
| std::copy_if(edges.begin(), edges.end(), edges.begin(), [](char c) { return c != '{' or c != '}'; }); |
| std::stringstream ss(edges); |
| ss >> rising_edge >> falling_edge; |
| is_waveform_specified = true; |
| continue; |
| } |
| break; |
| } |
| if (period <= 0) { |
| log_cmd_error("Incorrect period value\n"); |
| } |
| // Add "w:" prefix to selection arguments to enforce wire object |
| // selection |
| AddWirePrefix(args, argidx); |
| extra_args(args, argidx, design); |
| // If clock name is not specified then take the name of the first target |
| std::vector<RTLIL::Wire *> selected_wires; |
| for (auto module : design->modules()) { |
| if (!design->selected(module)) { |
| continue; |
| } |
| for (auto wire : module->wires()) { |
| if (design->selected(module, wire)) { |
| #ifdef SDC_DEBUG |
| log("Selected wire %s\n", RTLIL::unescape_id(wire->name).c_str()); |
| #endif |
| selected_wires.push_back(wire); |
| } |
| } |
| } |
| if (selected_wires.size() == 0) { |
| log_cmd_error("Target selection is empty\n"); |
| } |
| if (name.empty()) { |
| name = RTLIL::unescape_id(selected_wires.at(0)->name); |
| } |
| if (!is_waveform_specified) { |
| rising_edge = 0; |
| falling_edge = period / 2; |
| } |
| Clock::Add(name, selected_wires, period, rising_edge, falling_edge, Clock::EXPLICIT); |
| } |
| |
| void AddWirePrefix(std::vector<std::string> &args, size_t argidx) |
| { |
| auto selection_begin = args.begin() + argidx; |
| std::transform(selection_begin, args.end(), selection_begin, [](std::string &w) { return "w:" + w; }); |
| } |
| }; |
| |
| struct GetClocksCmd : public Pass { |
| GetClocksCmd() : Pass("get_clocks", "Create clock object") {} |
| |
| void help() override |
| { |
| log("\n"); |
| log(" get_clocks [-include_generated_clocks] [-of <nets>] " |
| "[<patterns>]\n"); |
| log("\n"); |
| log("Returns all clocks in the design.\n"); |
| log("\n"); |
| log(" -include_generated_clocks\n"); |
| log(" Include auto-generated clocks.\n"); |
| log("\n"); |
| log(" -of\n"); |
| log(" Get clocks of these nets.\n"); |
| log("\n"); |
| log(" <pattern>\n"); |
| log(" Pattern of clock names. Default are all clocks in the " |
| "design.\n"); |
| log("\n"); |
| } |
| |
| std::vector<std::string> extract_list(const std::string &args) |
| { |
| std::vector<std::string> port_list; |
| std::stringstream ss(args); |
| std::istream_iterator<std::string> begin(ss); |
| std::istream_iterator<std::string> end; |
| std::copy(begin, end, std::back_inserter(port_list)); |
| return port_list; |
| } |
| |
| void execute(std::vector<std::string> args, RTLIL::Design *design) override |
| { |
| |
| // Parse command arguments |
| bool include_generated_clocks(false); |
| std::vector<std::string> clocks_nets; |
| size_t argidx(0); |
| |
| // Parse command switches |
| for (argidx = 1; argidx < args.size(); argidx++) { |
| std::string arg = args[argidx]; |
| if (arg == "-include_generated_clocks") { |
| include_generated_clocks = true; |
| continue; |
| } |
| if (arg == "-of" and argidx + 1 < args.size()) { |
| clocks_nets = extract_list(args[++argidx]); |
| #ifdef SDC_DEBUG |
| for (auto clock_net : clocks_nets) { |
| log("Clock filter %s\n", clock_net.c_str()); |
| } |
| #endif |
| continue; |
| } |
| if (arg.size() > 0 and arg[0] == '-') { |
| log_cmd_error("Unknown option %s.\n", arg.c_str()); |
| } |
| |
| break; |
| } |
| |
| // Parse object patterns |
| std::vector<std::string> clocks_list(args.begin() + argidx, args.end()); |
| |
| // Fetch clocks in the design |
| std::map<std::string, RTLIL::Wire *> clocks(Clocks::GetClocks(design)); |
| if (clocks.size() == 0) { |
| log_warning("No clocks found in design\n"); |
| } |
| |
| // Extract clocks into tcl list |
| Tcl_Interp *interp = yosys_get_tcl_interp(); |
| Tcl_Obj *tcl_list = Tcl_NewListObj(0, NULL); |
| for (auto &clock : clocks) { |
| // Skip propagated clocks (i.e. clock wires with the same parameters |
| // as the master clocks they originate from |
| if (Clock::IsPropagated(clock.second)) { |
| continue; |
| } |
| // Skip generated clocks if -include_generated_clocks is not specified |
| if (Clock::IsGenerated(clock.second) and !include_generated_clocks) { |
| continue; |
| } |
| // Check if clock name is in the list of design clocks |
| if (clocks_list.size() > 0 and std::find(clocks_list.begin(), clocks_list.end(), clock.first) == clocks_list.end()) { |
| continue; |
| } |
| // Check if clock wire is in the -of list |
| if (clocks_nets.size() > 0 and std::find(clocks_nets.begin(), clocks_nets.end(), Clock::WireName(clock.second)) == clocks_nets.end()) { |
| continue; |
| } |
| auto &wire = clock.second; |
| const char *name = RTLIL::id2cstr(wire->name); |
| Tcl_Obj *name_obj = Tcl_NewStringObj(name, -1); |
| Tcl_ListObjAppendElement(interp, tcl_list, name_obj); |
| } |
| Tcl_SetObjResult(interp, tcl_list); |
| } |
| }; |
| |
| struct PropagateClocksCmd : public Pass { |
| PropagateClocksCmd() : Pass("propagate_clocks", "Propagate clock information") {} |
| |
| void help() override |
| { |
| log("\n"); |
| log(" propagate_clocks\n"); |
| log("\n"); |
| log("Propagate clock information throughout the design.\n"); |
| log("\n"); |
| } |
| |
| void execute(std::vector<std::string> args, RTLIL::Design *design) override |
| { |
| if (args.size() > 1) { |
| log_warning("Command accepts no arguments.\nAll will be ignored.\n"); |
| } |
| if (!design->top_module()) { |
| log_cmd_error("No top module selected\n"); |
| } |
| |
| std::array<std::unique_ptr<Propagation>, 2> passes{std::unique_ptr<Propagation>(new BufferPropagation(design, this)), |
| std::unique_ptr<Propagation>(new ClockDividerPropagation(design, this))}; |
| |
| log("Perform clock propagation\n"); |
| |
| for (auto &pass : passes) { |
| pass->Run(); |
| } |
| |
| Clocks::UpdateAbc9DelayTarget(design); |
| } |
| }; |
| |
| class SdcPlugin |
| { |
| public: |
| SdcPlugin() : write_sdc_cmd_(sdc_writer_), set_false_path_cmd_(sdc_writer_), set_max_delay_cmd_(sdc_writer_), set_clock_groups_cmd_(sdc_writer_) |
| { |
| log("Loaded SDC plugin\n"); |
| } |
| |
| ReadSdcCmd read_sdc_cmd_; |
| WriteSdcCmd write_sdc_cmd_; |
| CreateClockCmd create_clock_cmd_; |
| GetClocksCmd get_clocks_cmd_; |
| PropagateClocksCmd propagate_clocks_cmd_; |
| SetFalsePath set_false_path_cmd_; |
| SetMaxDelay set_max_delay_cmd_; |
| SetClockGroups set_clock_groups_cmd_; |
| |
| private: |
| SdcWriter sdc_writer_; |
| } SdcPlugin; |
| |
| PRIVATE_NAMESPACE_END |