Skip to content

Commit

Permalink
[slang] Add an inputs/edges combination coverage check for udp
Browse files Browse the repository at this point in the history
  • Loading branch information
Yan Churkin committed Aug 20, 2024
1 parent 53a547e commit 1a3971f
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 34 deletions.
2 changes: 2 additions & 0 deletions include/slang/ast/symbols/MemberSymbols.h
Original file line number Diff line number Diff line change
Expand Up @@ -261,12 +261,14 @@ class SLANG_EXPORT PrimitiveSymbol : public Symbol, public Scope {
std::string_view inputs;
char state = 0;
char output = 0;
bool isEdgeSensitive = false;
};

std::span<const PrimitivePortSymbol* const> ports;
std::span<const TableEntry> table;
const ConstantValue* initVal = nullptr;
bool isSequential = false;
bool isEdgeSensitive = false;
enum PrimitiveKind { UserDefined, Fixed, NInput, NOutput, BiDiSwitch } primitiveKind;

PrimitiveSymbol(Compilation& compilation, std::string_view name, SourceLocation loc,
Expand Down
2 changes: 2 additions & 0 deletions scripts/diagnostics.txt
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,7 @@ error BindUnderBind "cannot bind an instance underneath the scope of another bin
error UdpWrongInputCount "incorrect number of input fields in table row; have {} but expect {}"
error UdpDupDiffOutput "primitive table row duplicates a set of inputs with a different specified output value"
error UdpAllX "primitive table row has all 'x' inputs so its output must also be 'x'"
error UdpEdgeSenseMiss "if the behavior of the UDP is sensitive to edges of any input, the desired output state shall be specified for all edges of all inputs."
error SubroutinePortInitializer "subroutine port declaration cannot contain an initializer"
error CheckerPortDirectionType "checker ports with an explicit direction declared must also declare a type"
error CheckerOutputBadType "checker output ports cannot be of type 'untyped', 'sequence', or 'property'"
Expand Down Expand Up @@ -542,6 +543,7 @@ warning warning-task WarningTask "$warning encountered{}"
note InfoTask "$info encountered{}"
note NoteComparisonReduces "comparison reduces to ({} {} {})"
note NoteCommonAncestor "common ancestor here violates that constraint"
note NoteUdpEdgeSenseMiss "missed desired output for '{}' edge-sensitive input specification"
warning explicit-static StaticInitializerMustBeExplicit "initializing a static variable in a procedural context requires an explicit 'static' keyword"
warning case-gen-dup CaseGenerateDup "more than one case generate block matches the value {}"
warning case-gen-none CaseGenerateNoBlock "no case generate expression matches the value {}"
Expand Down
127 changes: 114 additions & 13 deletions source/ast/symbols/MemberSymbols.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -767,22 +767,17 @@ void PrimitivePortSymbol::serializeTo(ASTSerializer& serializer) const {
// Each 'bit' is one input value in the row.
class BitTrie {
public:
// Tries to insert the row. Returns nullopt if the row is
// successfully inserted, and a set of the previously
// inserted rows with the same bit pattern if a collision
// is found.
template<typename TAllocator>
std::optional<std::set<const UdpEntrySyntax*>> insert(const UdpEntrySyntax& syntax,
std::span<const char> inputs,
char stateChar, TAllocator& allocator) {
std::optional<std::set<const UdpEntrySyntax*>> find(BitTrie*& primaryNode,
std::span<const char> inputs,
char stateChar, TAllocator& allocator) {
SmallVector<BitTrie*> nodes;
BitTrie* primary = this;
nodes.push_back(this);

auto advance = [&](char c) {
SmallVector<BitTrie*> nextNodes;
for (auto node : nodes)
node->nextNodesFor(c, nextNodes, primary, allocator);
node->nextNodesFor(c, nextNodes, primaryNode, allocator);
nodes = std::move(nextNodes);
};

Expand Down Expand Up @@ -834,6 +829,25 @@ class BitTrie {
eSyntaxes.insert(node->entry);
}

// If we have an empty syntaxes set we saw an error somewhere,
// in which case don't insert. Otherwise we've found the
// correct place to insert, pointed to by the primary.
if (eSyntaxes.empty())
return std::nullopt;

return eSyntaxes;
}

// Tries to insert the row. Returns nullopt if the row is
// successfully inserted, and a set of the previously
// inserted rows with the same bit pattern if a collision
// is found.
template<typename TAllocator>
std::optional<std::set<const UdpEntrySyntax*>> insert(const UdpEntrySyntax& syntax,
std::span<const char> inputs,
char stateChar, TAllocator& allocator) {
BitTrie* primary = this;
auto founded = find(primary, inputs, stateChar, allocator);
// Always store so as not to miss info it in case of possible overlap.
// If the primary->entry already has a value,
// then rewriting will not spoil anything,
Expand All @@ -843,9 +857,9 @@ class BitTrie {
// If we have an empty syntaxes set we saw an error somewhere,
// in which case don't insert. Otherwise we've found the
// correct place to insert, pointed to by the primary.
if (eSyntaxes.empty())
if (!founded.has_value())
return std::nullopt;
return eSyntaxes;
return founded;
}

private:
Expand Down Expand Up @@ -947,8 +961,10 @@ static void createTableRow(const Scope& scope, const UdpEntrySyntax& syntax,
SmallVector<char> inputs;
size_t numInputs = 0;
bool allX = true;
bool isEdgeSensitive = false;
for (auto input : syntax.inputs) {
if (input->kind == SyntaxKind::UdpEdgeField) {
isEdgeSensitive = true;
auto& edge = input->as<UdpEdgeFieldSyntax>();
inputs.push_back('(');

Expand Down Expand Up @@ -988,6 +1004,18 @@ static void createTableRow(const Scope& scope, const UdpEntrySyntax& syntax,
auto tok = input->as<UdpSimpleFieldSyntax>().field;
for (auto c : tok.rawText()) {
auto d = charToLower(c);
switch (d) {
case '*':
case 'r':
case 'f':
case 'p':
case 'n':
isEdgeSensitive = true;
break;
default:
break;
}

inputs.push_back(d);
numInputs++;
allX &= d == 'x';
Expand Down Expand Up @@ -1093,7 +1121,8 @@ static void createTableRow(const Scope& scope, const UdpEntrySyntax& syntax,
}

auto inputSpan = inputs.copy(scope.getCompilation());
table.push_back({std::string_view(inputSpan.data(), inputSpan.size()), stateChar, outputChar});
table.push_back({std::string_view(inputSpan.data(), inputSpan.size()), stateChar, outputChar,
isEdgeSensitive});
}

PrimitiveSymbol& PrimitiveSymbol::fromSyntax(const Scope& scope,
Expand Down Expand Up @@ -1336,10 +1365,82 @@ PrimitiveSymbol& PrimitiveSymbol::fromSyntax(const Scope& scope,
BumpAllocator alloc;
PoolAllocator<BitTrie> trieAlloc(alloc);
SmallVector<TableEntry> table;
for (auto entry : syntax.body->entries)
for (auto entry : syntax.body->entries) {
createTableRow(scope, *entry, table, ports.size(), trie, trieAlloc);
if (!table.empty() && !prim->isEdgeSensitive)
prim->isEdgeSensitive = table.back().isEdgeSensitive;
}

prim->table = table.copy(comp);

// Check that if the behavior of the UDP is sensitive to edges of any input, the desired
// output state shall be specified for all edges of all inputs.
if (prim->isEdgeSensitive && !prim->table.empty()) {
auto portSize = ports.size();
auto numInput = (portSize >= 2) ? portSize - 1 : portSize;
// All possible clock edges
const static std::set<std::string_view> edgeScope = {"(01)", "(10)", "(0x)",
"(x0)", "(1x)", "(x1)"};
// Row size except single edge-inpute
std::vector<char> initialState(numInput - 1, '0');
std::vector<char> state = initialState;
std::vector<char> endState(numInput - 1, 'x');
auto incrementState = [](std::vector<char>& state) {
for (size_t i = 0; i < state.size(); ++i) {
if (state.at(i) == '0') {
state.at(i) = '1';
break;
}

if (state.at(i) == '1') {
state.at(i) = 'x';
break;
}

state.at(i) = '0';
}
};
Diagnostic* diag = nullptr;
// Try to find the missing combinations by completely enumerating all possible table
// rows
for (size_t inInd = 0; inInd < numInput; ++inInd) {
bool next = true;
while (next) {
next = (state != endState);
for (auto& eS : edgeScope) {
SmallVector<char> inputTest;
if (inInd != 0)
inputTest.append(state.begin(), state.begin() + long(inInd));
inputTest.append(eS.begin(), eS.end());
inputTest.append(state.begin() + long(inInd), state.end());
BitTrie* triePtr = &trie;
if (!trie.find(triePtr, inputTest, '?', trieAlloc).has_value()) {
if (!diag) {
diag = &scope.addDiag(diag::UdpEdgeSenseMiss, prim->location);
}
else {
std::string noteStr;
bool nextSplit = false;
for (auto c : inputTest) {
if (c == ')') {
nextSplit = true;
noteStr += c;
continue;
}

if (nextSplit)
noteStr += ' ';
noteStr += c;
}
diag->addNote(diag::NoteUdpEdgeSenseMiss, prim->location)
<< noteStr;
}
}
}
incrementState(state);
}
}
}
}

prim->ports = ports.copy(comp);
Expand Down
Loading

0 comments on commit 1a3971f

Please sign in to comment.