Skip to content

Commit

Permalink
[clang-format] Put ports on separate lines in Verilog module headers
Browse files Browse the repository at this point in the history
New:
```
module mh1
    (input var int in1,
     input var in2, in3,
     output tagged_st out);
endmodule
```

Old:
```
module mh1
    (input var int in1, input var in2, in3, output tagged_st out);
endmodule
```

`getNextNonComment` was modified to return a non-const pointer because
we needed to use it that way in `verilogGroupDecl`.

The comment on line 2626 was a typo.  We corrected it while modifying
the function.

Reviewed By: MyDeveloperDay

Differential Revision: https://reviews.llvm.org/D143825
  • Loading branch information
eywdck2l committed Feb 20, 2023
1 parent 55900a0 commit 6e473ae
Show file tree
Hide file tree
Showing 3 changed files with 360 additions and 1 deletion.
25 changes: 25 additions & 0 deletions clang/lib/Format/FormatToken.h
Expand Up @@ -152,8 +152,12 @@ namespace format {
TYPE(VerilogDimensionedTypeName) \
/* for the base in a number literal, not including the quote */ \
TYPE(VerilogNumberBase) \
/* like `(strong1, pull0)` */ \
TYPE(VerilogStrength) \
/* Things inside the table in user-defined primitives. */ \
TYPE(VerilogTableItem) \
/* those that separate ports of different types */ \
TYPE(VerilogTypeComma) \
TYPE(Unknown)

/// Determines the semantic type of a syntactic token, e.g. whether "<" is a
Expand Down Expand Up @@ -1792,6 +1796,27 @@ struct AdditionalKeywords {
kw_input, kw_output, kw_sequence)));
}

bool isVerilogQualifier(const FormatToken &Tok) const {
switch (Tok.Tok.getKind()) {
case tok::kw_extern:
case tok::kw_signed:
case tok::kw_static:
case tok::kw_unsigned:
case tok::kw_virtual:
return true;
case tok::identifier:
return Tok.isOneOf(
kw_let, kw_var, kw_ref, kw_automatic, kw_bins, kw_coverpoint,
kw_ignore_bins, kw_illegal_bins, kw_inout, kw_input, kw_interconnect,
kw_local, kw_localparam, kw_output, kw_parameter, kw_pure, kw_rand,
kw_randc, kw_scalared, kw_specparam, kw_tri, kw_tri0, kw_tri1,
kw_triand, kw_trior, kw_trireg, kw_uwire, kw_vectored, kw_wand,
kw_wildcard, kw_wire, kw_wor);
default:
return false;
}
}

private:
/// The JavaScript keywords beyond the C++ keyword set.
std::unordered_set<IdentifierInfo *> JsExtraKeywords;
Expand Down
159 changes: 158 additions & 1 deletion clang/lib/Format/TokenAnnotator.cpp
Expand Up @@ -2596,8 +2596,21 @@ class ExpressionParser {
FormatToken *Start = Current;
FormatToken *LatestOperator = nullptr;
unsigned OperatorIndex = 0;
// The first name of the current type in a port list.
FormatToken *VerilogFirstOfType = nullptr;

while (Current) {
// In Verilog ports in a module header that don't have a type take the
// type of the previous one. For example,
// module a(output b,
// c,
// output d);
// In this case there need to be fake parentheses around b and c.
if (Style.isVerilog() && Precedence == prec::Comma) {
VerilogFirstOfType =
verilogGroupDecl(VerilogFirstOfType, LatestOperator);
}

// Consume operators with higher precedence.
parse(Precedence + 1);

Expand All @@ -2610,7 +2623,7 @@ class ExpressionParser {
Start = Current;
}

// At the end of the line or when an operator with higher precedence is
// At the end of the line or when an operator with lower precedence is
// found, insert fake parenthesis and return.
if (!Current ||
(Current->closesScope() &&
Expand Down Expand Up @@ -2647,6 +2660,10 @@ class ExpressionParser {
}
}

// Group variables of the same type.
if (Style.isVerilog() && Precedence == prec::Comma && VerilogFirstOfType)
addFakeParenthesis(VerilogFirstOfType, prec::Comma);

if (LatestOperator && (Current || Precedence > 0)) {
// The requires clauses do not neccessarily end in a semicolon or a brace,
// but just go over to struct/class or a function declaration, we need to
Expand Down Expand Up @@ -2782,6 +2799,140 @@ class ExpressionParser {
}
}

// Add fake parenthesis around declarations of the same type for example in a
// module prototype. Return the first port / variable of the current type.
FormatToken *verilogGroupDecl(FormatToken *FirstOfType,
FormatToken *PreviousComma) {
if (!Current)
return nullptr;

FormatToken *Start = Current;

// Skip attributes.
while (Start->startsSequence(tok::l_paren, tok::star)) {
if (!(Start = Start->MatchingParen) ||
!(Start = Start->getNextNonComment())) {
return nullptr;
}
}

FormatToken *Tok = Start;

if (Tok->is(Keywords.kw_assign))
Tok = Tok->getNextNonComment();

// Skip any type qualifiers to find the first identifier. It may be either a
// new type name or a variable name. There can be several type qualifiers
// preceding a variable name, and we can not tell them apart by looking at
// the word alone since a macro can be defined as either a type qualifier or
// a variable name. Thus we use the last word before the dimensions instead
// of the first word as the candidate for the variable or type name.
FormatToken *First = nullptr;
while (Tok) {
FormatToken *Next = Tok->getNextNonComment();

if (Tok->is(tok::hash)) {
// Start of a macro expansion.
First = Tok;
Tok = Next;
if (Tok)
Tok = Tok->getNextNonComment();
} else if (Tok->is(tok::hashhash)) {
// Concatenation. Skip.
Tok = Next;
if (Tok)
Tok = Tok->getNextNonComment();
} else if ((Keywords.isVerilogQualifier(*Tok) ||
Keywords.isVerilogIdentifier(*Tok))) {
First = Tok;
Tok = Next;
// The name may have dots like `interface_foo.modport_foo`.
while (Tok && Tok->isOneOf(tok::period, tok::coloncolon) &&
(Tok = Tok->getNextNonComment())) {
if (Keywords.isVerilogIdentifier(*Tok))
Tok = Tok->getNextNonComment();
}
} else if (!Next) {
Tok = nullptr;
} else if (Tok->is(tok::l_paren)) {
// Make sure the parenthesized list is a drive strength. Otherwise the
// statement may be a module instantiation in which case we have already
// found the instance name.
if (Next->isOneOf(
Keywords.kw_highz0, Keywords.kw_highz1, Keywords.kw_large,
Keywords.kw_medium, Keywords.kw_pull0, Keywords.kw_pull1,
Keywords.kw_small, Keywords.kw_strong0, Keywords.kw_strong1,
Keywords.kw_supply0, Keywords.kw_supply1, Keywords.kw_weak0,
Keywords.kw_weak1)) {
Tok->setType(TT_VerilogStrength);
Tok = Tok->MatchingParen;
if (Tok) {
Tok->setType(TT_VerilogStrength);
Tok = Tok->getNextNonComment();
}
} else {
break;
}
} else if (Tok->is(tok::hash)) {
if (Next->is(tok::l_paren))
Next = Next->MatchingParen;
if (Next)
Tok = Next->getNextNonComment();
} else {
break;
}
}

// Find the second identifier. If it exists it will be the name.
FormatToken *Second = nullptr;
// Dimensions.
while (Tok && Tok->is(tok::l_square) && (Tok = Tok->MatchingParen))
Tok = Tok->getNextNonComment();
if (Tok && (Tok->is(tok::hash) || Keywords.isVerilogIdentifier(*Tok)))
Second = Tok;

// If the second identifier doesn't exist and there are qualifiers, the type
// is implied.
FormatToken *TypedName = nullptr;
if (Second) {
TypedName = Second;
if (First && First->is(TT_Unknown))
First->setType(TT_VerilogDimensionedTypeName);
} else if (First != Start) {
// If 'First' is null, then this isn't a declaration, 'TypedName' gets set
// to null as intended.
TypedName = First;
}

if (TypedName) {
// This is a declaration with a new type.
if (TypedName->is(TT_Unknown))
TypedName->setType(TT_StartOfName);
// Group variables of the previous type.
if (FirstOfType && PreviousComma) {
PreviousComma->setType(TT_VerilogTypeComma);
addFakeParenthesis(FirstOfType, prec::Comma, PreviousComma->Previous);
}

FirstOfType = TypedName;

// Don't let higher precedence handle the qualifiers. For example if we
// have:
// parameter x = 0
// We skip `parameter` here. This way the fake parentheses for the
// assignment will be around `x = 0`.
while (Current && Current != FirstOfType) {
if (Current->opensScope()) {
next();
parse();
}
next();
}
}

return FirstOfType;
}

const FormatStyle &Style;
const AdditionalKeywords &Keywords;
const AnnotatedLine &Line;
Expand Down Expand Up @@ -4213,6 +4364,9 @@ bool TokenAnnotator::spaceRequiredBefore(const AnnotatedLine &Line,
// Add space in attribute like `(* ASYNC_REG = "TRUE" *)`.
if (Left.endsSequence(tok::star, tok::l_paren) && Right.is(tok::identifier))
return true;
// Add space before drive strength like in `wire (strong1, pull0)`.
if (Right.is(tok::l_paren) && Right.is(TT_VerilogStrength))
return true;
}
if (Left.is(TT_ImplicitStringLiteral))
return Right.hasWhitespaceBefore();
Expand Down Expand Up @@ -4524,6 +4678,9 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line,
return true;
}
} else if (Style.isVerilog()) {
// Break between ports of different types.
if (Left.is(TT_VerilogTypeComma))
return true;
// Break after labels. In Verilog labels don't have the 'case' keyword, so
// it is hard to identify them in UnwrappedLineParser.
if (!Keywords.isVerilogBegin(Right) && Keywords.isVerilogEndOfLabel(Left))
Expand Down

0 comments on commit 6e473ae

Please sign in to comment.