Last active
August 25, 2025 00:49
-
-
Save a4lg/4c63a478d46a1886713351e2ab53f8df to your computer and use it in GitHub Desktop.
prover: RISC-V: implication rules around the "C" extension
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #! /usr/bin/env python3 | |
| import z3 | |
| # Input variables | |
| rv32 = z3.Bool('RV32') | |
| f = z3.Bool('F') | |
| d = z3.Bool('D') | |
| c = z3.Bool('C') | |
| zca = z3.Bool('Zca') | |
| zcf = z3.Bool('Zcf') | |
| zcd = z3.Bool('Zcd') | |
| def get_constraints(zcf, zcd): | |
| global rv32, f, d, c, zca | |
| return z3.And(*[ | |
| # Each constraint comes from text of the ISA Manual. | |
| z3.Implies(d, f), # D -> F | |
| z3.Implies(z3.Not(rv32), z3.Not(zcf)), # Zcf is RV32 only | |
| z3.Implies(zcf, z3.And(zca, f)), # Zcf -> Zca + F (no RV32 checking required here) | |
| z3.Implies(zcd, z3.And(zca, d)), # Zcd -> Zca + D | |
| # [Note that it can be proven from stronger equality form in `statements`] | |
| # RISC-V ISA Manual: | |
| # > The C extension is the superset of the following extensions: | |
| z3.Implies( | |
| z3.And( | |
| zca, | |
| z3.Implies(z3.And(rv32, f), zcf), | |
| z3.Implies(d, zcd), | |
| ), | |
| c, | |
| ), | |
| # [Note that they can be proven from stronger equality form in `statements`] | |
| # RISC-V ISA Manual: | |
| # > As C defines the same instructions as Zca, Zcf and Zcd, the rule is that: | |
| z3.Implies(c, zca), # C -> Zca | |
| z3.Implies(z3.And(c, f, rv32), zcf), # C+F -> Zcf (RV32) | |
| z3.Implies(z3.And(c, d), zcd), # C+D -> Zcd | |
| ]) | |
| statements = [ | |
| # Practically the same as the ISA Manual definition | |
| # but in a stronger equality form. | |
| c == \ | |
| z3.And( | |
| zca, | |
| z3.Implies(z3.And(rv32, f), zcf), | |
| z3.Implies(d, zcd), | |
| ), | |
| # Implication checker to imply "C" *from* others. | |
| # Using `c == PROP` instead of `z3.Implies(PROP, c)` ensures that | |
| # no other PROPs can imply "C" on each clause (unless equivalent). | |
| # | |
| # Note that, appearance of "zcd" and "zcf" on a clause denotes that | |
| # we cannot say anything about "C" on the given clause if we need to | |
| # rely on rustc's target features. | |
| z3.If( | |
| rv32, | |
| z3.If( | |
| d, | |
| c == z3.And(zcf, zcd), | |
| z3.If( | |
| f, | |
| c == zcf, | |
| c == zca, | |
| ) | |
| ), | |
| z3.If( | |
| d, | |
| c == zcd, | |
| c == zca, | |
| ), | |
| ), | |
| # Imply certain extensions in a best-effort manner (with rustc's | |
| # `target_feature`s; avoids using zcd and zcf on the left). | |
| # For "C", there is a stronger statement. | |
| z3.Implies(z3.And(c, d), zcd), # Zcd | |
| z3.Implies(z3.And(rv32, c, f), zcf), # Zcf | |
| z3.Implies(z3.And(z3.Not(d), z3.Or(z3.Not(rv32), z3.Not(f))), zca == c), # C | |
| # For "C", check that if the expression above is not satisfied and "C" | |
| # is enabled, either "Zcd" or "Zcf" must be true. | |
| # | |
| # This is equivalent to the statement: | |
| # the condition above is optimal to force "C" to be enabled | |
| # if we are not allowed to depend on "Zcd" or "Zcf". | |
| z3.Implies( | |
| z3.Not(z3.And(z3.Not(d), z3.Or(z3.Not(rv32), z3.Not(f)))), | |
| z3.Implies(c, z3.Or(zcd, zcf)) | |
| ), | |
| # For "Zcf" and "Zcd", check that each condition (depending on other | |
| # variables) above to imply target extension is indeed optimal unless | |
| # equivalent to the condition in the proof. | |
| # | |
| # Accidentally, those optimal solutions (where only tautology is avoided) | |
| # do not involve uses of `"zcd"` or `"zcf"`. | |
| # | |
| # This is expressed as follows: | |
| # | |
| # 1. Even if the condition is *not* satisfied, there may be cases | |
| # where the all constraints are satisfied if the target is true. | |
| # 2. Even in such cases, the same model except target == false | |
| # *also* satisfies the given constraints. | |
| # 3. That means, the target is not forced to be true | |
| # by other variables. | |
| # Zcd | |
| z3.Implies( | |
| z3.Not(z3.And(c, d)), | |
| z3.Implies( | |
| # Substitute `zcd` with constants. | |
| get_constraints(zcf, True), | |
| get_constraints(zcf, False), | |
| ) | |
| ), | |
| # Zcf | |
| z3.Implies( | |
| z3.Not(z3.And(rv32, c, f)), | |
| z3.Implies( | |
| # Substitute `zcf` with constants. | |
| get_constraints(True, zcd), | |
| get_constraints(False, zcd), | |
| ) | |
| ), | |
| ] | |
| # For each statement, find counterexamples. | |
| for statement in statements: | |
| solver = z3.Solver() | |
| solver.add(get_constraints(zcf, zcd)) | |
| solver.add(z3.Not(statement)) | |
| # Expect "unsat" but print counterexample on "sat". | |
| result = solver.check() | |
| if result == z3.sat: | |
| print(f'Failed on: {statement}') | |
| print(solver.model()) | |
| assert(result == z3.unsat) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment