Skip to content

Support SMV resize and type-conversion operators#475

Open
augustomafra wants to merge 13 commits intostanford-centaur:mainfrom
augustomafra:smv_frontend_v4
Open

Support SMV resize and type-conversion operators#475
augustomafra wants to merge 13 commits intostanford-centaur:mainfrom
augustomafra:smv_frontend_v4

Conversation

@augustomafra
Copy link
Contributor

@augustomafra augustomafra commented Sep 28, 2025

SMV files generated from Yosys use several bit-vector resize and type conversion operations that are not currently supported by pono.

Also adding test cases for soundness bug on RTS that got exposed when testing Yosys-generated SMV files (see soundness fix PR at #474 ).

Copy link
Member

@CyanoKobalamyne CyanoKobalamyne left a comment

Choose a reason for hiding this comment

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

Hi! Thank you for contributing to Pono and sorry for taking so long to review your PR. I have left a couple of comments. :)

Comment on lines +832 to +836
s << " resize (";
ex1->generate_ostream(name, prefix, module_list, new_prefix, s);
s << " , ";
ex2->generate_ostream(name, prefix, module_list, new_prefix, s);
s << " ) ";
Copy link
Member

Choose a reason for hiding this comment

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

What is the purpose of this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The SMV parser actually does two passes: the first one generating an AST with SMVnodes whose only purpose is to be dumped as text with all modules flattened in SMVEncoder::preprocess(). Then the second pass reads the flattened stream and does the actual encoding to smt-switch terms.

The first pass is run with enc.module_flat = false, and the second one with enc.module_flat = true, hence the branching on this condition almost everywhere in smvparser.y.

This flow is executed from SMVEncoder's constructor:

module_flat = false;
file = filename;
parse(filename);
preprocess();
module_flat = true;
loc.end.line = 0;
std::string output = preprocess().str();
processCase();

Having that in mind, the issue here is that the flattened text had a syntax error: it would dump resize a, 32 instead of resize(a, 32)

%right OP_IMPLY
%left OP_BI
%left IF_ELSE
%right IF_ELSE
Copy link
Member

Choose a reason for hiding this comment

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

I can't find the associativity of the ternary conditional described anywhere in the NuSMV manual; why did you make this change?

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 seems NuSMV/NuXmv manuals do not specify that, indeed. I figured out this rule by running the test tests/encoders/inputs/smv/ternary.smv with NuXmv directly, and it would accept it as right-associative whereas pono would raise a syntax error.

I hit this condition because some SMV files generated from Yosys contained right-associative ternaries, e.g. result := a ? 0.0 : b ? 1.0 : c ? 2.0 : d ? 3.0 : 4.0;

