Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[slang][lang-compliance] Introduce nondegeneracy check #998

Merged

Conversation

likeamahoney
Copy link
Contributor

Based on 16.12.22 SystemVerilog LRM section and partially (F.4.3/F.5.2/F.5.5).

Also Ben Cohen's paper about nondegeneracy understanding from here.

Complex sequences are not often used in industrial/open source designs, but I think these checks will help to verify restrictions from 16.12.22 in most cases.

I written about ~300 lines of sv tests but i need help with more complex test cases but if anyone could also come up with more complex tests to test heuristics, that would be nice.

@likeamahoney likeamahoney changed the title [slang] Introduce nondegenerancy check [slang] Introduce nondegeneracy check May 17, 2024
Copy link

codecov bot commented May 17, 2024

Codecov Report

Attention: Patch coverage is 74.46043% with 71 lines in your changes are missing coverage. Please review.

Project coverage is 94.14%. Comparing base (b1354ad) to head (a4b2714).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #998      +/-   ##
==========================================
- Coverage   94.24%   94.14%   -0.10%     
==========================================
  Files         191      191              
  Lines       47344    47568     +224     
==========================================
+ Hits        44618    44785     +167     
- Misses       2726     2783      +57     
Files Coverage Δ
source/ast/expressions/MiscExpressions.cpp 94.11% <100.00%> (+<0.01%) ⬆️
include/slang/ast/expressions/AssertionExpr.h 55.65% <22.58%> (-7.79%) ⬇️
source/ast/expressions/AssertionExpr.cpp 87.45% <80.81%> (-2.75%) ⬇️

... and 2 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b1354ad...a4b2714. Read the comment docs.

@likeamahoney likeamahoney changed the title [slang] Introduce nondegeneracy check [slang][lang-compliance] Introduce nondegeneracy check May 17, 2024
@MikePopoloski
Copy link
Owner

Thank you for tackling this; the sections of the LRM that address this are daunting which is why I had not implemented it yet. I'll have to review this quite closely.

One thing that can help inform additional tests is looking at the coverage report, which shows some big chunks that haven't been tested at all yet.

