| // Copyright 2023 The Verible Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| |
| #include "verilog/tools/ls/autoexpand.h" |
| |
| #include <algorithm> |
| #include <cstddef> |
| #include <cstdint> |
| #include <functional> |
| #include <iterator> |
| #include <memory> |
| #include <optional> |
| #include <ostream> |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/container/flat_hash_set.h" |
| #include "absl/container/node_hash_map.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/numbers.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "common/analysis/syntax_tree_search.h" |
| #include "common/lsp/lsp-protocol.h" |
| #include "common/strings/line_column_map.h" |
| #include "common/strings/position.h" |
| #include "common/text/symbol.h" |
| #include "common/text/text_structure.h" |
| #include "common/text/token_info.h" |
| #include "common/text/tree_utils.h" |
| #include "common/util/logging.h" |
| #include "re2/re2.h" |
| #include "verilog/CST/declaration.h" |
| #include "verilog/CST/dimensions.h" |
| #include "verilog/CST/module.h" |
| #include "verilog/CST/net.h" |
| #include "verilog/CST/port.h" |
| #include "verilog/CST/type.h" |
| #include "verilog/CST/verilog_matchers.h" // IWYU pragma: keep |
| #include "verilog/CST/verilog_nonterminals.h" |
| #include "verilog/analysis/verilog_analyzer.h" |
| #include "verilog/formatting/format_style.h" |
| #include "verilog/formatting/format_style_init.h" |
| #include "verilog/formatting/formatter.h" |
| #include "verilog/tools/ls/lsp-parse-buffer.h" |
| #include "verilog/tools/ls/symbol-table-handler.h" |
| |
| namespace verilog { |
| using verible::FindLastSubtree; |
| using verible::Interval; |
| using verible::LineColumn; |
| using verible::LineColumnRange; |
| using verible::StringSpanOfSymbol; |
| using verible::Symbol; |
| using verible::SymbolCastToNode; |
| using verible::SymbolKind; |
| using verible::SymbolPtr; |
| using verible::SyntaxTreeLeaf; |
| using verible::SyntaxTreeNode; |
| using verible::TextStructureView; |
| using verible::TokenInfo; |
| using verible::TreeSearchMatch; |
| using verible::lsp::CodeAction; |
| using verible::lsp::CodeActionParams; |
| using verible::lsp::TextEdit; |
| using verilog::VerilogAnalyzer; |
| using verilog::formatter::FormatStyle; |
| using verilog::formatter::InitializeFromFlags; |
| |
| namespace { |
| // Possible kinds of AUTO |
| enum class AutoKind { |
| kAutoarg, |
| kAutoinst, |
| kAutoinput, |
| kAutoinout, |
| kAutooutput, |
| kAutowire, |
| kAutoreg, |
| }; |
| |
| // Takes a TextStructureView and generates LSP TextEdits for AUTO expansion |
| class AutoExpander { |
| public: |
| // TODO: move most of these items to private |
| |
| // An AUTO matched in the buffer text |
| struct Match { |
| absl::string_view auto_span; // Span of the entire AUTO |
| absl::string_view comment_span; // Span of the AUTO pragma comment |
| }; |
| |
| // A single AUTO expansion in terms of the replaced span and expanded text |
| struct Expansion { |
| absl::string_view replaced_span; // Span that is to be replaced |
| std::string new_text; // Text to replace the span with |
| }; |
| |
| // Represents a port connection |
| struct Connection { |
| std::string port_name; // The name of the port in the module instance |
| bool emit_dimensions; // If true, when emitted, the connection should be |
| // annotated with the signal dimensions |
| }; |
| |
| // Stores information about the instance the port is connected to |
| struct ConnectedInstance { |
| std::optional<absl::string_view> instance; // Name of the instance a port |
| // is connected to |
| absl::string_view type; // Type of the instance a port is connected to |
| }; |
| |
| // A SystemVerilog range [msb:lsb] |
| struct DimensionRange { |
| int64_t msb; |
| int64_t lsb; |
| }; |
| |
| // A dimension can be a range, an unsigned integer, or, if it cannot be |
| // interpreted as one of these, a string |
| using Dimension = std::variant<absl::string_view, size_t, DimensionRange>; |
| |
| // Iterates through the given dimension vectors and returns a new one with |
| // each element being the maximum of corresponding original dimensions. |
| static std::vector<Dimension> MaxDimensions( |
| const std::vector<Dimension> &first, |
| const std::vector<Dimension> &second); |
| |
| // Representation of a net-like, base type for Port and Wire (you probably |
| // want to use those) |
| struct Net { |
| std::string name; // Name of the net |
| std::vector<ConnectedInstance> conn_inst; // What instances is |
| // it connected to? |
| std::vector<Dimension> packed_dimensions; // This net's packed dimensions |
| std::vector<Dimension> unpacked_dimensions; // This net's unpacked |
| // dimensions |
| |
| // Writes the port's identifier with packed and unpacked dimensions to the |
| // output stream |
| void EmitIdWithDimensions(std::ostream &output) const; |
| |
| // Returns true if the port is connected to any instance |
| bool IsConnected() const { return !conn_inst.empty(); } |
| |
| // Adds the given connected instance to the net's list of connections, and |
| // makes the packed dimensions the max of the current dimensions and the |
| // ones provided |
| void AddConnection(const ConnectedInstance &connected, |
| const std::vector<Dimension> &new_packed_dimensions) { |
| conn_inst.push_back(connected); |
| packed_dimensions = |
| AutoExpander::MaxDimensions(packed_dimensions, new_packed_dimensions); |
| } |
| }; |
| |
| // A port, with direction and some meta-info |
| struct Port final : Net { |
| enum class Direction { kInput, kInout, kOutput }; |
| enum class Declaration { kUndeclared, kAutogenerated, kDeclared }; |
| |
| Direction direction; // Direction of the port |
| Declaration declaration; // Is it user-declared or autogenerated |
| absl::string_view::const_iterator it; // Location of the port's declaration |
| // in the source file |
| |
| // Writes the port's direction to the output stream |
| void EmitDirection(std::ostream &output) const; |
| |
| // Writes a comment describing the port's connection to the output stream |
| void EmitConnectionComment(std::ostream &output) const; |
| }; |
| |
| // A wire generated by AUTO expansion |
| struct Wire final : Net { |
| // Writes a comment describing the port's connection to the output stream |
| void EmitConnectionComment(std::ostream &output) const; |
| }; |
| |
| // Represents an AUTO_TEMPLATE |
| struct Template { |
| using Map = absl::flat_hash_map<absl::string_view, std::vector<Template>>; |
| |
| absl::string_view::const_iterator it; // Location of the template in the |
| // source file |
| std::shared_ptr<RE2> instance_name_re; // Regex for matching the instance |
| // name. Shared between templates |
| // declared at the same place |
| absl::flat_hash_map<absl::string_view, Connection> |
| connections; // Map of instance ports ports to connected module ports |
| }; |
| |
| enum class PortDeclStyle { |
| kColonSeparator, |
| kCommaSeparator, |
| kCommaSeparatorExceptLast |
| }; |
| |
| // Module information relevant to AUTO expansion |
| class Module { |
| public: |
| explicit Module(const Symbol &module) |
| : symbol_(module), name_(GetModuleName(symbol_)->get().text()) { |
| RetrieveModuleHeaderPorts(); |
| RetrieveModuleBodyPorts(); |
| } |
| |
| // Writes all port names that match the predicate to the output stream, |
| // under the specified heading comment |
| void EmitNonAnsiPortList( |
| std::ostream &output, absl::string_view heading, |
| const std::function<bool(const Port &)> &pred) const; |
| |
| // Writes port connections to all ports to the output stream, under the |
| // specified heading comment |
| void EmitPortConnections(std::ostream &output, |
| absl::string_view instance_name, |
| absl::string_view header, |
| const std::function<bool(const Port &)> &pred, |
| const Template *tmpl) const; |
| |
| // Writes declarations of ports that fulfill the given predicate to the |
| // output stream |
| void EmitPortDeclarations( |
| std::ostream &output, PortDeclStyle style, |
| const std::function<bool(const Port &)> &pred) const; |
| |
| // Writes wire declarations of undeclared output ports to the output stream, |
| // with the provided span defining which existing wires were autogenerated |
| void EmitUndeclaredWireDeclarations(std::ostream &output, |
| absl::string_view auto_span) const; |
| |
| // Writes reg declarations of unconnected output ports to the output stream, |
| // with the provided span defining which existing regs were autogenerated |
| void EmitUnconnectedOutputRegDeclarations( |
| std::ostream &output, absl::string_view auto_span) const; |
| |
| // Calls the closure on each port and the name of the port that should be |
| // connected to it. If a template is given, the connected port name is taken |
| // from the template, otherwise it's the same as the port name. |
| void GenerateConnections( |
| absl::string_view instance_name, const Template *tmpl, |
| const std::function<void(const Port &, const Connection &)> &fun) const; |
| |
| // Set an existing port's connection, or create a new port with the given |
| // name, direction, and connection |
| void AddGeneratedConnection( |
| const std::string &port_name, Port::Direction direction, |
| const ConnectedInstance &connected, |
| const std::vector<Dimension> &packed_dimensions, |
| const std::vector<Dimension> &unpacked_dimensions); |
| |
| // Sort ports by location in the source |
| void SortPortsByLocation(); |
| |
| // Gets all AUTO_TEMPLATEs from the module |
| void RetrieveAutoTemplates(); |
| |
| // Gets all dependencies of the module (modules instantiated within it) |
| void RetrieveDependencies( |
| const absl::node_hash_map<absl::string_view, Module> &modules); |
| |
| // Retrieves the matching template from a typename -> template map |
| const Template *GetAutoTemplate( |
| absl::string_view type_id, absl::string_view instance_name, |
| absl::string_view::const_iterator instance_it) const; |
| |
| // Returns true if the module depends on (uses) a given module |
| bool DependsOn(const Module *module) const { |
| if (this == module) return false; |
| absl::flat_hash_set<const Module *> visited; |
| return DependsOn(module, &visited); |
| } |
| |
| // Returns true if any ports fulfill the given predicate |
| bool AnyPorts(const std::function<bool(const Port &)> &pred) { |
| return std::find_if(ports_.begin(), ports_.end(), pred) != ports_.end(); |
| } |
| |
| // Calls the given function on each port |
| void ForEachPort(const std::function<void(Port &)> &fun) { |
| std::for_each(ports_.begin(), ports_.end(), fun); |
| } |
| |
| // Erase all ports that fulfill the given predicate |
| void ErasePortsIf(const std::function<bool(const Port &)> &pred) { |
| const auto it = std::remove_if(ports_.begin(), ports_.end(), pred); |
| ports_.erase(it, ports_.end()); |
| } |
| |
| // Returns the Symbol representing this module |
| const verible::Symbol &Symbol() const { return symbol_; } |
| |
| // Returns the module name |
| absl::string_view Name() const { return name_; } |
| |
| private: |
| // Gets ports from the header of the module |
| void RetrieveModuleHeaderPorts(); |
| |
| // Gets ports from the body of the module |
| void RetrieveModuleBodyPorts(); |
| |
| // Store the given port in the internal vector |
| void PutDeclaredPort(const SyntaxTreeNode &port_node); |
| |
| // Recurses into dependencies to check if we depend on a given module. |
| // Stores visited modules in a set to avoid infinite loops. For big |
| // dependency graphs one should build a proper graph and do a |
| // topological sort. However, these dependencies (and the dependent) are all |
| // from a single file, and usually there is only one module per file. This |
| // should be fast enough for unusual cases where there are multiple modules |
| // in a single file. |
| bool DependsOn(const Module *module, |
| absl::flat_hash_set<const Module *> *visited) const; |
| |
| // The symbol that represents this module |
| const verible::Symbol &symbol_; |
| |
| // The name of this module |
| const absl::string_view name_; |
| |
| // This module's ports |
| std::vector<Port> ports_; |
| |
| // New wires to emit (if not already defined) |
| std::vector<Wire> wires_to_generate_; |
| |
| // This module's direct dependencies |
| absl::flat_hash_set<const Module *> dependencies_; |
| |
| // This module's AUTO_TEMPLATEs |
| Template::Map templates_; |
| }; |
| |
| AutoExpander(const TextStructureView &text_structure, |
| SymbolTableHandler *symbol_table_handler) |
| : text_structure_(text_structure), |
| symbol_table_handler_(symbol_table_handler) { |
| expand_span_ = text_structure_.Contents(); |
| } |
| |
| AutoExpander(const TextStructureView &text_structure, |
| SymbolTableHandler *symbol_table_handler, |
| Interval<size_t> line_range) |
| : AutoExpander(text_structure, symbol_table_handler) { |
| size_t min = line_range.min < text_structure.Lines().size() |
| ? line_range.min |
| : text_structure.Lines().size() - 1; |
| size_t max = line_range.max < text_structure.Lines().size() |
| ? line_range.max |
| : text_structure.Lines().size() - 1; |
| const auto begin = text_structure.Lines()[min].begin(); |
| const auto end = text_structure.Lines()[max].end(); |
| const size_t length = static_cast<size_t>(std::distance(begin, end)); |
| expand_span_ = absl::string_view(begin, length); |
| } |
| |
| AutoExpander(const TextStructureView &text_structure, |
| SymbolTableHandler *symbol_table_handler, |
| const absl::flat_hash_set<AutoKind> &allowed_autos) |
| : AutoExpander(text_structure, symbol_table_handler) { |
| allowed_autos_ = allowed_autos; |
| } |
| |
| // Retrieves port names from a module declared before the given location |
| absl::flat_hash_set<absl::string_view> GetPortsListedBefore( |
| const Symbol &module, absl::string_view::const_iterator it) const; |
| |
| // Retrieves port names from a module instance connected before the given |
| // location |
| absl::flat_hash_set<absl::string_view> GetPortsConnectedBefore( |
| const Symbol &instance, absl::string_view::const_iterator it) const; |
| |
| // Expands AUTOARG for the given module |
| std::optional<Expansion> ExpandAutoarg(const Module &module) const; |
| |
| // Expands AUTOINST for the given module instance |
| std::optional<Expansion> ExpandAutoinst(Module *module, |
| const Symbol &instance, |
| absl::string_view type_id); |
| |
| // Expands AUTO<port-direction/data-type> for the given module |
| // Limitation: this only detects ports from AUTOINST. This limitation is also |
| // present in the original Emacs Verilog-mode. |
| std::optional<Expansion> ExpandAutoDeclarations( |
| const Module &module, Match match, absl::string_view description, |
| const std::function<void(const Module &, std::ostream &)> &emit) const; |
| |
| // Expands AUTOINPUT/AUTOINOUT/AUTOOUTPUT for the given module |
| std::optional<Expansion> ExpandAutoPorts(Module *module, |
| std::optional<Match> match, |
| Port::Direction direction) const; |
| |
| // Expands AUTOWIRE for the given module |
| std::optional<Expansion> ExpandAutowire(const Module &module) const; |
| |
| // Expands AUTOREG for the given module |
| std::optional<Expansion> ExpandAutoreg(const Module &module) const; |
| |
| // Expands all AUTOs in the buffer |
| std::vector<Expansion> Expand(); |
| |
| // Find kinds of AUTO used in the expand span |
| absl::flat_hash_set<AutoKind> FindAutoKinds(); |
| |
| private: |
| // Matches the given regex and erases ports from the module that are in the |
| // match span |
| std::optional<Match> FindMatchAndErasePorts(AutoExpander::Module *module, |
| AutoKind kind, const RE2 &re); |
| |
| // Finds the span that should be replaced in the symbol (from the start of |
| // the comment span to the end of the symbol span. Used by AUTOARG and |
| // AUTOINST) |
| std::optional<absl::string_view> FindSpanToReplace( |
| const Symbol &symbol, absl::string_view auto_span) const; |
| |
| // Checks if the given AUTO kind should be expanded |
| bool ShouldExpand(const AutoKind kind) const { |
| return allowed_autos_.empty() || allowed_autos_.contains(kind); |
| } |
| |
| // Span in which expansions are allowed |
| absl::string_view expand_span_; |
| |
| // Kinds of AUTOs that can be expanded (all if this set is empty) |
| absl::flat_hash_set<AutoKind> allowed_autos_; |
| |
| // Text structure of the buffer to expand AUTOs in |
| const TextStructureView &text_structure_; |
| |
| // Symbol table wrapper for the language server |
| SymbolTableHandler *symbol_table_handler_; |
| |
| // Gathered module information (module name -> module info) |
| absl::node_hash_map<absl::string_view, Module> modules_; |
| |
| // Regex for finding any AUTOs |
| static const LazyRE2 auto_re_; |
| |
| // Regex for finding AUTOARG comments |
| static const LazyRE2 autoarg_re_; |
| |
| // Regex for finding AUTOINST comments |
| static const LazyRE2 autoinst_re_; |
| |
| // Regexes for AUTO_TEMPLATE comments |
| static const LazyRE2 autotemplate_re_; |
| static const LazyRE2 autotemplate_type_re_; |
| static const LazyRE2 autotemplate_conn_re_; |
| |
| // Regexes for AUTOINPUT/AUTOOUTPUT/AUTOINOUT/AUTOWIRE/AUTOREG comments |
| static const LazyRE2 autoinput_re_; |
| static const LazyRE2 autooutput_re_; |
| static const LazyRE2 autoinout_re_; |
| static const LazyRE2 autowire_re_; |
| static const LazyRE2 autoreg_re_; |
| }; |
| |
| const LazyRE2 AutoExpander::auto_re_{ |
| R"(/\*\s*(AUTOARG|AUTOINST|AUTOINPUT|AUTOINOUT|AUTOOUTPUT|AUTOWIRE|AUTOREG)\s*\*/)"}; |
| |
| const LazyRE2 AutoExpander::autoarg_re_{R"((/\*\s*AUTOARG\s*\*/))"}; |
| |
| const LazyRE2 AutoExpander::autoinst_re_{R"((/\*\s*AUTOINST\s*\*/))"}; |
| |
| // AUTO_TEMPLATE regex breakdown: |
| // The entire expression is wrapped in () so the first capturing group is the |
| // entire match. |
| // /\* – start of comment |
| // (?:\s*\S+\s+AUTO_TEMPLATE\s*\n)* – optional other AUTO_TEMPLATE types, end |
| // with newline |
| // \s*\S+\s+AUTO_TEMPLATE – at least one AUTO_TEMPLATE is required |
| // \s*(?:"([^"])*")? – optional instance name regex |
| // \s*\(?:[\s\S]*?\); – parens with port connections |
| // \s*\*/ – end of comment |
| const LazyRE2 AutoExpander::autotemplate_re_{ |
| R"((/\*(?:\s*\S+\s+AUTO_TEMPLATE\s*\n)*\s*\S+\s+AUTO_TEMPLATE\s*(?:"([^"]*)\")?\s*\([\s\S]*?\);\s*\*/))"}; |
| |
| // AUTO_TEMPLATE type regex: the first capturing group is the instance type |
| const LazyRE2 AutoExpander::autotemplate_type_re_{R"((\S+)\s+AUTO_TEMPLATE)"}; |
| |
| // AUTO_TEMPLATE connection regex breakdown: |
| // \.\s* – starts with a dot |
| // ([^\s(]+?) – first group, at least one character other than whitespace or |
| // opening paren |
| // \s*\(\s* – optional whitespace, opening paren, optional whitespace again |
| // ([^\s(]+?) – second group, same as the first one |
| // \s*(\[\])? – optional third group, capturing '[]' |
| // \s*\)* – optional whitespace, closing paren |
| const LazyRE2 AutoExpander::autotemplate_conn_re_{ |
| R"(\.\s*([^\s(]+?)\s*\(\s*([^\s(]+?)\s*(\[\])?\s*\))"}; |
| |
| // AUTOINPUT/OUTPUT/INOUT/WIRE/REG regex breakdown: |
| // The entire expression is wrapped in () so the first capturing group is the |
| // entire match. |
| // (/\*\s* ... \s*\*/\s*?\n) – starting comment |
| // (?:\s*//.*\n)? – optional starting comment |
| // ("Beginning of automatic...") |
| // (?: ... )? – an optional non-capturing group: |
| // [\s\S]*? – any text (usually port |
| // declarations) |
| // [^\S\r\n]*// End of automatics.*\n – ended by an "End of automatics" |
| // comment |
| #define MAKE_AUTODECL_REGEX(decl_kind) \ |
| R"(((/\*\s*AUTO)" decl_kind \ |
| R"(\s*\*/\s*?)(?:\s*//.*)?(?:[\s\S]*?[^\S\r\n]*// End of automatics.*)?))" |
| const LazyRE2 AutoExpander::autoinput_re_{MAKE_AUTODECL_REGEX("INPUT")}; |
| const LazyRE2 AutoExpander::autoinout_re_{MAKE_AUTODECL_REGEX("INOUT")}; |
| const LazyRE2 AutoExpander::autooutput_re_{MAKE_AUTODECL_REGEX("OUTPUT")}; |
| const LazyRE2 AutoExpander::autowire_re_{MAKE_AUTODECL_REGEX("WIRE")}; |
| const LazyRE2 AutoExpander::autoreg_re_{MAKE_AUTODECL_REGEX("REG")}; |
| |
| using Dimension = AutoExpander::Dimension; |
| using DimensionRange = AutoExpander::DimensionRange; |
| |
| // Fallback for the case of comparing two dimensions if there is no obvious |
| // maximum. Simply returns the first given dimension. |
| template <typename T, typename U> |
| static Dimension MaxDimension(const T first, const U second) { |
| return first; |
| } |
| |
| // Returns the greater of the two given dimensions. |
| template <> |
| Dimension MaxDimension(const size_t first, const size_t second) { |
| return std::max(first, second); |
| } |
| |
| // Returns a range that can fit the two given dimensions. |
| template <> |
| Dimension MaxDimension(const DimensionRange first, |
| const DimensionRange second) { |
| int64_t max = std::max(std::max(first.msb, first.lsb), |
| std::max(second.msb, second.lsb)); |
| int64_t min = std::min(std::min(first.msb, first.lsb), |
| std::min(second.msb, second.lsb)); |
| if (first.msb >= first.lsb) { |
| return DimensionRange{.msb = max, .lsb = min}; |
| } |
| return DimensionRange{.msb = min, .lsb = max}; |
| } |
| |
| // Converts the first dimension to a DimensionRange, and then returns the |
| // maximum of it and the given range. |
| template <> |
| Dimension MaxDimension(const size_t first, const DimensionRange second) { |
| return MaxDimension( |
| DimensionRange{.msb = static_cast<int64_t>(first) - 1, .lsb = 0}, second); |
| } |
| |
| // Converts the second dimension to a DimensionRange, and then returns the |
| // maximum of it and the given range. |
| template <> |
| Dimension MaxDimension(const DimensionRange first, const size_t second) { |
| return MaxDimension( |
| first, DimensionRange{.msb = static_cast<int64_t>(second) - 1, .lsb = 0}); |
| } |
| |
| std::vector<Dimension> AutoExpander::MaxDimensions( |
| const std::vector<Dimension> &first, const std::vector<Dimension> &second) { |
| if (first.empty() && second.size() == 1) return second; |
| if (second.empty() && first.size() == 1) return first; |
| if (first.size() != second.size()) LOG(ERROR) << "Mismatched dimensions"; |
| std::vector<Dimension> dims; |
| auto it1 = first.begin(), it2 = second.begin(); |
| while (it1 != first.end() && it2 != second.end()) { |
| const auto dims1 = *it1, dims2 = *it2; |
| std::visit( |
| [&](const auto d1) { |
| std::visit( |
| [&](const auto d2) { dims.push_back(MaxDimension(d1, d2)); }, |
| dims2); |
| }, |
| dims1); |
| ++it1; |
| ++it2; |
| } |
| return dims; |
| } |
| |
| std::ostream &operator<<(std::ostream &os, const DimensionRange range) { |
| os << range.msb << ":" << range.lsb; |
| return os; |
| } |
| |
| std::ostream &operator<<(std::ostream &os, const Dimension dim) { |
| std::visit([&os](auto &&arg) { os << '[' << arg << ']'; }, dim); |
| return os; |
| } |
| |
| void AutoExpander::Net::EmitIdWithDimensions(std::ostream &output) const { |
| if (!packed_dimensions.empty()) { |
| for (const Dimension &dimension : packed_dimensions) { |
| output << dimension; |
| } |
| output << ' '; |
| } |
| output << name; |
| for (const Dimension &dimension : unpacked_dimensions) { |
| output << dimension; |
| } |
| } |
| |
| void AutoExpander::Port::EmitDirection(std::ostream &output) const { |
| switch (direction) { |
| case Port::Direction::kInput: |
| output << "input "; |
| break; |
| case Port::Direction::kInout: |
| output << "inout "; |
| break; |
| case Port::Direction::kOutput: |
| output << "output "; |
| break; |
| default: |
| LOG(ERROR) << "Incorrect port direction"; |
| break; |
| } |
| } |
| |
| void AutoExpander::Port::EmitConnectionComment(std::ostream &output) const { |
| if (conn_inst.empty()) return; |
| const auto &instance = conn_inst[0].instance; |
| if (!instance) return; |
| const absl::string_view type = conn_inst[0].type; |
| switch (direction) { |
| case Direction::kInput: |
| output << " // To " << *instance << " of " << type; |
| break; |
| case Direction::kInout: |
| output << " // To/From " << *instance << " of " << type; |
| break; |
| case Direction::kOutput: |
| output << " // From " << *instance << " of " << type; |
| break; |
| default: |
| LOG(ERROR) << "Incorrect port direction"; |
| return; |
| } |
| if (conn_inst.size() > 1) output << ", ..."; |
| // TODO: Print as many instance names as we can without going against the |
| // formatter? |
| } |
| |
| void AutoExpander::Wire::EmitConnectionComment(std::ostream &output) const { |
| if (conn_inst.empty()) return; |
| const auto &instance = conn_inst[0].instance; |
| if (!instance) return; |
| output << " // To/From " << *instance << " of " << conn_inst[0].type; |
| if (conn_inst.size() > 1) output << ", .."; |
| } |
| |
| void AutoExpander::Module::EmitNonAnsiPortList( |
| std::ostream &output, const absl::string_view heading, |
| const std::function<bool(const Port &)> &pred) const { |
| bool first = true; |
| for (const Port &port : ports_) { |
| if (!pred(port)) continue; |
| if (first) { |
| if (output.tellp() != 0) output << ','; |
| output << '\n' << "// " << heading << '\n'; |
| first = false; |
| } else { |
| output << ','; |
| } |
| output << port.name; |
| } |
| } |
| |
| void AutoExpander::Module::EmitPortConnections( |
| std::ostream &output, const absl::string_view instance_name, |
| const absl::string_view header, |
| const std::function<bool(const Port &)> &pred, const Template *tmpl) const { |
| bool first = true; |
| GenerateConnections( |
| instance_name, tmpl, [&](const Port &port, const Connection &connected) { |
| if (!pred(port)) return; |
| if (first) { |
| if (output.tellp() != 0) output << ','; |
| output << '\n' << "// " << header; |
| first = false; |
| } else { |
| output << ','; |
| } |
| output << '\n' << '.' << port.name << '(' << connected.port_name; |
| if (!connected.emit_dimensions) { |
| output << ')'; |
| return; |
| } |
| if (port.packed_dimensions.size() > 1 || |
| !port.unpacked_dimensions.empty()) { |
| output << "/*"; |
| for (const Dimension &dimension : port.packed_dimensions) { |
| output << dimension; |
| } |
| if (!port.unpacked_dimensions.empty()) { |
| output << '.'; |
| for (const Dimension &dimension : port.unpacked_dimensions) { |
| output << dimension; |
| } |
| } |
| output << "*/"; |
| } else if (port.packed_dimensions.size() == 1) { |
| output << port.packed_dimensions[0]; |
| } |
| output << ')'; |
| }); |
| } |
| |
| // Checks if two string spans are overlapping |
| bool SpansOverlapping(const absl::string_view first, |
| const absl::string_view second) { |
| return first.end() > second.begin() && first.begin() < second.end(); |
| } |
| |
| void AutoExpander::Module::EmitUndeclaredWireDeclarations( |
| std::ostream &output, const absl::string_view auto_span) const { |
| absl::flat_hash_set<absl::string_view> declared_wires; |
| for (const auto ® : FindAllNetVariables(symbol_)) { |
| const SyntaxTreeLeaf *const net_name_leaf = |
| GetNameLeafOfNetVariable(*reg.match); |
| const absl::string_view net_name = net_name_leaf->get().text(); |
| if (!SpansOverlapping(net_name, auto_span)) { |
| declared_wires.insert(net_name); |
| } |
| } |
| |
| for (const Port &port : ports_) { |
| if (port.direction != Port::Direction::kInput && |
| port.declaration == Port::Declaration::kUndeclared && |
| port.IsConnected() && !declared_wires.contains(port.name)) { |
| output << "wire "; |
| port.EmitIdWithDimensions(output); |
| output << ';'; |
| port.EmitConnectionComment(output); |
| output << '\n'; |
| } |
| } |
| for (const Wire &wire : wires_to_generate_) { |
| output << "wire "; |
| wire.EmitIdWithDimensions(output); |
| output << ';'; |
| wire.EmitConnectionComment(output); |
| output << '\n'; |
| } |
| } |
| |
| void AutoExpander::Module::EmitUnconnectedOutputRegDeclarations( |
| std::ostream &output, const absl::string_view auto_span) const { |
| absl::flat_hash_set<absl::string_view> declared_regs; |
| for (const auto ® : FindAllRegisterVariables(symbol_)) { |
| const SyntaxTreeLeaf *const reg_name_leaf = |
| GetNameLeafOfRegisterVariable(*reg.match); |
| const absl::string_view reg_name = reg_name_leaf->get().text(); |
| if (!SpansOverlapping(reg_name, auto_span)) { |
| declared_regs.insert(reg_name); |
| } |
| } |
| |
| for (const Port &port : ports_) { |
| if (port.direction == Port::Direction::kOutput && |
| port.declaration == Port::Declaration::kDeclared && |
| !port.IsConnected() && !declared_regs.contains(port.name)) { |
| output << "reg "; |
| port.EmitIdWithDimensions(output); |
| output << ";\n"; |
| } |
| } |
| } |
| |
| void AutoExpander::Module::GenerateConnections( |
| absl::string_view instance_name, const Template *tmpl, |
| const std::function<void(const Port &, const Connection &)> &fun) const { |
| for (const Port &port : ports_) { |
| Connection connected{.port_name = port.name, .emit_dimensions = true}; |
| if (tmpl) { |
| const auto it = tmpl->connections.find(port.name); |
| if (it != tmpl->connections.end()) connected = it->second; |
| } |
| size_t pos = connected.port_name.find('@'); |
| while (pos != std::string::npos) { |
| connected.port_name.replace(pos, 1, instance_name.begin(), |
| instance_name.length()); |
| pos = connected.port_name.find('@', pos); |
| } |
| fun(port, connected); |
| } |
| } |
| |
| void AutoExpander::Module::AddGeneratedConnection( |
| const std::string &port_name, const Port::Direction direction, |
| const ConnectedInstance &connected, |
| const std::vector<Dimension> &packed_dimensions, |
| const std::vector<Dimension> &unpacked_dimensions) { |
| const auto name_matches = [&](const Net &net) { |
| return net.name == port_name; |
| }; |
| const auto wire_it = std::find_if(wires_to_generate_.begin(), |
| wires_to_generate_.end(), name_matches); |
| // If there is already a wire with the same name, add the connection to it. |
| // This wire is a connection between multiple instances. |
| if (wire_it != wires_to_generate_.end()) { |
| wire_it->AddConnection(connected, packed_dimensions); |
| return; |
| } |
| const auto port_it = std::find_if(ports_.begin(), ports_.end(), name_matches); |
| // Else look for an existing port with this name. If there is one, and it has |
| // the same direction, reuse it. If its direction differs, convert it to a new |
| // wire. |
| if (port_it != ports_.end()) { |
| Port &port = *port_it; |
| if (port.direction == direction) { |
| port.AddConnection(connected, packed_dimensions); |
| } else { |
| wires_to_generate_.push_back( |
| {{.name = port_name, |
| .conn_inst = port.conn_inst, |
| .packed_dimensions = AutoExpander::MaxDimensions( |
| port.packed_dimensions, packed_dimensions), |
| .unpacked_dimensions = unpacked_dimensions}}); |
| wires_to_generate_.back().AddConnection(connected, packed_dimensions); |
| ports_.erase(port_it, port_it + 1); |
| } |
| return; |
| } |
| // There are no wires or ports of the given name. Just make a new port. |
| ports_.push_back({ |
| {port_name, {connected}, packed_dimensions, unpacked_dimensions}, |
| direction, |
| Port::Declaration::kUndeclared, |
| nullptr, |
| }); |
| } |
| |
| void AutoExpander::Module::SortPortsByLocation() { |
| // Stable sort is needed here, as ports autogenerated via AUTOINPUT, |
| // AUTOOUTPUT, and AUTOINOUT get assigned one location, which is the start of |
| // the corresponding `AUTO` comment. Using unstable sort results in a random |
| // order of ports. |
| std::stable_sort( |
| ports_.begin(), ports_.end(), |
| [](const Port &left, const Port &right) { return left.it < right.it; }); |
| } |
| |
| void AutoExpander::Module::RetrieveAutoTemplates() { |
| absl::string_view autotmpl_search_span = StringSpanOfSymbol(symbol_); |
| absl::string_view autotmpl_span; |
| absl::string_view autotmpl_inst_name; |
| while (RE2::FindAndConsume(&autotmpl_search_span, *autotemplate_re_, |
| &autotmpl_span, &autotmpl_inst_name)) { |
| Template tmpl{.it = autotmpl_span.begin()}; |
| if (!autotmpl_inst_name.empty()) { |
| tmpl.instance_name_re = std::make_shared<RE2>(autotmpl_inst_name); |
| if (!tmpl.instance_name_re->ok()) { |
| LOG(ERROR) << "Invalid regex in AUTO template: " << autotmpl_inst_name; |
| continue; |
| } |
| } |
| |
| absl::string_view autotmpl_conn_search_span = autotmpl_span; |
| absl::string_view instance_port_name; |
| absl::string_view module_port_name; |
| absl::string_view dimensions; |
| while (RE2::FindAndConsume(&autotmpl_conn_search_span, |
| *autotemplate_conn_re_, &instance_port_name, |
| &module_port_name, &dimensions)) { |
| tmpl.connections.insert( |
| std::make_pair(instance_port_name, |
| Connection{.port_name = std::string(module_port_name), |
| .emit_dimensions = !dimensions.empty()})); |
| } |
| |
| absl::string_view autotmpl_type_search_span = autotmpl_span; |
| absl::string_view instance_type_name; |
| while (RE2::FindAndConsume(&autotmpl_type_search_span, |
| *autotemplate_type_re_, &instance_type_name)) { |
| templates_[instance_type_name].push_back(tmpl); |
| } |
| } |
| } |
| |
| void AutoExpander::Module::RetrieveDependencies( |
| const absl::node_hash_map<absl::string_view, Module> &modules) { |
| for (const auto &data : FindAllDataDeclarations(symbol_)) { |
| const verible::Symbol *const type_id_node = |
| GetTypeIdentifierFromDataDeclaration(*data.match); |
| // Some data declarations do not have a type id, ignore those |
| if (!type_id_node) continue; |
| const absl::string_view dependency_name = StringSpanOfSymbol(*type_id_node); |
| const auto it = modules.find(dependency_name); |
| if (it != modules.end()) { |
| dependencies_.insert(&it->second); |
| } |
| } |
| } |
| |
| const AutoExpander::Template *AutoExpander::Module::GetAutoTemplate( |
| const absl::string_view type_id, const absl::string_view instance_name, |
| const absl::string_view::const_iterator instance_it) const { |
| const auto it = templates_.find(type_id); |
| if (it == templates_.end()) return nullptr; |
| const Template *matching_tmpl = nullptr; |
| // Linear search for the matching template (there should be very few |
| // templates per type, often just one) |
| for (const Template &tmpl : it->second) { |
| if (instance_it < tmpl.it) break; |
| if (tmpl.instance_name_re) { |
| if (!RE2::FullMatch(instance_name, *tmpl.instance_name_re)) continue; |
| } |
| matching_tmpl = &tmpl; |
| } |
| return matching_tmpl; |
| } |
| |
| void AutoExpander::Module::EmitPortDeclarations( |
| std::ostream &output, const PortDeclStyle style, |
| const std::function<bool(const Port &)> &pred) const { |
| const auto end = std::find_if(ports_.crbegin(), ports_.crend(), pred).base(); |
| for (auto it = ports_.cbegin(); it != end; it++) { |
| const Port &port = *it; |
| if (!pred(port)) continue; |
| port.EmitDirection(output); |
| port.EmitIdWithDimensions(output); |
| if (style == PortDeclStyle::kColonSeparator) { |
| output << ';'; |
| } else if (style == PortDeclStyle::kCommaSeparator || it + 1 < end) { |
| output << ','; |
| } |
| port.EmitConnectionComment(output); |
| output << '\n'; |
| } |
| } |
| |
| void AutoExpander::Module::RetrieveModuleHeaderPorts() { |
| const auto module_ports = GetModulePortDeclarationList(symbol_); |
| if (!module_ports) return; |
| for (const SymbolPtr &port : module_ports->children()) { |
| if (port->Kind() == SymbolKind::kLeaf) continue; |
| const SyntaxTreeNode &port_node = SymbolCastToNode(*port); |
| const NodeEnum tag = NodeEnum(port_node.Tag().tag); |
| if (tag == NodeEnum::kPortDeclaration) { |
| PutDeclaredPort(port_node); |
| } |
| } |
| } |
| |
| void AutoExpander::Module::RetrieveModuleBodyPorts() { |
| for (const auto &port : FindAllModulePortDeclarations(symbol_)) { |
| PutDeclaredPort(SymbolCastToNode(*port.match)); |
| } |
| } |
| |
| // Converts kDimensionScalar and kDimensionRange nodes to Dimensions. Parses |
| // them as integers or ranges if possible, falls back to a string span. |
| std::vector<AutoExpander::Dimension> GetDimensionsFromNodes( |
| const std::vector<TreeSearchMatch> &dimension_nodes) { |
| using Dimension = AutoExpander::Dimension; |
| std::vector<Dimension> dimensions; |
| dimensions.reserve(dimension_nodes.size()); |
| for (auto &dimension : dimension_nodes) { |
| for (const auto &scalar : |
| SearchSyntaxTree(*dimension.match, NodekDimensionScalar())) { |
| size_t size; |
| const Symbol &scalar_value = *SymbolCastToNode(*scalar.match)[1]; |
| const absl::string_view span = StringSpanOfSymbol(scalar_value); |
| const bool result = absl::SimpleAtoi(span, &size); |
| dimensions.push_back(result ? Dimension{size} : Dimension{span}); |
| } |
| for (const auto &range : |
| SearchSyntaxTree(*dimension.match, NodekDimensionRange())) { |
| const Symbol *left = GetDimensionRangeLeftBound(*range.match); |
| const Symbol *right = GetDimensionRangeRightBound(*range.match); |
| int64_t msb, lsb; |
| const bool left_result = |
| absl::SimpleAtoi(StringSpanOfSymbol(*left), &msb); |
| const bool right_result = |
| absl::SimpleAtoi(StringSpanOfSymbol(*right), &lsb); |
| if (left_result && right_result) { |
| dimensions.push_back( |
| AutoExpander::DimensionRange{.msb = msb, .lsb = lsb}); |
| } else { |
| const absl::string_view left_span = StringSpanOfSymbol(*left); |
| const absl::string_view right_span = StringSpanOfSymbol(*right); |
| dimensions.push_back(absl::string_view{ |
| left_span.begin(), static_cast<size_t>(std::distance( |
| left_span.begin(), right_span.end()))}); |
| } |
| } |
| } |
| return dimensions; |
| } |
| |
| void AutoExpander::Module::PutDeclaredPort(const SyntaxTreeNode &port_node) { |
| const NodeEnum tag = NodeEnum(port_node.Tag().tag); |
| const SyntaxTreeLeaf *const dir_leaf = |
| tag == NodeEnum::kPortDeclaration |
| ? GetDirectionFromPortDeclaration(port_node) |
| : GetDirectionFromModulePortDeclaration(port_node); |
| const SyntaxTreeLeaf *const id_leaf = |
| tag == NodeEnum::kPortDeclaration |
| ? GetIdentifierFromPortDeclaration(port_node) |
| : GetIdentifierFromModulePortDeclaration(port_node); |
| if (!dir_leaf || !id_leaf) return; |
| const absl::string_view dir_span = dir_leaf->get().text(); |
| const std::string name{id_leaf->get().text()}; |
| std::vector<Dimension> packed_dimensions = |
| GetDimensionsFromNodes(FindAllPackedDimensions(port_node)); |
| std::vector<Dimension> unpacked_dimensions = |
| GetDimensionsFromNodes(FindAllUnpackedDimensions(port_node)); |
| |
| Port::Direction direction; |
| if (dir_span == "input") { |
| direction = Port::Direction::kInput; |
| } else if (dir_span == "inout") { |
| direction = Port::Direction::kInout; |
| } else if (dir_span == "output") { |
| direction = Port::Direction::kOutput; |
| } else { |
| LOG(ERROR) << "Incorrect port direction"; |
| return; |
| } |
| |
| ports_.push_back({ |
| { |
| name, |
| {}, |
| std::move(packed_dimensions), |
| std::move(unpacked_dimensions), |
| }, |
| direction, |
| Port::Declaration::kDeclared, |
| dir_span.begin(), |
| }); |
| } |
| |
| bool AutoExpander::Module::DependsOn( |
| const Module *module, absl::flat_hash_set<const Module *> *visited) const { |
| const bool already_visited = !visited->insert(this).second; |
| if (already_visited) return false; |
| for (const Module *dependency : dependencies_) { |
| if (dependency == module) return true; |
| if (dependency->DependsOn(module, visited)) return true; |
| } |
| return false; |
| } |
| |
| absl::flat_hash_set<absl::string_view> AutoExpander::GetPortsListedBefore( |
| const Symbol &module, const absl::string_view::const_iterator it) const { |
| absl::flat_hash_set<absl::string_view> ports_before; |
| const auto all_ports = GetModulePortDeclarationList(module); |
| if (!all_ports) return {}; |
| |
| for (const SymbolPtr &port : all_ports->children()) { |
| if (port->Kind() == SymbolKind::kLeaf) continue; |
| const SyntaxTreeNode &port_node = SymbolCastToNode(*port); |
| const NodeEnum tag = NodeEnum(port_node.Tag().tag); |
| const SyntaxTreeLeaf *port_id_node = nullptr; |
| |
| if (tag == NodeEnum::kPortDeclaration) { |
| port_id_node = GetIdentifierFromPortDeclaration(port_node); |
| } else if (tag == NodeEnum::kPort) { |
| const SyntaxTreeNode *const port_ref_node = |
| GetPortReferenceFromPort(port_node); |
| if (port_ref_node) { |
| port_id_node = GetIdentifierFromPortReference(*port_ref_node); |
| } |
| } |
| if (!port_id_node) { |
| LOG(WARNING) << "Unhandled type of port declaration or port declaration " |
| "with no identifier. Ignoring"; |
| continue; |
| } |
| |
| const TokenInfo &port_id_token = port_id_node->get(); |
| if (port_id_token.text().end() <= it) { |
| ports_before.insert(port_id_token.text()); |
| } |
| } |
| return ports_before; |
| } |
| |
| absl::flat_hash_set<absl::string_view> AutoExpander::GetPortsConnectedBefore( |
| const Symbol &instance, const absl::string_view::const_iterator it) const { |
| absl::flat_hash_set<absl::string_view> ports_before; |
| for (const auto &port : FindAllActualNamedPort(instance)) { |
| const SyntaxTreeLeaf *const id_node = GetActualNamedPortName(*port.match); |
| if (!id_node) { |
| LOG(WARNING) << "Named port connection with no identifier? Ignoring"; |
| continue; |
| } |
| const TokenInfo &id_token = id_node->get(); |
| if (id_token.text().end() <= it) { |
| ports_before.insert(id_token.text()); |
| } |
| } |
| return ports_before; |
| } |
| |
| // Does a regex search in the span of the given symbol, returns match |
| std::optional<AutoExpander::Match> FindMatchInSymbol(const Symbol &symbol, |
| const RE2 &re) { |
| const absl::string_view symbol_span = StringSpanOfSymbol(symbol); |
| absl::string_view match; |
| absl::string_view comment; |
| if (RE2::PartialMatch(symbol_span, re, &match, &comment)) { |
| return AutoExpander::Match{ |
| .auto_span = {match.begin(), match.length()}, |
| .comment_span = {comment.begin(), comment.length()}}; |
| } |
| return std::nullopt; |
| } |
| |
| // Does a regex search in the span of the given symbol, returns matched span |
| std::optional<absl::string_view> FindSpanInSymbol(const Symbol &symbol, |
| const RE2 &re) { |
| const absl::string_view symbol_span = StringSpanOfSymbol(symbol); |
| absl::string_view match; |
| if (RE2::PartialMatch({symbol_span.data(), symbol_span.length()}, re, |
| &match)) { |
| return absl::string_view{match.begin(), match.length()}; |
| } |
| return std::nullopt; |
| } |
| |
| // Returns the deepest node that contains the given span |
| const Symbol *FindNodeContainingSpan(const Symbol &root, |
| const absl::string_view span) { |
| return FindLastSubtree(&root, [span](const Symbol &sym) { |
| auto sym_span = StringSpanOfSymbol(sym); |
| return span.begin() >= sym_span.begin() && sym_span.end() >= span.end(); |
| }); |
| } |
| |
| // Returns true if the given span is directly under the port declaration list |
| // (or the port paren group if there is no port declaration list) |
| bool IsSpanDirectlyUnderPortDeclarationList(const Symbol &port_parens, |
| const absl::string_view span) { |
| if (const Symbol *symbol = FindNodeContainingSpan(port_parens, span)) { |
| return symbol == &port_parens || |
| NodeEnum(symbol->Tag().tag) == NodeEnum::kPortDeclarationList; |
| } |
| return false; |
| } |
| |
| std::optional<AutoExpander::Expansion> AutoExpander::ExpandAutoarg( |
| const Module &module) const { |
| const SyntaxTreeNode *const port_parens = |
| GetModulePortParenGroup(module.Symbol()); |
| if (!ShouldExpand(AutoKind::kAutoarg)) return std::nullopt; |
| if (!port_parens) return std::nullopt; // No port paren group, so no AUTOARG |
| auto auto_span = FindSpanInSymbol(*port_parens, *autoarg_re_); |
| if (!auto_span) return std::nullopt; |
| auto replaced_span = FindSpanToReplace(*port_parens, *auto_span); |
| if (!replaced_span) return std::nullopt; |
| if (!IsSpanDirectlyUnderPortDeclarationList(*port_parens, *auto_span)) { |
| LOG(ERROR) << "Not expanding AUTOARG. Incorrect context"; |
| return std::nullopt; |
| } |
| |
| // Ports listed before the comment should not be redeclared |
| const auto predeclared_ports = |
| GetPortsListedBefore(module.Symbol(), auto_span->begin()); |
| |
| std::ostringstream new_text; |
| module.EmitNonAnsiPortList(new_text, "Inputs", [&](const Port &port) { |
| return port.direction == Port::Direction::kInput && |
| !predeclared_ports.contains(port.name); |
| }); |
| module.EmitNonAnsiPortList(new_text, "Inouts", [&](const Port &port) { |
| return port.direction == Port::Direction::kInout && |
| !predeclared_ports.contains(port.name); |
| }); |
| module.EmitNonAnsiPortList(new_text, "Outputs", [&](const Port &port) { |
| return port.direction == Port::Direction::kOutput && |
| !predeclared_ports.contains(port.name); |
| }); |
| |
| return Expansion{.replaced_span = *replaced_span, |
| .new_text = absl::StrCat(*auto_span, new_text.str())}; |
| } |
| |
| // Returns true if the given span is directly under the port actual list (or |
| // instance paren group if there is no port actual list) |
| bool IsSpanDirectlyUnderPortActualList(const Symbol &instance_parens, |
| const absl::string_view span) { |
| if (const Symbol *symbol = FindNodeContainingSpan(instance_parens, span)) { |
| return symbol == &instance_parens || |
| NodeEnum(symbol->Tag().tag) == NodeEnum::kPortActualList; |
| } |
| return false; |
| } |
| |
| std::optional<AutoExpander::Expansion> AutoExpander::ExpandAutoinst( |
| Module *module, const Symbol &instance, absl::string_view type_id) { |
| if (!ShouldExpand(AutoKind::kAutoinst)) return std::nullopt; |
| const SyntaxTreeNode *parens = GetParenGroupFromModuleInstantiation(instance); |
| |
| auto auto_span = FindSpanInSymbol(*parens, *autoinst_re_); |
| if (!auto_span) return std::nullopt; |
| auto replaced_span = FindSpanToReplace(*parens, *auto_span); |
| if (!replaced_span) return std::nullopt; |
| if (!IsSpanDirectlyUnderPortActualList(*parens, *auto_span)) { |
| LOG(ERROR) << "Not expanding AUTOINST. Incorrect context"; |
| return std::nullopt; |
| } |
| |
| const Symbol *const type_def = |
| symbol_table_handler_->FindDefinitionSymbol(type_id); |
| if (!type_def) { |
| LOG(ERROR) << "AUTOINST: No definition found for module type: " << type_id; |
| return std::nullopt; |
| } |
| if (NodeEnum(type_def->Tag().tag) != NodeEnum::kModuleDeclaration) { |
| LOG(ERROR) << "AUTOINST: Instance type " << type_id |
| << " is not a module, but a '" << NodeEnum(type_def->Tag().tag) |
| << "'"; |
| return std::nullopt; |
| } |
| if (!modules_.contains(type_id)) { |
| modules_.insert(std::make_pair(type_id, Module(*type_def))); |
| } |
| const Module &inst_module = modules_.at(type_id); |
| |
| // Find an AUTO_TEMPLATE that matches this instance |
| const verible::TokenInfo *instance_name_token = |
| GetModuleInstanceNameTokenInfoFromGateInstance(instance); |
| if (!instance_name_token) { |
| LOG(ERROR) << "AUTOINST: Instance with no name, aborting"; |
| return std::nullopt; |
| } |
| const absl::string_view instance_name = instance_name_token->text(); |
| const Template *const tmpl = module->GetAutoTemplate( |
| inst_module.Name(), instance_name, StringSpanOfSymbol(instance).begin()); |
| |
| // Ports connected before the AUTOINST comment should be ignored |
| const auto preconnected_ports = |
| GetPortsConnectedBefore(instance, auto_span->begin()); |
| |
| std::ostringstream new_text; |
| inst_module.EmitPortConnections( |
| new_text, instance_name, "Inputs", |
| [&](const Port &port) { |
| return port.direction == Port::Direction::kInput && |
| port.declaration != Port::Declaration::kUndeclared && |
| !preconnected_ports.contains(port.name); |
| }, |
| tmpl); |
| inst_module.EmitPortConnections( |
| new_text, instance_name, "Inouts", |
| [&](const Port &port) { |
| return port.direction == Port::Direction::kInout && |
| port.declaration != Port::Declaration::kUndeclared && |
| !preconnected_ports.contains(port.name); |
| }, |
| tmpl); |
| inst_module.EmitPortConnections( |
| new_text, instance_name, "Outputs", |
| [&](const Port &port) { |
| return port.direction == Port::Direction::kOutput && |
| port.declaration != Port::Declaration::kUndeclared && |
| !preconnected_ports.contains(port.name); |
| }, |
| tmpl); |
| |
| // The module's port connections need to be updated, as new ones may have |
| // been generated |
| inst_module.GenerateConnections( |
| instance_name, tmpl, [&](const Port &port, const Connection &connected) { |
| if (port.declaration == Port::Declaration::kUndeclared) return; |
| ConnectedInstance connection{.instance = instance_name, |
| .type = type_id}; |
| module->AddGeneratedConnection(connected.port_name, port.direction, |
| connection, port.packed_dimensions, |
| port.unpacked_dimensions); |
| }); |
| |
| return Expansion{.replaced_span = *replaced_span, |
| .new_text = absl::StrCat(*auto_span, new_text.str())}; |
| } |
| |
| std::optional<AutoExpander::Expansion> AutoExpander::ExpandAutoDeclarations( |
| const Module &module, const Match match, |
| const absl::string_view description, |
| const std::function<void(const Module &, std::ostream &)> &emit) const { |
| std::stringstream new_text; |
| new_text << match.comment_span << "\n// Beginning of automatic " |
| << description << '\n'; |
| const int64_t length_before_emit = new_text.tellp(); |
| emit(module, new_text); |
| if (length_before_emit == new_text.tellp()) { |
| if (match.auto_span != match.comment_span) { |
| return Expansion{.replaced_span = match.auto_span, |
| .new_text = std::string{match.comment_span}}; |
| } |
| return std::nullopt; |
| } |
| new_text << "// End of automatics"; |
| return Expansion{.replaced_span = match.auto_span, |
| .new_text = new_text.str()}; |
| } |
| |
| // Returns true if the span is directly under the module item list (or the |
| // module if there is no module item list) |
| bool IsSpanDirectlyUnderModule(const Symbol &module, |
| const absl::string_view span) { |
| if (const Symbol *symbol = FindNodeContainingSpan(module, span)) { |
| return symbol == &module || |
| NodeEnum(symbol->Tag().tag) == NodeEnum::kModuleItemList; |
| } |
| return false; |
| } |
| |
| std::optional<AutoExpander::Expansion> AutoExpander::ExpandAutoPorts( |
| Module *module, const std::optional<Match> match, |
| const Port::Direction direction) const { |
| if (!match) return std::nullopt; |
| const absl::string_view module_span = StringSpanOfSymbol(module->Symbol()); |
| const auto begin = match->auto_span.end(); |
| auto end = module_span.end(); |
| const SyntaxTreeNode *const port_parens = |
| GetModulePortParenGroup(module->Symbol()); |
| |
| const bool in_header = port_parens && IsSpanDirectlyUnderPortDeclarationList( |
| *port_parens, match->auto_span); |
| if (!in_header && |
| !IsSpanDirectlyUnderModule(module->Symbol(), match->auto_span)) { |
| LOG(ERROR) << "Not expanding AUTO ports. Incorrect context"; |
| return std::nullopt; |
| } |
| |
| if (port_parens) end = StringSpanOfSymbol(*port_parens).end(); |
| const bool last = !module->AnyPorts([begin, end](const Port &port) { |
| return port.it >= begin && port.it < end; |
| }); |
| |
| const PortDeclStyle style = |
| in_header ? last ? PortDeclStyle::kCommaSeparatorExceptLast |
| : PortDeclStyle::kCommaSeparator |
| : PortDeclStyle::kColonSeparator; |
| const absl::string_view description = direction == Port::Direction::kInput |
| ? "inputs (from autoinst inputs)" |
| : direction == Port::Direction::kInout |
| ? "inouts (from autoinst inouts)" |
| : "outputs (from autoinst outputs)"; |
| |
| if (!SpansOverlapping(match->auto_span, expand_span_)) return std::nullopt; |
| auto result = ExpandAutoDeclarations( |
| *module, *match, description, |
| [direction, style](const Module &module, std::ostream &output) { |
| module.EmitPortDeclarations( |
| output, style, [direction](const Port &port) { |
| return port.declaration == Port::Declaration::kUndeclared && |
| port.direction == direction; |
| }); |
| }); |
| |
| module->ForEachPort([direction](Port &port) { |
| if (port.declaration == Port::Declaration::kUndeclared && |
| port.direction == direction) { |
| port.declaration = Port::Declaration::kAutogenerated; |
| } |
| }); |
| return result; |
| } |
| |
| std::optional<AutoExpander::Expansion> AutoExpander::ExpandAutowire( |
| const Module &module) const { |
| if (!ShouldExpand(AutoKind::kAutowire)) return std::nullopt; |
| const auto match = FindMatchInSymbol(module.Symbol(), *autowire_re_); |
| if (!match) return std::nullopt; |
| if (!SpansOverlapping(match->auto_span, expand_span_)) { |
| return std::nullopt; |
| } |
| if (!IsSpanDirectlyUnderModule(module.Symbol(), match->auto_span)) { |
| LOG(ERROR) << "Not expanding AUTOWIRE. Incorrect context"; |
| return std::nullopt; |
| } |
| return ExpandAutoDeclarations( |
| module, *match, "wires (for undeclared instantiated-module outputs)", |
| [match](const Module &module, std::ostream &output) { |
| module.EmitUndeclaredWireDeclarations(output, match->auto_span); |
| }); |
| } |
| |
| std::optional<AutoExpander::Expansion> AutoExpander::ExpandAutoreg( |
| const Module &module) const { |
| if (!ShouldExpand(AutoKind::kAutoreg)) return std::nullopt; |
| const auto match = FindMatchInSymbol(module.Symbol(), *autoreg_re_); |
| if (!match) return std::nullopt; |
| if (!SpansOverlapping(match->auto_span, expand_span_)) { |
| return std::nullopt; |
| } |
| if (!IsSpanDirectlyUnderModule(module.Symbol(), match->auto_span)) { |
| LOG(ERROR) << "Not expanding AUTOREG. Incorrect context"; |
| return std::nullopt; |
| } |
| return ExpandAutoDeclarations( |
| module, *match, "regs (for this module's undeclared outputs)", |
| [match](const Module &module, std::ostream &output) { |
| module.EmitUnconnectedOutputRegDeclarations(output, match->auto_span); |
| }); |
| } |
| |
| std::optional<AutoExpander::Match> AutoExpander::FindMatchAndErasePorts( |
| AutoExpander::Module *module, const AutoKind kind, const RE2 &re) { |
| if (!ShouldExpand(kind)) return std::nullopt; |
| const auto match = FindMatchInSymbol(module->Symbol(), re); |
| if (match) { |
| if (SpansOverlapping(StringSpanOfSymbol(module->Symbol()), |
| match->auto_span)) { |
| module->ErasePortsIf([match](const AutoExpander::Port &port) { |
| return port.it >= match->auto_span.begin() && |
| port.it < match->auto_span.end(); |
| }); |
| } |
| } |
| return match; |
| } |
| |
| // Constructs a function that assigns the match's source location to undeclared |
| // ports of the specified direction |
| auto SetUndeclaredPortLocations(AutoExpander::Module *module, |
| const AutoExpander::Match match, |
| const AutoExpander::Port::Direction direction) { |
| const auto it = match.auto_span.begin(); |
| return [it, direction](AutoExpander::Port &port) { |
| if (port.declaration == AutoExpander::Port::Declaration::kUndeclared && |
| port.direction == direction) { |
| port.it = it; |
| } |
| }; |
| } |
| |
| std::vector<AutoExpander::Expansion> AutoExpander::Expand() { |
| std::vector<Expansion> expansions; |
| if (!text_structure_.SyntaxTree()) { |
| LOG(ERROR) |
| << "Cannot perform AUTO expansion: failed to retrieve a syntax tree"; |
| return {}; |
| } |
| std::vector<Module *> buffer_modules; // Ordered list of all modules |
| // in the buffer being modified |
| for (const auto &mod_decl : |
| FindAllModuleDeclarations(*text_structure_.SyntaxTree())) { |
| Module module(*mod_decl.match); |
| buffer_modules.push_back( |
| &modules_.insert(std::make_pair(module.Name(), std::move(module))) |
| .first->second); |
| } |
| for (auto module : buffer_modules) { |
| module->RetrieveDependencies(modules_); |
| } |
| // Sort modules in the buffer based on a dependency graph, so that AUTOs are |
| // expanded in order |
| std::sort(buffer_modules.begin(), buffer_modules.end(), |
| [](const Module *left, const Module *right) { |
| return right->DependsOn(left); |
| }); |
| for (Module *const module : buffer_modules) { |
| // Ports declared in AUTOINPUT/AUTOINOUT/AUTOOUTPUT must be removed from |
| // the module, as they should be regenerated every time (in case they get |
| // removed or their names change) |
| const auto autoinput_match = |
| FindMatchAndErasePorts(module, AutoKind::kAutoinput, *autoinput_re_); |
| const auto autoinout_match = |
| FindMatchAndErasePorts(module, AutoKind::kAutoinout, *autoinout_re_); |
| const auto autooutput_match = |
| FindMatchAndErasePorts(module, AutoKind::kAutooutput, *autooutput_re_); |
| // Do AUTOINST expansion |
| module->RetrieveAutoTemplates(); |
| for (const auto &data : FindAllDataDeclarations(module->Symbol())) { |
| const Symbol *const type_id_node = |
| GetTypeIdentifierFromDataDeclaration(*data.match); |
| // Some data declarations do not have a type id, ignore those |
| if (!type_id_node) continue; |
| const absl::string_view type_id = StringSpanOfSymbol(*type_id_node); |
| for (const auto &instance : FindAllGateInstances(*data.match)) { |
| if (const auto expansion = |
| ExpandAutoinst(module, *instance.match, type_id)) { |
| expansions.push_back(*expansion); |
| } |
| } |
| } |
| // Set AUTO port locations. This has to be done before any port expansions |
| // so that ExpandAutoPorts() has the correct locations. |
| if (autoinput_match) { |
| module->ForEachPort(SetUndeclaredPortLocations(module, *autoinput_match, |
| Port::Direction::kInput)); |
| } |
| if (autoinout_match) { |
| module->ForEachPort(SetUndeclaredPortLocations(module, *autoinout_match, |
| Port::Direction::kInout)); |
| } |
| if (autooutput_match) { |
| module->ForEachPort(SetUndeclaredPortLocations(module, *autooutput_match, |
| Port::Direction::kOutput)); |
| } |
| // Expand AUTO port declarations |
| if (const auto expansion = |
| ExpandAutoPorts(module, autoinput_match, Port::Direction::kInput)) { |
| expansions.push_back(*expansion); |
| } |
| if (const auto expansion = |
| ExpandAutoPorts(module, autoinout_match, Port::Direction::kInout)) { |
| expansions.push_back(*expansion); |
| } |
| if (const auto expansion = ExpandAutoPorts(module, autooutput_match, |
| Port::Direction::kOutput)) { |
| expansions.push_back(*expansion); |
| } |
| // Expand AUTO wire/reg declarations |
| if (const auto expansion = ExpandAutowire(*module)) { |
| expansions.push_back(*expansion); |
| } |
| if (const auto expansion = ExpandAutoreg(*module)) { |
| expansions.push_back(*expansion); |
| } |
| module->SortPortsByLocation(); // Ports need to be sorted by location in |
| // source file to ensure AUTOARG stability |
| // AUTOARG |
| if (const auto expansion = ExpandAutoarg(*module)) { |
| expansions.push_back(*expansion); |
| } |
| } |
| return expansions; |
| } |
| |
| absl::flat_hash_set<AutoKind> AutoExpander::FindAutoKinds() { |
| absl::flat_hash_set<AutoKind> kinds; |
| absl::string_view search_span = expand_span_; |
| absl::string_view auto_str; |
| while (RE2::FindAndConsume(&search_span, *auto_re_, &auto_str)) { |
| if (auto_str == "AUTOARG") { |
| kinds.insert(AutoKind::kAutoarg); |
| } else if (auto_str == "AUTOINST") { |
| kinds.insert(AutoKind::kAutoinst); |
| } else if (auto_str == "AUTOINPUT") { |
| kinds.insert(AutoKind::kAutoinput); |
| } else if (auto_str == "AUTOINOUT") { |
| kinds.insert(AutoKind::kAutoinout); |
| } else if (auto_str == "AUTOOUTPUT") { |
| kinds.insert(AutoKind::kAutooutput); |
| } else if (auto_str == "AUTOWIRE") { |
| kinds.insert(AutoKind::kAutowire); |
| } else if (auto_str == "AUTOREG") { |
| kinds.insert(AutoKind::kAutoreg); |
| } else { |
| LOG(ERROR) << "Invalid AUTO comment string"; |
| } |
| } |
| return kinds; |
| } |
| |
| std::optional<absl::string_view> AutoExpander::FindSpanToReplace( |
| const Symbol &symbol, const absl::string_view auto_span) const { |
| const absl::string_view symbol_span = StringSpanOfSymbol(symbol); |
| const size_t replaced_length = static_cast<size_t>( |
| std::distance(auto_span.begin(), symbol_span.end() - 1)); |
| const absl::string_view replaced_span{auto_span.begin(), replaced_length}; |
| if (!SpansOverlapping(replaced_span, expand_span_)) { |
| return std::nullopt; |
| } |
| return replaced_span; |
| } |
| |
| // Returns an iterator pointing at the next expansion |
| // not overlapping with *start |
| // TODO: It's a template for readability, use C++20 concepts here |
| template <typename It> |
| It GetNextNonOverlappingExpansion(It start, It end) { |
| It next = start; |
| do { |
| ++next; |
| } while (next != end && |
| SpansOverlapping(start->replaced_span, next->replaced_span)); |
| return next; |
| } |
| |
| // Applies the given AUTO expansions to the text structure, returning the |
| // resulting text |
| std::string ApplyExpansions( |
| const TextStructureView &text_structure, |
| const std::vector<AutoExpander::Expansion> &expansions) { |
| std::string text; |
| auto begin = text_structure.Contents().begin(); |
| for (auto it = expansions.begin(), next = it; it != expansions.end(); |
| it = next) { |
| next = GetNextNonOverlappingExpansion(it, expansions.end()); |
| // If the next expansion does not overlap with ours, we can expand |
| if (next == it + 1) { |
| const AutoExpander::Expansion &expansion = *it; |
| text.append(begin, expansion.replaced_span.begin()); |
| text.append(expansion.new_text); |
| begin = expansion.replaced_span.end(); |
| } else { |
| // TODO: Notify the user about this |
| LOG(ERROR) << "Ignoring " << std::distance(it, next) |
| << " overlapping AUTO expansions"; |
| } |
| } |
| text.append(begin, text_structure.Contents().end()); |
| return text; |
| } |
| |
| // Converts AutoExpander::Expansion structs to LSP TextEdits and performs |
| // formatting on them if possible |
| std::vector<TextEdit> ConvertAutoExpansionsToFormattedTextEdits( |
| const TextStructureView &text_structure, |
| std::vector<AutoExpander::Expansion> expansions) { |
| std::sort(expansions.begin(), expansions.end(), |
| [](const AutoExpander::Expansion &first, |
| const AutoExpander::Expansion &second) { |
| return first.replaced_span.begin() < second.replaced_span.begin(); |
| }); |
| |
| // To format expansions, we need to apply them to the source. Then we only |
| // call the formatter once, but with formatting ranges limited to |
| // autogenerated code. The result is a single TextEdit that replaces the |
| // entire file. This is orders of magnitude faster than formatting individual |
| // TextEdits. |
| std::string text = ApplyExpansions(text_structure, expansions); |
| |
| VerilogAnalyzer analyzer(text, "<autoexpand-reformat>"); |
| const absl::Status analyze_status = analyzer.Analyze(); |
| if (!analyze_status.ok()) { |
| LOG(ERROR) << "AUTO expansion produced invalid syntax. Aborting."; |
| return {}; |
| } |
| |
| FormatStyle format_style; |
| InitializeFromFlags(&format_style); |
| |
| int64_t line_diff_acc = 0; |
| verible::LineNumberSet lines; |
| for (const AutoExpander::Expansion &expansion : expansions) { |
| const LineColumnRange range = |
| text_structure.GetRangeForText(expansion.replaced_span); |
| // Formatter expects 1-indexed lines, hence the +1 |
| const int start_line = |
| static_cast<int>(range.start.line + line_diff_acc + 1); |
| const int new_text_line_count = |
| std::count(expansion.new_text.begin(), expansion.new_text.end(), '\n') + |
| 1; |
| const int end_line = static_cast<int>(start_line + new_text_line_count); |
| const int line_diff = |
| range.start.line + new_text_line_count - range.end.line - 1; |
| line_diff_acc += line_diff; |
| lines.Add({start_line, end_line}); |
| } |
| |
| std::string formatted; |
| const absl::Status format_status = |
| FormatVerilog(analyzer.Data(), "", format_style, &formatted, lines); |
| |
| std::string &new_text = text; |
| if (format_status.ok()) { |
| new_text = formatted; |
| } else { |
| LOG(ERROR) << "Failed to format AUTO expanded code"; |
| } |
| |
| const LineColumn linecol = |
| text_structure.GetRangeForText(text_structure.Contents()).end; |
| return {{.range = |
| { |
| .start = {.line = 0, .character = 0}, |
| .end = {.line = linecol.line, .character = linecol.column}, |
| }, |
| .newText = std::move(new_text)}}; |
| } |
| |
| } // namespace |
| |
| std::vector<CodeAction> GenerateAutoExpandCodeActions( |
| SymbolTableHandler *symbol_table_handler, |
| const BufferTracker *const tracker, const CodeActionParams &p) { |
| Interval<size_t> line_range{static_cast<size_t>(p.range.start.line), |
| static_cast<size_t>(p.range.end.line)}; |
| if (!tracker) return {}; |
| const auto current = tracker->current(); |
| if (!current) return {}; // Can only expand if we have latest version |
| const TextStructureView &text_structure = current->parser().Data(); |
| AutoExpander range_expander(text_structure, symbol_table_handler, line_range); |
| const auto &auto_kinds = range_expander.FindAutoKinds(); |
| if (auto_kinds.empty()) return {}; |
| |
| AutoExpander full_expander(text_structure, symbol_table_handler); |
| const auto &expansions_full = full_expander.Expand(); |
| if (expansions_full.empty()) return {}; |
| std::vector<CodeAction> result; |
| result.emplace_back(CodeAction{ |
| .title = "Expand all AUTOs in file", |
| .kind = "refactor.rewrite", |
| .edit = {.changes = {{p.textDocument.uri, |
| ConvertAutoExpansionsToFormattedTextEdits( |
| text_structure, expansions_full)}}}, |
| }); |
| |
| const auto &expansions_range = range_expander.Expand(); |
| if (expansions_range.empty() || |
| expansions_range.size() == expansions_full.size()) { |
| return result; |
| } |
| result.push_back({ |
| .title = expansions_range.size() > 1 |
| ? "Expand all AUTOs in selected range" |
| : "Expand this AUTO", |
| .kind = "refactor.rewrite", |
| .edit = {.changes = {{p.textDocument.uri, |
| ConvertAutoExpansionsToFormattedTextEdits( |
| text_structure, expansions_range)}}}, |
| }); |
| |
| AutoExpander kind_expander(text_structure, symbol_table_handler, auto_kinds); |
| const auto &expansions_kind = kind_expander.Expand(); |
| if (expansions_kind.empty() || |
| expansions_kind.size() == expansions_range.size()) { |
| return result; |
| } |
| result.push_back({ |
| .title = expansions_range.size() > 1 |
| ? "Expand all AUTOs of same kinds as selected" |
| : "Expand all AUTOs of same kind as this one", |
| .kind = "refactor.rewrite", |
| .edit = {.changes = {{p.textDocument.uri, |
| ConvertAutoExpansionsToFormattedTextEdits( |
| text_structure, expansions_kind)}}}, |
| }); |
| return result; |
| } |
| |
| } // namespace verilog |