Comment on lines 854 to +863
if((bvs_a == SMVnode::Real && bvs_b == SMVnode::Integer) || (bvs_a == SMVnode::Integer && bvs_b == SMVnode::Real) ){
e = enc.solver_->make_term(smt::Equal, a->getTerm(), b->getTerm());
smt::Term a_term = a->getTerm();
smt::Term b_term = b->getTerm();
if (bvs_a == SMVnode::Real || bvs_b == SMVnode::Real){
if (bvs_a != SMVnode::Real)
a_term = enc.solver_->make_term(smt::To_Real, a_term);
if (bvs_b != SMVnode::Real)
b_term = enc.solver_->make_term(smt::To_Real, b_term);
}
e = enc.solver_->make_term(smt::Equal, a_term, b_term);
Copy link
Member

Choose a reason for hiding this comment

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

As per the NuSMV manual, mixed-type equality check are not actually allowed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe there's a conceptual definition that I should double check regarding Pono's language frontend.
I see you mention NuSMV's manual (https://nusmv.fbk.eu/userman/v26/nusmv.pdf), however, I was actually following NuXmv's manual (https://nuxmv.fbk.eu/downloads/nuxmv-user-manual.pdf).

I assumed NuXmv was the reference used because real arithmetic was introduced only in it. Also, it introduced the signatures integer * real → boolean and real * integer → boolean for = and !=.

I don't know if Pono should stick strictly to SMV, as indeed there were no SMV tests with real types before my change. Let me know what is your decision here.
In any case, I would need to continue with the NuXmv style on my fork for the purpose of my work, so I could figure out later how to merge the related changes in SMV here.

if(enc.module_flat){
SMVnode *boolean = $3;
if(boolean->getType() != SMVnode::Boolean){
throw PonoException("Word1 word type is uncompatible");
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
throw PonoException("Word1 word type is uncompatible");
throw PonoException("word1 can only be applied to booleans");

boolean->getTerm(),
enc.solver_->make_term(1, bv1sort),
enc.solver_->make_term(0, bv1sort));
assert(res); //check res non-null
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is necessary. When would res be null? In any case, assertions get compiled out of non-debug builds.

throw PonoException("No array");
}
| basic_expr IF_ELSE basic_expr ":" basic_expr {
| basic_expr IF_ELSE basic_expr ":" basic_expr %prec IF_ELSE {
Copy link
Member

Choose a reason for hiding this comment

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

What does this affect and why is it needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This complements the change making ternaries right-associative. The %prec directive tells Bison that the associativity of the entire rule follows the associative of the given token (IF_ELSE). The first change is not effective without this extra directive.

Comment on lines +1344 to +1358
if(enc.module_flat){
SMVnode *expr = $4;
SMVnode::Type expr_type = expr->getType();

if(expr_type != SMVnode::Integer){
throw PonoException("Signed word conversion type is uncompatible");
}
assert(expr);
smt::Term res = enc.solver_->make_term(smt::Op(smt::Int_To_BV, $2), expr->getTerm());
assert(res);
$$ = new SMVnode(res, SMVnode::Signed);
}else{
SMVnode *integer = new constant(std::to_string($2));
$$ = new resize_expr($4,integer);
}
Copy link
Member

Choose a reason for hiding this comment

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

Where is this conversion coming from? As far as I can tell, the current NuSMV manual language description says that "signed" is used for unsigned-to-signed bit vector conversion. There is a separate swconst operator for converting an integer to a signed bit-vector constant.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was another difference introduced in nuXmv. Again, it is fine if Pono would stick to SMV, then I could keep those changes limited to my fork.

{ "simple_counter_integer_uf.smv", pono::ProverResult::FALSE },
{ "combined-false.smv", pono::ProverResult::FALSE },
{ "combined-true.smv", pono::ProverResult::TRUE },
{ "counter_yosys.smv", pono::ProverResult::FALSE },
Copy link
Member

Choose a reason for hiding this comment

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

Is there any particular reason for this ordering?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

None at all, I only appended the new tests at the bottom. Please let me know if any other grouping is preferred (e.g. grouping by TRUE/FALSE results or alphabetical order)

Comment on lines +12 to +13
_$auto$rtlil#cc#2837#Not$9 := !resize(_$eq$arithmetic#sv#8$6_Y, 1);
_$auto$rtlil#cc#2884#And$11 := resize(_$auto$rtlil#cc#2837#Not$9, 1) & resize(0ub1_1, 1);
Copy link
Member

Choose a reason for hiding this comment

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

What are these two variables?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For some reason (maybe a bug) Yosys always dumps those dangling variables at the end of the SMV definitions. They don't have any purpose, but I left them in the test only for ensuring that Yosys' output won't be rejected in the future.

_delta := 1.00000000000000000 / 8.00000000000000000;
_$div$maurice2024_adc#sv#46$14_Y := _VIN / _delta;
_$floor$maurice2024_adc#sv#46$15_Y := signed word[3](floor(_$div$maurice2024_adc#sv#46$14_Y));
INVARSPEC bool(word1(_$floor$maurice2024_adc#sv#46$15_Y = 0sb3_000));
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this hold? At least if we assume that the type conversion is mod 8.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This test is actually pretty underconstrained because _VIN is a free input. So any real value could propagate into the floor operation.
Maybe a more meaningful test case should have an actual true invariant to check for. My goal at first was just to ensure that the syntax and typing checks would pass.

@augustomafra
Copy link
Contributor Author

Hi, @CyanoKobalamyne ,

Thanks for the review! No problem with the time at all: actually, I'm pretty much focused on finishing my thesis right now, so I will probably take a while until I'm able to address the changes here. It's great to hear feedback, I will work on the improvements to carry on with the merge request when it's possible.

I'm leaving some notes regarding the reasoning behind some of the changes, so feel free to continue commenting if you have something else in mind.

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.

2 participants