@@ -91,12 +96,22 @@ class SLANG_EXPORT AssertionExpr {
/// and false otherwise.
bool admitsEmpty() const;

bool isAdmitsOnlyEmpty() const { return admitsOnlyEmpty; }
Copy link
Owner

Choose a reason for hiding this comment

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

Can you rework these three functions into a single one that returns the admits empty / no-match properties via a single flags enum? You can move the AdmitsEmpty enum up here and expand it to include a bit for the no-match part as well. Then you won't need the clunky extra admitsOnlyEmpty member below that is only validly set when you call admitsEmpty first, which seems like a footgun.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for suggestion, fixed)!


bool operator==(const SequenceRange& right) const;

bool operator>(const SequenceRange& right) const;
Copy link
Owner

Choose a reason for hiding this comment

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

You can instead implement all of the comparison operators by implementing the "spaceship" operator: operator<=> to simplify this a bunch.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Contributor Author

Choose a reason for hiding this comment

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

currently it's failed on MacOS build - I'm working with it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I chose to implement only the operator< and changed the comparison logic

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Spaceship (<=>) operator requires realization for any field but for std::optional that operator marked as deleted. It requires for changing processing of SequenceRanges with unbonded maximum I don't have any good idea for resolving with another way.

@@ -417,6 +490,71 @@ void SequenceRange::serializeTo(ASTSerializer& serializer) const {
serializer.write("max", "$"sv);
}

bool SequenceRange::operator!=(const SequenceRange& right) const {
Copy link
Owner

Choose a reason for hiding this comment

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

I think the logic here could just be defaulted? You're just comparing field by field right? So operator==(const SourceRange&) = default; should have the correct behavior?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it was removed in terms of fixing a previous comment

return (*this == right) || (*this < right);
}

/// `SequenceRange`s intersects if max delay of one range
Copy link
Owner

Choose a reason for hiding this comment

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

Documentation comments should go in the header, otherwise I don't think they'll be picked up by doxygen into the doc build.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

moved

context.addDiag(diag::SeqPropAdmitEmpty, syntax.expr->sourceRange());
// Any sequence that is used as a property shall be nondegenerate and shall not admit
// any empty match.
if (bool isSeqAdmitsEmpty = expr.admitsEmpty(); isSeqAdmitsEmpty || expr.admitsNoMatch()) {
Copy link
Owner

Choose a reason for hiding this comment

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

Rather than checking this again here can you rely on the bind() call above to do it by passing the right flags?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

currently I don't have any idea how to correctly fix that because there is call to bind overload which accepts SequenceExprSyntax instead of PropertyExprSyntax

there are no way to fix that without changes in bind call behavior or StrongWeakPropertyExprSyntax struct

/// Returns maximum possible sequence length beetween all case labels sequence expressions
/// include `default` label
std::optional<SequenceRange> CaseAssertionExpr::computeSequenceLengthImpl() const {
std::set<SequenceRange> seqRangeItems;
Copy link
Owner

Choose a reason for hiding this comment

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

You're just using the set for sorting here right? It's generally a good idea to avoid allocating containers unless strictly necessary, and in this case I think you can just store the current maximum and update it on each iteration if the new value is larger? You don't actually need to store all the items.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@@ -1178,6 +1539,14 @@ void CaseAssertionExpr::serializeTo(ASTSerializer& serializer) const {
serializer.write("defaultCase", *defaultCase);
}

/// Returns `nullopt` if `disable iff` condition is always `true` (`1`)
std::optional<SequenceRange> DisableIffAssertionExpr::computeSequenceLengthImpl() const {
if (condition.constant && condition.constant->integer().countOnes())
Copy link
Owner

Choose a reason for hiding this comment

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

The ConstantValue::isTrue() function is probably a nicer way to express this than calling countOnes()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

static const AssertionExpr& bind(const syntax::SequenceExprSyntax& syntax,
const ASTContext& context, bool allowDisable = false);

static const AssertionExpr& bind(const syntax::PropertyExprSyntax& syntax,
const ASTContext& context, bool allowDisable = false,
bool allowSeqAdmitEmpty = false);
bool isFollowedByProp = false, bool isOverlapImpl = false,
Copy link
Owner

Choose a reason for hiding this comment

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

This is becoming too many boolean arguments in a row, which makes it easy to mess up the ordering of them when calling the function. I think this should be made a separate flags enum.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@likeamahoney likeamahoney force-pushed the feature/introduce-nondegeneracy branch from 3050cdf to 6895b51 Compare May 22, 2024 12:44
@likeamahoney
Copy link
Contributor Author

likeamahoney commented May 22, 2024

Thank you for tackling this; the sections of the LRM that address this are daunting which is why I had not implemented it yet. I'll have to review this quite closely.

One thing that can help inform additional tests is looking at the coverage report, which shows some big chunks that haven't been tested at all yet.

At the moment I only added a few tests to increase the possible coverage but at all it still in progress

@likeamahoney
Copy link
Contributor Author

Thank you for tackling this; the sections of the LRM that address this are daunting which is why I had not implemented it yet. I'll have to review this quite closely.
One thing that can help inform additional tests is looking at the coverage report, which shows some big chunks that haven't been tested at all yet.

At the moment I only added a few tests to increase the possible coverage but at all it still in progress

And also add support for checking nondegenerancy for lhs of "followed-by property" properties (e.g. #-# and #=#)

@likeamahoney likeamahoney force-pushed the feature/introduce-nondegeneracy branch from 0829546 to 85c8dae Compare May 23, 2024 12:15
@@ -7,6 +7,10 @@
//------------------------------------------------------------------------------
#pragma once

#include <optional>
#include <set>
Copy link
Owner

Choose a reason for hiding this comment

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

No longer need to include set here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed all unnecessary headers which I was added early

bool allowSeqAdmitEmpty = false);
static const AssertionExpr& bind(
const syntax::PropertyExprSyntax& syntax, const ASTContext& context,
bool allowDisable = false, const NondegeneracyFlags nondegFlags = NondegeneracyFlags::None);
Copy link
Owner

Choose a reason for hiding this comment

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

The const here does nothing -- since this is passed by value a copy will be made, which is fine, it's a small type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@@ -239,11 +295,19 @@ class SLANG_EXPORT SimpleAssertionExpr : public AssertionExpr {
/// An optional repetition of the sequence.
std::optional<SequenceRepetition> repetition;

SimpleAssertionExpr(const Expression& expr, std::optional<SequenceRepetition> repetition) :
AssertionExpr(AssertionExprKind::Simple), expr(expr), repetition(repetition) {}
/// Store `true` if sequence exression is can be evaluated as constant `false` (`0`).
Copy link
Owner

Choose a reason for hiding this comment

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

"expression", -is

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@@ -7,6 +7,9 @@
//------------------------------------------------------------------------------
#include "slang/ast/expressions/AssertionExpr.h"

#include <iostream>
Copy link
Owner

Choose a reason for hiding this comment

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

iostream include should be unnecessary

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -417,6 +471,47 @@ void SequenceRange::serializeTo(ASTSerializer& serializer) const {
serializer.write("max", "$"sv);
}

bool SequenceRange::operator<(const SequenceRange& right) const {
// if both sequences are unbounded than check min's
Copy link
Owner

Choose a reason for hiding this comment

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

s/than/then

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

// If condition of `disable iff` can be evaluated only as `true` (`1`) than sequence property
// is no match
if (const auto evaluatedValue = context.tryEval(cond);
evaluatedValue.isInteger() && evaluatedValue.integer().countOnes()) {
Copy link
Owner

Choose a reason for hiding this comment

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

isTrue

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed for all locations when I used countOnes

std::optional<SequenceRange> max;
for (const auto& item : items) {
if (const auto itemLen = item.body->computeSequenceLength(); itemLen.has_value()) {
if (const auto itemLenVal = itemLen.value(); max.has_value() || max < itemLenVal)
Copy link
Owner

Choose a reason for hiding this comment

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

Is this meant to be !max.has_value() ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes it's typo - fixed

: res;
break;
}
case BinaryAssertionOperator::And: {
Copy link
Owner

Choose a reason for hiding this comment

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

You no longer set the admitsEmpty flag for the And case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

: res;
break;
}
case BinaryAssertionOperator::Intersect: {
Copy link
Owner

Choose a reason for hiding this comment

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

Same here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

case BinaryAssertionOperator::Throughout: {
res = (rightAdmitsEmpty) ? res | NondegeneracyStatus::AdmitsEmpty : res;

// In case of `throught` full sequence is no match if right part is no match
Copy link
Owner

Choose a reason for hiding this comment

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

s/throught/throughout

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@MikePopoloski
Copy link
Owner

Ok, I think this is good enough to land -- we can tweak or improve it later if needed.

@MikePopoloski MikePopoloski merged commit 71c4be2 into MikePopoloski:master May 25, 2024
15 checks passed
jameshanlon added a commit to jameshanlon/slang that referenced this pull request May 28, 2024
* master: (37 commits)
  Assertion expressions: only sequences can be nondegenerate, not properties
  Fix MSVC warning
  Be better about preserving the types of packed structs and unions used in binary operators
  Fix parameter default value visitor
  Cleanup of recent assertion expr changes; no change in behavior
  [slang][lang-compliance] Introduce nondegeneracy check (MikePopoloski#998)
  Update CHANGELOG.md
  Add Warith-op-mismatch, Wbitwise-op-mismatch, Wcomparison-mismatch, and Wsign-compare warnings
  Fix array type of cover symbol with-expression iterators
  Improve diagnostic location for constant conversion warnings
  Refactor Expression::getEffectiveSign to return a 3-state result
  Fix checking of sequence concat delay with single bracketed delay expression
  Add Wunused-import and Wunused-wildcard-import
  Improve parser error when encountering extra end delimiter in member list
  Don't add invalid generic class specializations to internal specialization map
  Add optional compiler directives from annex E
  Add optional system tasks and functions from annex D
  Make error for invalid state-dependent path conditions suppressible
  Update CHANGELOG.md
  v1800-2023 clarification: only specific bidi switches allow UDNT connections
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants