From 82a142b14cf51add8162d8902cefaa6117c47cc8 Mon Sep 17 00:00:00 2001 From: Louis-Emile <27809483+mndstrmr@users.noreply.github.com> Date: Wed, 26 Jun 2024 10:14:58 +0100 Subject: [PATCH] Formal equivalence checking with Sail Here's a high-level overview of what this commit does: - Compiles Sail into SystemVerilog including patchin compiler bugs - Create a TCL file that tells JasperGold what to prove and assume - Check memory operations modelling the LSU Most of these properties now prove without time-bound on the response from memory due to alternative LSUs - Check memory even with Smepmp errors: Continues on top of https://github.com/riscv/sail-riscv/pull/196 - CSR verification - Checks for instruction types such as B-Type, I-Type, R-Type - Check illegal instructions and WFI instructions - Using psgen language for proof generation - Documentation on how to use the setup - Wrap around proof that proves instructions executed in a row still match the specification. - Liveness proof to guarantee instructions will retire within a upper bound of cycles. All of these proofs make heavy use of the concept of k-induction. All the different properties and steps are necessary to help the tool get the useful properties it needs to prove the next step. The instruction correctness, wrap-around and liveness all give us increased confidence that Ibex is trace-equivalent to Sail. Throughout this process an issue was found in Ibex where the pipeline was not flushing properly on changing PMP registers using clear: #2193 Alternative LSUs: This makes all top level memory properties prove quickly and at a low proof effort (1 or 2-induction). Three 'alternative LSUs' representing three stages of memory instructions: 1. Before the first response is received, in the EX stage 2. After the first response is received, but not the second grant, also in the EX stage 3. Before the last response is received in the WB stage. In each case we ask 'if the response came now, would the result be correct?'. Similar is applied for CSRs/PC though less directly. This is particularly interesting (read: ugly) in the case of a PMP error wbexc_exists makes Wrap properties fast to prove. The bottleneck becomes SpecPastNoWbexcPC, which fails only due to a bug. See the comment in riscv.proof. Co-authored-by: Marno van der Maas --- CREDITS.md | 5 + dv/formal/.gitignore | 2 + dv/formal/Makefile | 38 ++ dv/formal/README.md | 142 ++++++++ dv/formal/Sources.mk | 27 ++ dv/formal/buildspec.py | 113 ++++++ dv/formal/check/encodings.sv | 151 ++++++++ dv/formal/check/peek/abs.sv | 107 ++++++ dv/formal/check/peek/alt_lsu.sv | 279 +++++++++++++++ dv/formal/check/peek/compare_helper.sv | 126 +++++++ dv/formal/check/peek/follower.sv | 132 +++++++ dv/formal/check/peek/mem.sv | 122 +++++++ dv/formal/check/protocol/irqs.sv | 26 ++ dv/formal/check/protocol/mem.sv | 49 +++ dv/formal/check/spec_instance.sv | 70 ++++ dv/formal/check/top.sv | 475 +++++++++++++++++++++++++ dv/formal/instrs.json | 58 +++ dv/formal/rtl-changes.patch | 46 +++ dv/formal/spec/fix_pmp_bug.py | 28 ++ dv/formal/spec/main.sail | 94 +++++ dv/formal/spec/spec_api.sv | 368 +++++++++++++++++++ dv/formal/spec/stub.sv | 80 +++++ dv/formal/thm/btype.proof | 48 +++ dv/formal/thm/ibex.proof | 326 +++++++++++++++++ dv/formal/thm/mem.proof | 169 +++++++++ dv/formal/thm/riscv.proof | 194 ++++++++++ dv/formal/verify.tcl | 271 ++++++++++++++ 27 files changed, 3546 insertions(+) create mode 100644 dv/formal/.gitignore create mode 100644 dv/formal/Makefile create mode 100644 dv/formal/README.md create mode 100644 dv/formal/Sources.mk create mode 100644 dv/formal/buildspec.py create mode 100644 dv/formal/check/encodings.sv create mode 100644 dv/formal/check/peek/abs.sv create mode 100644 dv/formal/check/peek/alt_lsu.sv create mode 100644 dv/formal/check/peek/compare_helper.sv create mode 100644 dv/formal/check/peek/follower.sv create mode 100644 dv/formal/check/peek/mem.sv create mode 100644 dv/formal/check/protocol/irqs.sv create mode 100644 dv/formal/check/protocol/mem.sv create mode 100644 dv/formal/check/spec_instance.sv create mode 100644 dv/formal/check/top.sv create mode 100644 dv/formal/instrs.json create mode 100644 dv/formal/rtl-changes.patch create mode 100644 dv/formal/spec/fix_pmp_bug.py create mode 100644 dv/formal/spec/main.sail create mode 100644 dv/formal/spec/spec_api.sv create mode 100644 dv/formal/spec/stub.sv create mode 100644 dv/formal/thm/btype.proof create mode 100644 dv/formal/thm/ibex.proof create mode 100644 dv/formal/thm/mem.proof create mode 100644 dv/formal/thm/riscv.proof create mode 100644 dv/formal/verify.tcl diff --git a/CREDITS.md b/CREDITS.md index c1d249a2d9..2187a89c58 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -6,6 +6,10 @@ University of Bologna under the name Zero-riscy. In December 2018, Ibex has been contributed to lowRISC who is maintaining and advancing the design since then. +Similarly, the Ibex `dv/formal` work was originally developed by the University of +Oxford in the summer of 2023. In the summer of 2024 this work was extended +for and contributed to lowRISC who is now maintaining and advancing the design. + Throughout the years, Ibex has seen contributions from many people and we at lowRISC are very thankful for all of them. This file lists the many people who contributed to what is called Ibex today. If you made a contribution to Ibex @@ -38,6 +42,7 @@ please feel free to open a pull request to get your name added to this file. - Ivan Ribeiro - Karol Gugala - Leon Woestenberg +- Louis-Emile Ploix - Luís Marques - Marek Pikuła - Markus Wegmann diff --git a/dv/formal/.gitignore b/dv/formal/.gitignore new file mode 100644 index 0000000000..617dc80ff5 --- /dev/null +++ b/dv/formal/.gitignore @@ -0,0 +1,2 @@ +jgproject +jgproofs diff --git a/dv/formal/Makefile b/dv/formal/Makefile new file mode 100644 index 0000000000..bdb2706fdd --- /dev/null +++ b/dv/formal/Makefile @@ -0,0 +1,38 @@ +SAIL=sail + +SAIL_RISCV_MODEL_DIR=../sail-riscv/model + +include Sources.mk + +PSGEN_SRCS=thm/btype.proof thm/ibex.proof thm/mem.proof thm/riscv.proof +PSGEN_FLAGS=-root riscv -task + +SAIL_EXTRA_SRCS=../spec/main.sail + +SAIL_SV_FLAGS=-sv -sv-comb -sv-inregs -sv-outregs -sv-nostrings -sv-nopacked -sv-nomem -Oconstant_fold -memo-z3 \ + -sv-unreachable translate -sv-unreachable lookup_TLB -sv-unreachable translate_tlb_miss -sv-unreachable translate_tlb_hit -sv-unreachable pt_walk \ + -sv-fun2wires 2:read_ram \ + -sv-fun2wires 2:write_ram \ + -sv-fun2wires wX \ + -sv-fun2wires 2:rX \ + +.PHONY: sv +sv: + mkdir -p build + python3 buildspec.py header > build/ibexspec_instrs.sv + cd build && $(SAIL) $(SAIL_SRCS) $(SAIL_EXTRA_SRCS) $(SAIL_SV_FLAGS) `cd .. && python3 buildspec.py unreachables` -o ibexspec + python3 spec/fix_pmp_bug.py + python3 buildspec.py unreachable_loc_hack + +.PHONY: reset-sub +reset-sub: + cd sail-riscv && git reset --hard HEAD + +.PHONY: update-sub +update-sub: reset-sub + cd sail-riscv && git pull + +.PHONY: psgen +psgen: + mkdir -p build + psgen $(addprefix -path ,$(PSGEN_SRCS)) $(PSGEN_FLAGS) -sv-out build/psgen.sv -tcl-out build/psgen.tcl diff --git a/dv/formal/README.md b/dv/formal/README.md new file mode 100644 index 0000000000..9be8529354 --- /dev/null +++ b/dv/formal/README.md @@ -0,0 +1,142 @@ +# End to End Formal Verification against the Sail + +Prerequisities (in your PATH): +- [The lowRISC fork of the Sail compiler](https://github.com/lowRISC/sail/tree/lowrisc) +- [psgen](https://github.com/mndstrmr/psgen) + +Build instructions: +- `git submodule init` +- `make psgen` to build the SV for the proofs given in `thm/` +- `make sv` to build the SV translation of the Sail compiler. Will invoke `buildspec.py`, which can be configured to adjust which instructions are defined. By default all of them are, this is correct but slow. +- Make the changes to Ibex described in the RTL changes. +- `SAIL_DIR= jg verify.tcl` + +## Conclusivity +All properties are currently known to be conclusive, with the exception of M-Types. The later stages (Memory, Top, RegMatch and Wrap and some Liveness) generally take longer. + +This means that (up to base case) Ibex is trace equivalent to the Sail (i.e. where the Sail main function is run in a loop). This means: +- If one takes a sequence of instructions and ran Ibex on them, the same memory operations in the same order would be produced by running the Sail main function in a loop. +- We do not prove that the instruction executed with a given PC was loaded with that PC. +- We haven't proved that Ibex resets correctly, if it doesn't there is no trace equivalence. + +## RTL Changes +- `ResetAll = 1` is required for now (instead of `ResetAll = Lockstep`) +- Fix for [#2913](https://github.com/lowRISC/ibex/issues/2193): Include `|| csr_op_o == CSR_OP_CLEAR` in the cases for `csr_pipeline_flushes` in `ibex_id_stage.sv`. + +## Code Tour +### Top Level Goals +The top level objective is to get proofs for `Wrap`, `Live`, `Load`, `Store` and `NoMem`. These properties are themselves enough to prove trace equivalence (see below). +They are intended to be simple and human interpretable, you should be able to convince yourself that those properties together imply specification conformance of Ibex (igoring the holes listed below). +- `Wrap` proves that on each invocation of the specification (each time `spec_en` is high) the new inputs to the spec are consistent with the previous outputs of the spec. +- `Live` proves that you will always eventually get a `spec_en` event, (i.e. it's not possible to avoid comparing with the spec). This is done by finding a very conservative but finite bound on the amount of time between spec `spec_en`. +- `Load` and `Store` prove that the requests made to memory are correct, given a common spec/Ibex starting state. `Wrap`+`Live` proves that that starting state is actually the same. They are written only in terms of top level signals, and `instr_will_progress`, which directly implies `spec_en`. +- `NoMem` checks that non memory instructions don't make memory requests. + +Everything else (about 1,000 properties) exist to help `Wrap` converge. + +Immediately below `Wrap` we have `Top`, which asserts that when any instruction retires it does so correctly. This is proved with a big case split, where we check the same for each individual instruction. Most of the work goes into proving those properties with many many invariants. + +### Helpers +Most Ibex specific helpers can be found in `thm/ibex.proof`, it's a mess of invariants that help the model checker significantly. +Each one was at some point obtained by analysing some kind of k-induction trace, or just by thinking about why a property can't be easily proved in its current state. + +The standout part of that file is `Memory`. It is the central graph induction used to prove that memory operations are well behaved. +The definitions of the nodes are fairly clear, though their invariants can be complex. They are however fairly intuitive with some thought. + +Arguably the most important helpers are those in `thm/riscv.proof` that individually check each instruction or type of instruction. +They pretty much all check the same thing: The RF addr/WE is correct, the RF data is correct, the CSRs are correct, and the PC is correct. +Most also have `use structure_fast`, which is a great help. + +That file also contains checks for non-instructions like IRQs, illegal instructions and fetch errors. They are checked in largely similar ways. + +Towards the end of `riscv.proof` we have Top, RegMatch and Wrap. Wrap is the goal. Top is the statement: 'every instruction is correct', and RegMatch is to help Wrap +with the otherwise difficult RegA and RegB properties. + +The other helpers are in `btype.proof` and `mem.proof`, which mostly just contain more graph inductions for properties that can otherwise be difficult to prove. + +### Liveness +The final property of `Wrap` is `Live`, which essentially proves that there will be infinite points of comparison between Ibex and the spec. It is this signal which means you don't have to trust that `spec_en` means anything. `Live` is difficult to prove. Ideally it would say `always (s_eventually spec_en)`, but in practice it has to enforce a strict bound, since proving `s_eventually` properties is quite difficult: `always (##[0:N] spec_en)`. You can find this value for `N` in `Wrap`, at the time of writing it's 212, essentially, meaning that an instruction must move to writeback at least once in 212 cycles. This is obviously an extremely weak bound, but that's fine since any finite bound is sufficient to prove liveness. + +The proof of `Live` is fairly neat, and can be found in `ibex.proof`. It composes many properties about smaller time bounds into one large property, which conservatively just adds them all up. The major difficulty in particular is proving that if an instruction is killed, then the next one will not be. There are also issues with long running instructions and events (divide, memory, WFI). + +By default live (and the liveness lemmas) are commented out, since it currently only applies when `TIME_LIMIT = 1`. + +## Guide to Bug Hunting +When you are looking to check you haven't introduced a bug you should be careful not to accidentically assume the absence of any bugs. + +**Either prove each task in order, or run a bounded check on the property you are interested in, but do so outside of the proof structure, i.e. in the `` task.** + +It is enough to check `Wrap`, `Load*` and `Store*`. That said it might be more convenient to check a lower level property. +- If you've changed the behaviour of an instruction (either its GPRs, CSRs, PC or whatever else) you should probably be checking the properties for that instruction or class of instructions. Alternatively you could check the `Top` properties. +- If you've changed the behaviour of interrupts check the `IRQ` properties. +- If you've changed PMP, or other memory related things check `FetchErr` as well as the data memory properties. +- Pipeline control issues are only directly checked by `Wrap`, though will likely be picked up by something else. + +## Proof Holes +1. Some CSRs are not checked. See `ex_is_checkable_csr` in `top.sv` for the full list. They are not checked either because they aren't specified, can't be specified or because I haven't got around to adding them yet. Some should be easy to check. When a CSR instruction touches one of those CSRs the next round of checks is essentially just disabled, anything can happen and verification will assume at the next `spec_en`. +2. Bus errors are assumed not to occur. They are not specified, though the bigger issue is that they would be hard to prove correctness of. +3. There are no NMIs or fast IRQs, since there are not specified. +4. We forbid attempting to enter debug mode, as this is also not specified. +5. We assume `ResetAll` and no clock gating. Both of these could be fixed with probably fairly little effort. +6. I don't check that Ibex ever handles interrupts. If it does it handle an interrupt it does so correctly. +7. I haven't proved security, I have proved correctness, or more specificially specification conformance. If there is a bug in the spec, or a side channel somewhere, I will not find it. +8. There is currently no proof of correctness of instruction fetch, i.e. when IF returns an instruction with a given PC, it has not been proved that that instruction was actually loaded from memory with that PC. This was proved for CHERIoT-Ibex and could probably be easily proved here too. +9. Ibex has not been proved to reset to the correct state, there's no reason it couldn't be. + +The precise configuration of Ibex can be found in `top.sv`, but is mostly the default with the following exceptions: +- 4 PMP Regions, enabled +- `BranchTargetALU = 1` +- `ResetAll = 1` (but `SecureIbex = 0`) +In particular, this means we have +- `RV32MFast` +- `RV32BNone` (Hence we are missing some instructions. This might be a good place for future work, it should be not terribly difficult to extend this) +- `RegFileFF` + +The following are not holes per se, but are limitations, needed to obtain convergence of liveness or others: +1. Memory grants/responses are bounded to take at most `TIME_LIMIT` cycles in `protocol/mem.sv`. Removing this restriction would be hard. That number is 10 at time of writing, but `Wrap` and `Live` have only been tested with 1. See below for more on that. +2. WFI instructions may not currently be enterred if they couldn't ever be exitted in `protocol/irqs.sv`. This probably should be legal behaviour, but without it liveness is false. There could probably instead be a specific carveout for this case, since it's also legal behaviour. +3. If a WFI is enterred it must be interrupted within `WFI_BOUND` cycles, in `protocol/irqs.sv`. An `s_eventually` equivalent would be nice but in practice is very difficult to prove properties with. +4. When an interrupt is fired, that interrupt must remain fired until a memory request is granted (i.e. as if it was some MMIO operation), in `protocol/irqs.sv`. This might be removable. + +## Wrap Around +The formal work includes `Wrap` properties. They check that each time the specification is 'used' the inputs to the specification equal its outputs the last time it was read. + +Wrap properties have currently only been tested with `TIME_LIMIT = 1`, which is way lower than is ideal. Higher numbers do probably work, but I have no idea how long they will take. If JG is doing its job properly it shouldn't matter at all. The liveness properties will fail for higher time limits (this should be easy but fiddly to fix! They would ideally have limits written in terms of `TIME_LIMIT` and `WFI_BOUND`), so they are commented out by default. + +While the implications of such a property are intuitive, we can be satisfying formal too. Define five functions: +1. The specification function $\text{Spec} : S \times I \to S$ maps an architectural state and input to the next architectural state. It does not matter what is meant by 'next' here, but in our case $\text{Spec}$ steps through either an illegal instruction, an interrupt or a regular instruction. +2. $\text{SpecOut} : S \times I \to O$ maps an architectural state and input to an output (i.e. memory outputs). +3. $\text{Ibex} : A \times I \to A$ maps one micro-architectural state and input to the next micro-architectural state, where 'next' is equivalent to 'next' for $\text{Spec}$ and might span multiple clock cycles. +4. $\text{IbexOut} : S \times I \to O$ maps a micro-architectural state and input to an output. +5. $\text{abs} : A \to S$ maps a micro-architectural state to an architectural state. + +The memory assertions (`Load`, `Store`, `NoMem`) prove the following statement, for any micro-architectural state $a$ and input $i$: +```math +\text{SpecOut}(\text{abs}(a), i) = \text{IbexOut}(a, i) +``` + +The assertions with prefix `Wrap_` (using essentially every other property we have as lemmas) prove the following statement: +```math +\text{Spec}(\text{abs}(a), i) = \text{abs}(\text{Ibex}(a, i)) +``` +If $a_0$ is the reset micro-architectural state we have, by a straightforward induction, that for a sequence of $n$ inputs $I$, +```math +\text{Spec}^n(\text{abs}(a_0), I) = \text{abs}(\text{Ibex}^n(a_0, I)) +``` +Where I here denote $f^{n + 1}(a, I)$ to be the the left fold of `f` over `I` using `a` initially, i.e. in Haskell syntax `foldl f a I`. Combined we derive that +```math +\text{SpecOut}(\text{abs}(\text{Ibex}^n (a_0, I)), i) = \text{SpecOut}(\text{Spec}^n (\text{abs}(a_0), I), i) = \text{IbexOut}(\text{Ibex}^n (a_0, I), i) +``` + +Finally, we will (but have not yet attempted to) prove that $\text{abs}(a_0) = s_0$, where $s_0$ is the initial state of the specification: +```math +\text{SpecOut}(\text{Spec}^n (s_0, I), i) = \text{IbexOut}(\text{Ibex}^n (a_0, I), i) +``` + +With that we can conclude that for any sequence of $n$ steps the outputs will remain the same between the specification and Ibex, proving their complete equivalence, so long as there are infinite such steps, which the `Live` property proves. + +Note a few things: +1. We do not implicitly rely on (or define) 'correctness' for $\text{abs}$. Satisfying the above propositions is sufficient. In practice it will need to be 'correct' in some sense for $\text{Spec}$ to make use of it. +2. We do not need to prove that the internal signals of Ibex mean anything, e.g. we have no explicit interpretation of the register file, the pipeline or anything else. To be convinced that I am correct you need to trust only that `Wrap`, `Live`, `Load`, `Store`, `NoMem` say what I claim they do above. If I am wrong about my interpretation about internal signals, it does not matter. +3. In practice there are steps we cannot prove equivalence across (i.e. accessing CSRs we don't have a specification for). In such cases we just disable the next check and resume after that. + diff --git a/dv/formal/Sources.mk b/dv/formal/Sources.mk new file mode 100644 index 0000000000..37e89a6d90 --- /dev/null +++ b/dv/formal/Sources.mk @@ -0,0 +1,27 @@ +SAIL_XLEN := riscv_xlen32.sail + +SAIL_CHECK_SRCS = riscv_addr_checks_common.sail riscv_addr_checks.sail riscv_misa_ext.sail +SAIL_DEFAULT_INST = riscv_insts_base.sail riscv_insts_cext.sail riscv_insts_mext.sail riscv_insts_zicsr.sail + +SAIL_SEQ_INST = $(SAIL_DEFAULT_INST) riscv_jalr_seq.sail + +SAIL_SEQ_INST_SRCS = riscv_insts_begin.sail $(SAIL_SEQ_INST) riscv_insts_end.sail + +SAIL_SYS_SRCS = riscv_csr_map.sail +SAIL_SYS_SRCS += riscv_sys_exceptions.sail +SAIL_SYS_SRCS += riscv_sync_exception.sail +SAIL_SYS_SRCS += riscv_csr_ext.sail +SAIL_SYS_SRCS += riscv_sys_control.sail + +PRELUDE = prelude.sail $(SAIL_XLEN) prelude_mem_metadata.sail prelude_mem.sail + +SAIL_REGS_SRCS = riscv_reg_type.sail riscv_regs.sail riscv_pc_access.sail riscv_sys_regs.sail +SAIL_REGS_SRCS += riscv_pmp_regs.sail riscv_pmp_control.sail +SAIL_REGS_SRCS += riscv_ext_regs.sail $(SAIL_CHECK_SRCS) + +SAIL_ARCH_SRCS = $(PRELUDE) +SAIL_ARCH_SRCS += riscv_types_common.sail riscv_types_ext.sail riscv_types.sail +SAIL_ARCH_SRCS += riscv_vmem_types.sail $(SAIL_REGS_SRCS) $(SAIL_SYS_SRCS) riscv_platform.sail +SAIL_ARCH_SRCS += riscv_mem.sail + +SAIL_SRCS = $(addprefix $(SAIL_RISCV_MODEL_DIR)/,$(SAIL_ARCH_SRCS) $(SAIL_SEQ_INST_SRCS)) \ No newline at end of file diff --git a/dv/formal/buildspec.py b/dv/formal/buildspec.py new file mode 100644 index 0000000000..92504c99bd --- /dev/null +++ b/dv/formal/buildspec.py @@ -0,0 +1,113 @@ +# Copyright lowRISC contributors. +# Copyright 2024 University of Oxford, see also CREDITS.md. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# Original author: Louis-Emile Ploix +# SPDX-License-Identifier: Apache-2.0 + +# This script is called from the Makefile. +# Essentially it is a way to enable and disable instructions from the Sail specification to make proofs easier. + +import json +import re +import sys + +with open("instrs.json", "r") as f: + INSTRS = set(json.load(f)) + +class SpecConfig: + def __init__(self): + self.instrs = set() + + def add(self, *instrs): + for instr in instrs: + assert instr in INSTRS + self.instrs.add(instr) + + def add_all(self): + self.instrs = INSTRS + + def add_noncompressed(self): + self.instrs = {x for x in INSTRS if not x.startswith("C_")} + + def remove(self, instr): + assert instr in INSTRS + self.instrs.discard(instr) + + def make_sail_unreachables(self): + return " ".join(f"-sv_unreachable execute_{instr}" for instr in INSTRS.difference(self.instrs)) + + def make_sv_header(self): + return "\n".join([ + "`ifndef SPEC_INSTRS", + "`define SPEC_INSTRS", + "", + *[f"`define SPEC_{instr.upper()} {int(instr in self.instrs)}" for instr in INSTRS], + "", + "`endif" + ]) + + def add_jmps(self): + self.add("RISCV_JAL", "RISCV_JALR") + + def add_itype(self): + self.add("ITYPE") + self.add("SHIFTIOP") + + def add_rtype(self): + self.add("RTYPE") + + def add_fences(self): + self.add("FENCE", "FENCEI") + + def add_loads(self): + self.add("LOAD") + + def add_stores(self): + self.add("STORE") + + def add_mtypes(self): + self.add("MUL", "DIV", "REM") + + def add_illegal(self, extra=True): + self.add("ILLEGAL", "C_ILLEGAL") + if extra: + self.add("CSR") + self.add("MRET", "WFI") + + def add_system(self): + self.add("ECALL", "EBREAK", "MRET", "WFI") + + def add_csr(self): + self.add("CSR") + + def add_utypes(self): + self.add("UTYPE") + + def add_btypes(self): + self.add("BTYPE") + +conf = SpecConfig() +conf.add_noncompressed() + +if len(sys.argv) > 1: + if sys.argv[1] == "unreachables": + print(conf.make_sail_unreachables()) + elif sys.argv[1] == "header": + print(conf.make_sv_header()) + elif sys.argv[1] == "unreachable_loc_hack": + with open("build/ibexspec.sv", "r") as f: + c = f.read() + c = c.replace( + "sail_reached_unreachable = 1;", + "begin sail_reached_unreachable = 1; sail_reached_unreachable_loc = `__LINE__; end" + ).replace( + "sail_reached_unreachable = 0;", + "begin sail_reached_unreachable = 0; sail_reached_unreachable_loc = -1; end" + ).replace( + "bit sail_reached_unreachable;", + "bit sail_reached_unreachable;\n\tlogic [31:0] sail_reached_unreachable_loc;" + ) + with open("build/ibexspec.sv", "w") as f: + f.write(c) +else: + print("Intended usage is to run make sv") diff --git a/dv/formal/check/encodings.sv b/dv/formal/check/encodings.sv new file mode 100644 index 0000000000..1ac37bee34 --- /dev/null +++ b/dv/formal/check/encodings.sv @@ -0,0 +1,151 @@ +// Copyright lowRISC contributors. +// Copyright 2024 University of Oxford, see also CREDITS.md. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// Original author: Louis-Emile Ploix +// SPDX-License-Identifier: Apache-2.0 + +`include "../build/ibexspec_instrs.sv" + +`ifndef CMP_INSNS +`define CMP_INSNS + +`define IS_ITYPE(idx) (`INSTR[6:0] == 7'b0010011 && `INSTR[14:12] == idx) +`define IS_ADDI `IS_ITYPE(3'b000) +`define IS_SLTI `IS_ITYPE(3'b010) +`define IS_SLTIU `IS_ITYPE(3'b011) +`define IS_XORI `IS_ITYPE(3'b100) +`define IS_ORI `IS_ITYPE(3'b110) +`define IS_ANDI `IS_ITYPE(3'b111) +`define IS_ANY_ITYPE ( \ + `IS_ADDI | `IS_SLTI | `IS_SLTIU | `IS_XORI | \ + `IS_ORI | `IS_ANDI \ +) + +`define ISS_ADDI (`IS_ADDI & `SPEC_ITYPE) +`define ISS_SLTI (`IS_SLTI & `SPEC_ITYPE) +`define ISS_SLTIU (`IS_SLTIU & `SPEC_ITYPE) +`define ISS_XORI (`IS_XORI & `SPEC_ITYPE) +`define ISS_ORI (`IS_ORI & `SPEC_ITYPE) +`define ISS_ANDI (`IS_ANDI & `SPEC_ITYPE) + +`define IS_SLLI (`IS_ITYPE(3'b001) && `INSTR[31:25] == 7'b0000000) +`define IS_SRLI (`IS_ITYPE(3'b101) && `INSTR[31:25] == 7'b0000000) +`define IS_SRAI (`IS_ITYPE(3'b101) && `INSTR[31:25] == 7'b0100000) + +`define IS_SHIFTIOP (`IS_SLLI | `IS_SRLI | `IS_SRAI) + +`define ISS_SLLI (`IS_SLLI & `SPEC_SHIFTIOP) +`define ISS_SRLI (`IS_SRLI & `SPEC_SHIFTIOP) +`define ISS_SRAI (`IS_SRAI & `SPEC_SHIFTIOP) + +`define ISS_SHIFTIOP (`ISS_SLLI | `ISS_SRLI | `ISS_SRAI) + +`define IS_RTYPE_0(idx) \ + (`INSTR[6:0] == 7'b0110011 && `INSTR[31:25] == 7'b0000000 && `INSTR[14:12] == idx) +`define IS_RTYPE_1(idx) \ + (`INSTR[6:0] == 7'b0110011 && `INSTR[31:25] == 7'b0100000 && `INSTR[14:12] == idx) +`define IS_ADD `IS_RTYPE_0(3'b000) +`define IS_SUB `IS_RTYPE_1(3'b000) +`define IS_SLL `IS_RTYPE_0(3'b001) +`define IS_SLT `IS_RTYPE_0(3'b010) +`define IS_SLTU `IS_RTYPE_0(3'b011) +`define IS_XOR `IS_RTYPE_0(3'b100) +`define IS_SRL `IS_RTYPE_0(3'b101) +`define IS_SRA `IS_RTYPE_1(3'b101) +`define IS_OR `IS_RTYPE_0(3'b110) +`define IS_AND `IS_RTYPE_0(3'b111) + +`define ISS_ADD (`IS_ADD & `SPEC_RTYPE) +`define ISS_SUB (`IS_SUB & `SPEC_RTYPE) +`define ISS_SLL (`IS_SLL & `SPEC_RTYPE) +`define ISS_SLT (`IS_SLT & `SPEC_RTYPE) +`define ISS_SLTU (`IS_SLTU & `SPEC_RTYPE) +`define ISS_XOR (`IS_XOR & `SPEC_RTYPE) +`define ISS_SRL (`IS_SRL & `SPEC_RTYPE) +`define ISS_SRA (`IS_SRA & `SPEC_RTYPE) +`define ISS_OR (`IS_OR & `SPEC_RTYPE) +`define ISS_AND (`IS_AND & `SPEC_RTYPE) + +`define IS_FENCETYPE(idx) ( \ + `INSTR[31:25] == 4'b0000 && `INSTR[19:15] == 5'b00000 && \ + `INSTR[11:0] == 12'b000000001111 && `INSTR[14:12] == idx) +`define IS_FENCE (`INSTR[31:28] == 4'b0 && `INSTR[19:0] == 20'b0001111) +`define IS_FENCEI (`INSTR == 32'h100f) + +`define ISS_FENCE (`IS_FENCE & `SPEC_FENCE) +`define ISS_FENCEI (`IS_FENCEI & `SPEC_FENCEI) + +`define IS_LOAD(idx) (`INSTR[6:0] == 7'b0000011 && `INSTR[14:12] == idx) +`define IS_LB `IS_LOAD(3'b000) +`define IS_LH `IS_LOAD(3'b001) +`define IS_LW `IS_LOAD(3'b010) +`define IS_LBU `IS_LOAD(3'b100) +`define IS_LHU `IS_LOAD(3'b101) + +`define ISS_LB (`IS_LB & `SPEC_LOAD) +`define ISS_LH (`IS_LH & `SPEC_LOAD) +`define ISS_LW (`IS_LW & `SPEC_LOAD) +`define ISS_LBU (`IS_LBU & `SPEC_LOAD) +`define ISS_LHU (`IS_LHU & `SPEC_LOAD) + +`define IS_STORE(idx) (`INSTR[6:0] == 7'b0100011 && `INSTR[14:12] == idx) +`define IS_SB `IS_STORE(3'b000) +`define IS_SH `IS_STORE(3'b001) +`define IS_SW `IS_STORE(3'b010) + +`define ISS_SB (`IS_SB & `SPEC_STORE) +`define ISS_SH (`IS_SH & `SPEC_STORE) +`define ISS_SW (`IS_SW & `SPEC_STORE) + +`define IS_JAL (`INSTR[6:0] == 7'h6f) +`define IS_JALR (`INSTR[6:0] == 7'h67 && `INSTR[14:12] == 3'b0) + +`define ISS_JAL (`IS_JAL & `SPEC_RISCV_JAL) +`define ISS_JALR (`IS_JALR & `SPEC_RISCV_JALR) + +`define IS_MTYPE(idx) \ + (`INSTR[6:0] == 7'b0110011 && `INSTR[31:25] == 7'b0000001 && `INSTR[14:12] == idx) +`define IS_MUL `IS_MTYPE(3'b000) +`define IS_MULH `IS_MTYPE(3'b001) +`define IS_MULHSH `IS_MTYPE(3'b010) +`define IS_MULHU `IS_MTYPE(3'b011) +`define IS_DIV `IS_MTYPE(3'b100) +`define IS_DIVU `IS_MTYPE(3'b101) +`define IS_REM `IS_MTYPE(3'b110) +`define IS_REMU `IS_MTYPE(3'b111) + +`define ISS_MUL (`IS_MUL & `SPEC_MUL) +`define ISS_MULH (`IS_MULH & `SPEC_MUL) +`define ISS_MULHSH (`IS_MULHSH & `SPEC_MUL) +`define ISS_MULHU (`IS_MULHU & `SPEC_MUL) +`define ISS_DIV (`IS_DIV & `SPEC_DIV) +`define ISS_DIVU (`IS_DIVU & `SPEC_DIV) +`define ISS_REM (`IS_REM & `SPEC_REM) +`define ISS_REMU (`IS_REMU & `SPEC_REM) + +`define IS_CSR (`INSTR[6:0] == 7'b1110011 && (|`INSTR[13:12])) +`define CSR_ADDR (`INSTR[31:20]) +`define ISS_CSR (`IS_CSR & `SPEC_CSR) + +`define IS_ECALL (`INSTR == 32'b00000000000000000000000001110011) +`define ISS_ECALL (`IS_ECALL & `SPEC_ECALL) + +`define IS_EBREAK (`INSTR == 32'b00000000000100000000000001110011) +`define ISS_EBREAK (`IS_EBREAK & `SPEC_EBREAK) + +`define IS_LUI (`INSTR[6:0] == 7'b0110111) +`define ISS_LUI (`IS_LUI & `SPEC_UTYPE) + +`define IS_AUIPC (`INSTR[6:0] == 7'b0010111) +`define ISS_AUIPC (`IS_AUIPC & `SPEC_UTYPE) + +`define IS_BTYPE (`INSTR[6:0] == 7'b1100011 && (`INSTR[13] -> `INSTR[14])) +`define ISS_BTYPE (`IS_BTYPE & `SPEC_BTYPE) + +`define IS_MRET (`INSTR == 32'b00110000001000000000000001110011) +`define ISS_MRET (`IS_MRET & `SPEC_MRET) + +`define IS_WFI (`INSTR == 32'b00010000010100000000000001110011) +`define ISS_WFI (`IS_WFI & `SPEC_WFI) + +`endif diff --git a/dv/formal/check/peek/abs.sv b/dv/formal/check/peek/abs.sv new file mode 100644 index 0000000000..145e17fa2c --- /dev/null +++ b/dv/formal/check/peek/abs.sv @@ -0,0 +1,107 @@ +// Copyright lowRISC contributors. +// Copyright 2024 University of Oxford, see also CREDITS.md. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// Original author: Louis-Emile Ploix +// SPDX-License-Identifier: Apache-2.0 + +/* +This file implements the abstract state for Ibex, it maps Ibex microarchitectural state to Sail architectural state. +pre_* is the pre_state, i.e. if an instruction is starting to run now, this is the architectural state that it observes. +post_* is the post_state, i.e. if an instruction starts in the next cycle, this is the architectural state that it will observe. +If this is wrong it does not matter, due to the wrap around proofs. +*/ + +/////////////// GPR Pre-State /////////////// + +assign pre_regs[0] = 32'h0; +for (genvar i = 1; i < 32; i++) begin: g_pre_regs + // Resolve forwarding + assign pre_regs[i] = (`CR.rf_write_wb && `CR.rf_waddr_wb == i) ? + `CR.rf_wdata_fwd_wb : `RF.rf_reg[i]; +end + +// FIXME: Redefined from ibex_cs_registers +typedef struct packed { + logic mie; + logic mpie; + priv_lvl_e mpp; + logic mprv; + logic tw; +} status_t; + +/////////////// CSR Pre-State /////////////// + +assign pre_pc = wbexc_handling_irq? `CR.pc_if:`CR.pc_id; +assign pre_nextpc = `CR.pc_id + (`CR.instr_is_compressed_id ? 2 : 4); +assign pre_priv = `CSR.priv_lvl_q == PRIV_LVL_M ? Machine : User; +assign pre_mstatus = '{ + mie: `CSR.mstatus_q.mie, + mpie: `CSR.mstatus_q.mpie, + tw: `CSR.mstatus_q.tw, + mprv: `CSR.mstatus_q.mprv, + mpp: `CSR.mstatus_q.mpp +}; +assign pre_mip = irqsToMInterrupts(`CSR.mip); +assign pre_mie = irqsToMInterrupts(`CSR.mie_q); +assign pre_mcause = widenMCause(`CSR.mcause_q); +assign pre_mtval = `CSR.mtval_q; +assign pre_mtvec = `CSR.mtvec_q; +assign pre_mscratch = `CSR.mscratch_q; +assign pre_mepc = `CSR.mepc_q; +assign pre_mcounteren = '0; // ibex hardwires to zero +assign pre_mseccfg = mseccfgToBits(`CSRP.pmp_mseccfg_q); +for (genvar i = 0; i < PMPNumRegions; i++) begin: g_pmp_pre + assign pre_pmp_cfg[i] = pmpCfgToBits(`CSRP.pmp_cfg[i]); + assign pre_pmp_addr[i] = `CSRP.pmp_addr[i]; +end + +/////////////// CSR Post-State /////////////// + +// Bit 0 of fetch addr n is always cleared. +assign post_pc = `IF.branch_req ? {`IF.fetch_addr_n[31:1], 1'b0} : `CSR.pc_if_i; +assign post_mie = `CSR.mie_en ? irqsToMInterrupts(`CSR.mie_d) : pre_mie; +assign post_priv = `CSR.priv_lvl_d == PRIV_LVL_M ? Machine : User; +assign post_mstatus = `CSR.mstatus_en ? '{ + mie: `CSR.mstatus_d.mie, + mpie: `CSR.mstatus_d.mpie, + tw: `CSR.mstatus_d.tw, + mprv: `CSR.mstatus_d.mprv, + mpp: `CSR.mstatus_d.mpp +}:pre_mstatus; +assign post_mcause = `CSR.mcause_en ? widenMCause(`CSR.mcause_d) : pre_mcause; +assign post_mtval = `CSR.mtval_en ? `CSR.mtval_d : pre_mtval; +assign post_mtvec = `CSR.mtvec_en ? `CSR.mtvec_d : pre_mtvec; +assign post_mepc = `CSR.mepc_en ? `CSR.mepc_d : pre_mepc; +assign post_mscratch = `CSR.mscratch_en? `CSR.csr_wdata_int : pre_mscratch; +assign post_mcounteren = '0; +assign post_mseccfg = `CSRP.pmp_mseccfg_we ? mseccfgToBits(`CSRP.pmp_mseccfg_d) : pre_mseccfg; +for (genvar i = 0; i < PMPNumRegions; i++) begin: g_pmp_post + assign post_pmp_cfg[i] = + `CSRP.pmp_cfg_we[i] ? pmpCfgToBits(`CSRP.pmp_cfg_wdata[i]) : pre_pmp_cfg[i]; + assign post_pmp_addr[i] = `CSRP.pmp_addr_we[i] ? `CSR.csr_wdata_int : pre_pmp_addr[i]; +end + +/////////////// Encoders /////////////// + +function automatic logic [7:0] pmpCfgToBits(pmp_cfg_t cfg); + return {cfg.lock, 2'b0, cfg.mode, cfg.exec, cfg.write, cfg.read}; +endfunction + +function automatic logic [31:0] mseccfgToBits(pmp_mseccfg_t mseccfg); + return {29'h0, mseccfg.rlb, mseccfg.mmwp, mseccfg.mml}; +endfunction + +function automatic logic [31:0] irqsToMInterrupts(irqs_t irqs); + // Sail has no notion of fast interrupts + return + (32'(irqs.irq_software) << 3) | + (32'(irqs.irq_timer) << 7) | + (32'(irqs.irq_external) << 11); +endfunction + +function automatic logic[31:0] widenMCause(exc_cause_t mcause); + // Intneral exceptions are not specified, maybe we should just ignore them? + return {mcause.irq_ext | mcause.irq_int, + mcause.irq_int ? {26{1'b1}} : 26'b0, + mcause.lower_cause[4:0]}; +endfunction diff --git a/dv/formal/check/peek/alt_lsu.sv b/dv/formal/check/peek/alt_lsu.sv new file mode 100644 index 0000000000..fffc9a8de9 --- /dev/null +++ b/dv/formal/check/peek/alt_lsu.sv @@ -0,0 +1,279 @@ +// Copyright lowRISC contributors. +// Copyright 2018 ETH Zurich and University of Bologna, see also CREDITS.md. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + + +/** + * Load Store Unit + * + * Load Store Unit, used to eliminate multiple access during processor stalls, + * and to align bytes and halfwords. + */ + +`include "prim_assert.sv" +`include "dv_fcov_macros.svh" + +module alt_lsu #( + parameter bit MemECC = 1'b0, + parameter int unsigned MemDataWidth = MemECC ? 32 + 7 : 32 +) ( + input logic clk_i, + input logic rst_ni, + + // data interface + output logic data_req_o, + input logic data_gnt_i, + input logic data_rvalid_i, + input logic data_bus_err_i, + input logic data_pmp_err_i, + + output logic [31:0] data_addr_o, + output logic data_we_o, + output logic [3:0] data_be_o, + output logic [MemDataWidth-1:0] data_wdata_o, + input logic [MemDataWidth-1:0] data_rdata_i, + + // signals to/from ID/EX stage + input logic lsu_we_i, // write enable -> from ID/EX + input logic [1:0] lsu_type_i, // data type: word, half word, byte -> from ID/EX + input logic [31:0] lsu_wdata_i, // data to write to memory -> from ID/EX + input logic lsu_sign_ext_i, // sign extension -> from ID/EX + + output logic [31:0] lsu_rdata_o, // requested data -> to ID/EX + output logic lsu_rdata_valid_o, + input logic lsu_req_i, // data request -> from ID/EX + + input logic [31:0] adder_result_ex_i, // address computed in ALU -> from ID/EX + + output logic addr_incr_req_o, // request address increment for + // misaligned accesses -> to ID/EX + output logic [31:0] addr_last_o, // address of last transaction -> to controller + // -> mtval + // -> AGU for misaligned accesses + + output logic lsu_req_done_o, // Signals that data request is complete + // (only need to await final data + // response) -> to ID/EX + + output logic lsu_resp_valid_o, // LSU has response from transaction -> to ID/EX + + // exception signals + output logic load_err_o, + output logic load_reXsp_intg_err_o, + output logic store_err_o, + output logic store_resp_intg_err_o, + + output logic busy_o, + + output logic perf_load_o, + output logic perf_store_o, + + input logic [31:0] addr_last_q, + input logic [31:8] rdata_q, + input logic [1:0] rdata_offset_q, + input logic [1:0] data_type_q, + input logic data_sign_ext_q, + input logic data_we_q, + input logic handle_misaligned_q, + input logic pmp_err_q, + input logic lsu_err_q +); + + logic [31:0] data_addr; + logic [31:0] data_addr_w_aligned; + logic [31:0] addr_last_d; + + logic addr_update; + logic ctrl_update; + logic rdata_update; + + logic [1:0] data_offset; // mux control for data to be written to memory + + logic [3:0] data_be; + logic [31:0] data_wdata; + + logic [31:0] data_rdata_ext; + + logic [31:0] rdata_w_ext; // word realignment for misaligned loads + logic [31:0] rdata_h_ext; // sign extension for half words + logic [31:0] rdata_b_ext; // sign extension for bytes + + logic split_misaligned_access; + logic handle_misaligned_d; // high after receiving grant for first + // part of a misaligned access + logic pmp_err_d; + logic lsu_err_d; + logic data_intg_err, data_or_pmp_err; + + assign data_addr = adder_result_ex_i; + assign data_offset = data_addr[1:0]; + + // Store last address for mtval + AGU for misaligned transactions. Do not update in case of + // errors, mtval needs the (first) failing address. Where an aligned access or the first half of + // a misaligned access sees an error provide the calculated access address. For the second half of + // a misaligned access provide the word aligned address of the second half. + assign addr_last_d = addr_incr_req_o ? data_addr_w_aligned : data_addr; + + // take care of misaligned words + always_comb begin + unique case (rdata_offset_q) + 2'b00: rdata_w_ext = data_rdata_i[31:0]; + 2'b01: rdata_w_ext = {data_rdata_i[ 7:0], rdata_q[31:8]}; + 2'b10: rdata_w_ext = {data_rdata_i[15:0], rdata_q[31:16]}; + 2'b11: rdata_w_ext = {data_rdata_i[23:0], rdata_q[31:24]}; + default: rdata_w_ext = data_rdata_i[31:0]; + endcase + end + + //////////////////// + // Sign extension // + //////////////////// + + // sign extension for half words + always_comb begin + unique case (rdata_offset_q) + 2'b00: begin + if (!data_sign_ext_q) begin + rdata_h_ext = {16'h0000, data_rdata_i[15:0]}; + end else begin + rdata_h_ext = {{16{data_rdata_i[15]}}, data_rdata_i[15:0]}; + end + end + + 2'b01: begin + if (!data_sign_ext_q) begin + rdata_h_ext = {16'h0000, data_rdata_i[23:8]}; + end else begin + rdata_h_ext = {{16{data_rdata_i[23]}}, data_rdata_i[23:8]}; + end + end + + 2'b10: begin + if (!data_sign_ext_q) begin + rdata_h_ext = {16'h0000, data_rdata_i[31:16]}; + end else begin + rdata_h_ext = {{16{data_rdata_i[31]}}, data_rdata_i[31:16]}; + end + end + + 2'b11: begin + if (!data_sign_ext_q) begin + rdata_h_ext = {16'h0000, data_rdata_i[7:0], rdata_q[31:24]}; + end else begin + rdata_h_ext = {{16{data_rdata_i[7]}}, data_rdata_i[7:0], rdata_q[31:24]}; + end + end + + default: rdata_h_ext = {16'h0000, data_rdata_i[15:0]}; + endcase // case (rdata_offset_q) + end + + // sign extension for bytes + always_comb begin + unique case (rdata_offset_q) + 2'b00: begin + if (!data_sign_ext_q) begin + rdata_b_ext = {24'h00_0000, data_rdata_i[7:0]}; + end else begin + rdata_b_ext = {{24{data_rdata_i[7]}}, data_rdata_i[7:0]}; + end + end + + 2'b01: begin + if (!data_sign_ext_q) begin + rdata_b_ext = {24'h00_0000, data_rdata_i[15:8]}; + end else begin + rdata_b_ext = {{24{data_rdata_i[15]}}, data_rdata_i[15:8]}; + end + end + + 2'b10: begin + if (!data_sign_ext_q) begin + rdata_b_ext = {24'h00_0000, data_rdata_i[23:16]}; + end else begin + rdata_b_ext = {{24{data_rdata_i[23]}}, data_rdata_i[23:16]}; + end + end + + 2'b11: begin + if (!data_sign_ext_q) begin + rdata_b_ext = {24'h00_0000, data_rdata_i[31:24]}; + end else begin + rdata_b_ext = {{24{data_rdata_i[31]}}, data_rdata_i[31:24]}; + end + end + + default: rdata_b_ext = {24'h00_0000, data_rdata_i[7:0]}; + endcase // case (rdata_offset_q) + end + + // select word, half word or byte sign extended version + always_comb begin + unique case (data_type_q) + 2'b00: data_rdata_ext = rdata_w_ext; + 2'b01: data_rdata_ext = rdata_h_ext; + 2'b10,2'b11: data_rdata_ext = rdata_b_ext; + default: data_rdata_ext = rdata_w_ext; + endcase // case (data_type_q) + end + + /////////////////////////////// + // Read data integrity check // + /////////////////////////////// + + assign data_intg_err = 1'b0; + + ///////////// + // LSU FSM // + ///////////// + + // check for misaligned accesses that need to be split into two word-aligned accesses + assign split_misaligned_access = + ((lsu_type_i == 2'b00) && (data_offset != 2'b00)) || // misaligned word access + ((lsu_type_i == 2'b01) && (data_offset == 2'b11)); // misaligned half-word access + + ///////////// + // Outputs // + ///////////// + + assign data_or_pmp_err = lsu_err_q | data_bus_err_i | pmp_err_q; + assign lsu_resp_valid_o = (data_rvalid_i | pmp_err_q); + assign lsu_rdata_valid_o = + data_rvalid_i & ~data_or_pmp_err & ~data_we_q & ~data_intg_err; + + // output to register file + assign lsu_rdata_o = data_rdata_ext; + + // output data address must be word aligned + assign data_addr_w_aligned = {data_addr[31:2], 2'b00}; + + // output to data interface + assign data_addr_o = data_addr_w_aligned; + assign data_we_o = lsu_we_i; + assign data_be_o = data_be; + + ///////////////////////////////////// + // Write data integrity generation // + ///////////////////////////////////// + + // SEC_CM: BUS.INTEGRITY + assign data_wdata_o = data_wdata; + + // output to ID stage: mtval + AGU for misaligned transactions + assign addr_last_o = addr_last_q; + + // Signal a load or store error depending on the transaction type outstanding + assign load_err_o = data_or_pmp_err & ~data_we_q & lsu_resp_valid_o; + assign store_err_o = data_or_pmp_err & data_we_q & lsu_resp_valid_o; + // Integrity errors are their own category for timing reasons. load_err_o is factored directly + // into data_req_o to enable synchronous exception on load errors without performance loss (An + // upcoming load cannot request until the current load has seen its response, so the earliest + // point the new request can be sent is the same cycle the response is seen). If load_err_o isn't + // factored into data_req_o there would have to be a stall cycle between all back to back loads. + // The data_intg_err signal is generated combinatorially from the incoming data_rdata_i. Were it + // to be factored into load_err_o there would be a feedthrough path from data_rdata_i to + // data_req_o which is undesirable. + assign load_resp_intg_err_o = data_intg_err & data_rvalid_i & ~data_we_q; + assign store_resp_intg_err_o = data_intg_err & data_rvalid_i & data_we_q; +endmodule diff --git a/dv/formal/check/peek/compare_helper.sv b/dv/formal/check/peek/compare_helper.sv new file mode 100644 index 0000000000..9b359e6f0d --- /dev/null +++ b/dv/formal/check/peek/compare_helper.sv @@ -0,0 +1,126 @@ +// Copyright lowRISC contributors. +// Copyright 2024 University of Oxford, see also CREDITS.md. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// Original author: Louis-Emile Ploix +// SPDX-License-Identifier: Apache-2.0 + +/* +These are various signals used in assertions. A lot of it is just convenient instruction checks, +some of it is checking spec conformance. +*/ + +`define INSTR wbexc_decompressed_instr + +assign wbexc_is_pres_load_instr = `ISS_LB | `ISS_LBU | `ISS_LH | `ISS_LHU | `ISS_LW; +assign wbexc_is_pres_store_instr = `ISS_SB | `ISS_SH | `ISS_SW; +assign wbexc_is_pres_mem_instr = wbexc_is_pres_load_instr | wbexc_is_pres_store_instr; + +assign wbexc_is_load_instr = `IS_LB | `IS_LBU | `IS_LH | `IS_LHU | `IS_LW; +assign wbexc_is_store_instr = `IS_SB | `IS_SH | `IS_SW; + +assign wbexc_is_mem_instr = `IS_LB | `IS_LBU | `IS_SB | + `IS_LH | `IS_LHU | `IS_SH | + `IS_LW | `IS_SW; +assign wbexc_is_wfi = `IS_WFI & ~wbexc_fetch_err; +`undef INSTR + +`define INSTR `CR.instr_rdata_id +assign ex_is_load_instr = `IS_LB | `IS_LBU | `IS_LH | `IS_LHU | `IS_LW; +assign ex_is_store_instr = `IS_SB | `IS_SH | `IS_SW; +assign ex_is_mem_instr = ex_is_load_instr | ex_is_store_instr; + +assign ex_is_pres_load_instr = `ISS_LB | `ISS_LBU | `ISS_LH | `ISS_LHU | `ISS_LW; +assign ex_is_pres_store_instr = `ISS_SB | `ISS_SH | `ISS_SW; +assign ex_is_pres_mem_instr = ex_is_pres_load_instr | ex_is_pres_store_instr; + +assign ex_is_pres_btype = `ISS_BTYPE; +assign ex_is_pres_jump = `ISS_JAL | `ISS_JALR; +assign ex_is_wfi = `IS_WFI; +assign ex_is_rtype = `IS_ADD | `IS_SUB | `IS_SLL | `IS_SLT | `IS_SLTU | `IS_XOR | `IS_SRL | `IS_SRA | `IS_OR | `IS_AND; +assign ex_is_div = `IS_DIV | `IS_DIVU | `IS_REM | `IS_REMU; +`undef INSTR + +// real_write checks that there is a write and the dest reg is not x0 +logic dut_real_write, spec_real_write; +assign dut_real_write = `WB.rf_we_wb_o & (|`WB.rf_waddr_wb_o); +assign spec_real_write = wbexc_post_wX_en & (|wbexc_post_wX_addr); + +assign addr_match = dut_real_write == spec_real_write && + (spec_real_write -> `WB.rf_waddr_wb_o == wbexc_post_wX_addr); +assign data_match = (spec_real_write && dut_real_write) -> wbexc_post_wX == `WB.rf_wdata_wb_o; + +assign finishing_executed = wbexc_finishing & ~wbexc_fetch_err; + +/* +Select CSRs based on the current state of the follower, in particular: +- In most cases we compare the CSRs that were about to written at the end of ID/EX to the CSRs from the spec also + from that time. It's unfortunate to do that now but not a huge hassle. +- In the case of an exception or WFI we compare the CSRs about to be written now with the CSRs from the spec at the end of ID/EX +- In the case of an IRQ we compare the the CSRs about to be written now with the CSRs from the spec now +*/ + +`define INSTR `CR.instr_rdata_id + +logic use_curr_dut, use_curr_spec; +assign use_curr_dut = wbexc_err | wbexc_handling_irq; +assign use_curr_spec = wbexc_handling_irq; + +`define X(c) assign wbexc_dut_cmp_post_``c = use_curr_dut ? post_``c : wbexc_dut_post_``c; +`X_EACH_CSR +`undef X + +`define X(c) assign wbexc_spec_cmp_post_``c = use_curr_spec ? spec_post_``c : wbexc_post_``c; +`X_EACH_CSR +`undef X + +`define X_DISABLE_PC +`define X(c) wbexc_dut_cmp_post_``c == wbexc_spec_cmp_post_``c && +assign csrs_match = `X_EACH_CSR 1; +`undef X +`undef X_DISABLE_PC + +assign pc_match = wbexc_dut_cmp_post_pc == wbexc_spec_cmp_post_pc; + +`define X_DISABLE_PC +assign csrs_didnt_change = +`define X(c) pre_``c == wbexc_dut_post_``c && + `X_EACH_CSR +`undef X + 1; +`undef X_DISABLE_PC + +`define X_DISABLE_PC +assign ex_csrs_match = +`define X(c) post_``c == spec_post_``c && + `X_EACH_CSR +`undef X + 1; +`undef X_DISABLE_PC + +assign csrs_match_non_exc = +`define X(c) post_``c == wbexc_post_``c && + `X(mstatus.mprv) + `X(mstatus.tw) + `X(mie) + `X(mtvec) + `X(mscratch) + `X(mcounteren) + `X(pmp_cfg) + `X(pmp_addr) + `X(mseccfg) +`undef X + 1; + +assign ex_csrs_match_non_exc = +`define X(c) post_``c == spec_post_``c && + `X(mstatus.mprv) + `X(mstatus.tw) + `X(mie) + `X(mtvec) + `X(mscratch) + `X(mcounteren) + `X(pmp_cfg) + `X(pmp_addr) + `X(mseccfg) +`undef X + 1; diff --git a/dv/formal/check/peek/follower.sv b/dv/formal/check/peek/follower.sv new file mode 100644 index 0000000000..ff40c27d3f --- /dev/null +++ b/dv/formal/check/peek/follower.sv @@ -0,0 +1,132 @@ +// Copyright lowRISC contributors. +// Copyright 2024 University of Oxford, see also CREDITS.md. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// Original author: Louis-Emile Ploix +// SPDX-License-Identifier: Apache-2.0 + +/* +The following implements the pipeline follower. It is pretty simple since the pipeline is short. + +The top of this file contains signals to indicate when an instruction moves from a pipeline stage, +and when it does so on what terms. + +We record both the spec post state and the implementation CSR post state as soon as the instruction moves on from ID/EX. +The latter is stored so that we can make comparisons at the same time, independent of when the instruction retires. We +probably could do this earlier in the cases where no exception occurs, but that would require special treatment for +exception vs non-exception cases. In fairness there is already a difference, but that's only a case split which can be unified. +*/ + +`define INSTR `CR.instr_rdata_id + +// These control signals are all extremely specific and probably very fragile. + +assign ex_success = `ID.instr_done; +assign ex_err = `IDC.exc_req_d; +assign ex_kill = `ID.wb_exception | ~`ID.controller_run; +// Note that this only kills instructions because e.g. of a jump ahead of it or an exception + +assign exc_finishing = `IDC.ctrl_fsm_cs == `ID.controller_i.FLUSH; +assign wbexc_handling_irq = `IDC.ctrl_fsm_cs == `ID.controller_i.IRQ_TAKEN; + +assign wb_finishing = wbexc_is_wfi? wfi_will_finish:`CR.instr_done_wb; + +assign wfi_will_finish = `IDC.ctrl_fsm_cs == `ID.controller_i.FLUSH; + +assign wbexc_err = wbexc_ex_err | + `IDC.wb_exception_o | + ((`IDC.ctrl_fsm_cs == `ID.controller_i.FLUSH) & ~wbexc_csr_pipe_flush); + // CSR pipe flushes don't count as exceptions + +assign wbexc_finishing = + wbexc_exists & (wbexc_err ? exc_finishing : wb_finishing); + +assign instr_will_progress = (~wbexc_exists | wbexc_finishing) & ~ex_kill & (ex_success | ex_err); + +always_comb begin + if (`CR.instr_new_id) begin + ex_has_branched_d = 1'b0; + end else begin + ex_has_branched_d = ex_has_branched_q; + end + + ex_has_branched_d = (ex_has_branched_d | `IF.branch_req) && ~ex_kill && (`IDC.ctrl_fsm_cs == `IDC.DECODE); +end + +always @(posedge clk_i or negedge rst_ni) begin + if (~rst_ni) begin + wbexc_exists <= 1'b0; + ex_has_compressed_instr <= 1'b0; + ex_has_branched_q <= 1'b0; + wbexc_csr_pipe_flush <= 1'b0; + end else begin + if (wbexc_finishing) begin + wbexc_exists <= 1'b0; + end + + ex_has_branched_q <= ex_has_branched_d; + if (instr_will_progress) begin + ex_has_branched_q <= 1'b0; + wbexc_post_wX <= spec_post_wX; + wbexc_post_wX_addr <= spec_post_wX_addr; + wbexc_post_wX_en <= spec_post_wX_en; + + `define X(n) wbexc_post_``n <= spec_post_``n; + `X_EACH_CSR + `undef X + + `define X(n) wbexc_dut_post_``n <= post_``n; + `X_EACH_CSR + `undef X + + wbexc_instr <= ex_compressed_instr; + wbexc_decompressed_instr <= `CR.instr_rdata_id; + wbexc_compressed_illegal <= `CR.illegal_c_insn_id; + wbexc_exists <= 1'b1; + wbexc_ex_err <= ex_err; + wbexc_fetch_err <= `ID.instr_fetch_err_i; + wbexc_post_int_err <= spec_int_err; + wbexc_illegal <= `CR.illegal_insn_id; + wbexc_pc <= `CR.pc_id; + wbexc_csr_pipe_flush <= `IDC.csr_pipe_flush; + wbexc_is_checkable_csr <= ex_is_checkable_csr; + + wbexc_spec_mem_read_fst_rdata <= spec_mem_read_fst_rdata; + wbexc_spec_mem_read_snd_rdata <= spec_mem_read_snd_rdata; + wbexc_mem_had_snd_req <= mem_gnt_snd_q | mem_gnt_snd_d; + end + + if (`IF.if_id_pipe_reg_we) begin + ex_compressed_instr <= `IF.if_instr_rdata; + ex_has_compressed_instr <= 1'b1; + end + end +end + +assign spec_en = wbexc_handling_irq | instr_will_progress; +// The definition of spec_en doesn't matter so long as it's live (which it is since +// instr will progress is live), so long as the necessary wraparound properties prove. + +always @(posedge clk_i) begin + if (~rst_ni) begin + has_spec_past = 1'b0; + spec_past_has_reg = 32'b0; + end else if (spec_en) begin + has_spec_past = `IS_CSR -> ex_is_checkable_csr; + + if (spec_post_wX_en) begin + spec_past_regs[spec_post_wX_addr] = spec_post_wX; + spec_past_has_reg[spec_post_wX_addr] = 1'b1; + end + if (`IS_CSR & ~ex_is_checkable_csr) begin + // Clear out everything, since we don't know what has been written to any more + // We could be stricter but there's little point in doing so. + spec_past_has_reg = 32'b0; + end + + `define X(n) spec_past_``n = spec_post_``n; + `X_EACH_CSR + `undef X + end + end + +`undef INSTR diff --git a/dv/formal/check/peek/mem.sv b/dv/formal/check/peek/mem.sv new file mode 100644 index 0000000000..c0a6e6833b --- /dev/null +++ b/dv/formal/check/peek/mem.sv @@ -0,0 +1,122 @@ +// Copyright lowRISC contributors. +// Copyright 2024 University of Oxford, see also CREDITS.md. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// Original author: Louis-Emile Ploix +// SPDX-License-Identifier: Apache-2.0 + +/* +This file implements the tracking of ibex memory. It (by assumption) sets data_rvalid_i to the corresponding value that was +sent to the spec. The interpretation of the signals used is justified in the proofs. +*/ + +logic has_snd_req; +assign has_snd_req = mem_gnt_snd_d | (mem_gnt_fst_q & `CR.data_req_out); + +logic outstanding_resp; +assign outstanding_resp = `CR.outstanding_load_wb | `CR.outstanding_store_wb; + +/* +Some definitions: +- An 'early response' is a response made to a memory instruction before the instruction has progressed to the WB stage, + i.e. while it is still in the ID/EX stage. This is only possible for multi-request instructions, and only one + early response can be made. +- A 'late response' is a response received during the WB stage. There can be at most one late response, since + if a memory instruction sends two requests, it won't go to WB until it has sent one request. +Responses are given in the order they are requested, so it suffices to check if WB is waiting on a response. +*/ +logic early_resp, late_resp; +assign early_resp = data_rvalid_i & ~outstanding_resp; +assign late_resp = data_rvalid_i & outstanding_resp; + +// If we receive an early response, give it the data sent to the spec directly +LoadMemEarlyResp: assume property ( + early_resp |-> data_rdata_i == spec_mem_read_fst_rdata +); + +// If we have a late response, and this is the first response send the low spec data +LoadMemLateRespFirst: assume property ( + late_resp & ~wbexc_mem_had_snd_req |-> data_rdata_i == wbexc_spec_mem_read_fst_rdata +); + +// If we have a late response, and this is the second response, send the high spec data +LoadMemLateRespSecond: assume property ( + late_resp & wbexc_mem_had_snd_req |-> data_rdata_i == wbexc_spec_mem_read_snd_rdata +); + +// The spec read data cannot change while an instruction is running, only when there is a new one +// Note these values are undriven, they are not from the spec as the name would suggest +SpecReadDataKeep: assume property (~instr_will_progress |=> + spec_mem_read_fst_rdata == $past(spec_mem_read_fst_rdata) && + spec_mem_read_snd_rdata == $past(spec_mem_read_snd_rdata) +); + +// Tracks what requests/grants have been since instr_will_progress (and thus spec_en). +// It's not dependent on any internal signal besides instr_will_progress, which implies +// spec_en, so is 'safe'. +assign mem_gnt_fst_d = mem_gnt_fst_q | data_gnt_i; +assign mem_gnt_snd_d = mem_gnt_snd_q | (data_gnt_i & mem_gnt_fst_q); + +assign mem_req_fst_d = data_req_o & ~mem_gnt_fst_q; +assign mem_req_snd_d = data_req_o & mem_gnt_fst_q; + +always @(posedge clk_i or negedge rst_ni) begin + if (~rst_ni | instr_will_progress) begin + mem_gnt_fst_q <= 1'b0; + mem_gnt_snd_q <= 1'b0; + end else begin + mem_gnt_fst_q <= mem_gnt_fst_d; + mem_gnt_snd_q <= mem_gnt_snd_d; + end +end + +`define ALT_LSU_STATE_COPY \ + .data_rvalid_i(1'b1), \ + .data_bus_err_i(1'b0), \ + .data_we_q(`LSU.data_we_q), \ + .handle_misaligned_q(`LSU.handle_misaligned_q), \ + .pmp_err_q(`LSU.pmp_err_q), \ + .lsu_err_q(`LSU.lsu_err_q), \ + +alt_lsu alt_lsu_late_i ( + .data_rdata_i( + ~wbexc_mem_had_snd_req ? + wbexc_spec_mem_read_fst_rdata : + wbexc_spec_mem_read_snd_rdata + ), + `ALT_LSU_STATE_COPY + .data_type_q(`LSU.data_type_q), + .rdata_offset_q(`LSU.rdata_offset_q), + .data_sign_ext_q(`LSU.data_sign_ext_q), + .rdata_q(`LSU.rdata_q) +); + +logic [31:0] alt_lsu_late_res; +assign alt_lsu_late_res = alt_lsu_late_i.lsu_rdata_o; + +alt_lsu alt_lsu_very_early_i ( + .data_rdata_i( + ~`LSU.split_misaligned_access ? + spec_mem_read_fst_rdata : + spec_mem_read_snd_rdata + ), + `ALT_LSU_STATE_COPY + .data_type_q(mem_gnt_fst_q ? `LSU.data_type_q : `LSU.lsu_type_i), + .data_sign_ext_q(mem_gnt_fst_q ? `LSU.data_sign_ext_q : `LSU.lsu_sign_ext_i), + .rdata_offset_q(mem_gnt_fst_q ? `LSU.rdata_offset_q : `LSU.data_offset), + .rdata_q(spec_mem_read_fst_rdata[31:8]) +); + +logic [31:0] alt_lsu_very_early_res; +assign alt_lsu_very_early_res = alt_lsu_very_early_i.lsu_rdata_o; + +alt_lsu alt_lsu_early_i ( + .data_rdata_i(spec_mem_read_snd_rdata), + `ALT_LSU_STATE_COPY + .data_type_q(`LSU.data_type_q), + .data_sign_ext_q(`LSU.data_sign_ext_q), + .rdata_offset_q(`LSU.rdata_offset_q), + .rdata_q(`LSU.rdata_q) +); + +logic [31:0] alt_lsu_early_res; +assign alt_lsu_early_res = alt_lsu_early_i.lsu_rdata_o; diff --git a/dv/formal/check/protocol/irqs.sv b/dv/formal/check/protocol/irqs.sv new file mode 100644 index 0000000000..d1d9204f5d --- /dev/null +++ b/dv/formal/check/protocol/irqs.sv @@ -0,0 +1,26 @@ +// Copyright lowRISC contributors. +// Copyright 2024 University of Oxford, see also CREDITS.md. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// Original author: Louis-Emile Ploix +// SPDX-License-Identifier: Apache-2.0 + +/* +The following give the protocol that IRQs must follow. +*/ + +// The Sail does not specify either of these +NoNMI: assume property (~irq_nm_i); +NoFastIRQ: assume property (~(|irq_fast_i)); + +// IRQs must remain active until they are cleared by some MMIO memory request +// The alternative is that IRQs disappear after just one cycle or something +MIPFair: assume property ( + (|`CSR.mip) |=> + $past(`CSR.mip) == ($past(`CSR.mip) & `CSR.mip) || data_gnt_i +); + +`define WFI_BOUND 20 +// If we are asleep we must eventually wake up. This is validated by the WFIStart assumption, +// which ensures that this is actually possible. We make the time bounded instead of s_eventually +// for conclusivity purposes. This can be commented out if you don't care about liveness. +WFIWakeUp: assume property (`IDC.ctrl_fsm_cs == SLEEP |-> ##[0:`WFI_BOUND] `IDC.irq_pending_i); diff --git a/dv/formal/check/protocol/mem.sv b/dv/formal/check/protocol/mem.sv new file mode 100644 index 0000000000..5142563a20 --- /dev/null +++ b/dv/formal/check/protocol/mem.sv @@ -0,0 +1,49 @@ +// Copyright lowRISC contributors. +// Copyright 2024 University of Oxford, see also CREDITS.md. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// Original author: Louis-Emile Ploix +// SPDX-License-Identifier: Apache-2.0 + +/* +The following describes the protocol for the memory interface to Ibex, as defined by the documentation. + +In this case all responses must be within bounded time (`TIME_BOUND cycles). Removing the bound +leaves some properties inconclusive. +*/ + +// Sail does not specify these I don't think +NoDataErr: assume property (~data_err_i); +NoInstrErr: assume property (~instr_err_i); +`define TIME_LIMIT 5 + +interface mem_assume_t( + input logic req_o, + input logic gnt_i, + input logic rvalid_i +); + logic [7:0] outstanding_reqs_q; + logic [7:0] outstanding_reqs; + assign outstanding_reqs = outstanding_reqs_q + gnt_i - rvalid_i; + + always @(posedge clk_i or negedge rst_ni) begin + outstanding_reqs_q <= rst_ni ? outstanding_reqs : 0; + end + + // 1. Only send an rvalid if there is an outstanding request, but not in this cycle + MemValidBounded: assume property (outstanding_reqs_q == 8'b0 |-> ~rvalid_i); + // 2. Grants can only be sent when they are requested + MemGntOnRequest: assume property (~req_o |-> ~gnt_i); + + // Grants must respond within TIME_LIMIT cycles + GntBound: assume property (req_o |-> ##[0:`TIME_LIMIT] gnt_i); + + // RValid takes no more than TIME_LIMIT cycles + MemValidTimer: assume property (outstanding_reqs != 0 |-> ##[0:`TIME_LIMIT] rvalid_i); + + // Responses have to come eventually, implied by the above bounds so removed + // MemGntEventually: assume property (req_o |-> s_eventually gnt_i); + // MemRespEventually: assume property (always (s_eventually (outstanding_reqs == 8'b0))); +endinterface + +mem_assume_t instr_mem_assume(instr_req_o, instr_gnt_i, instr_rvalid_i); +mem_assume_t data_mem_assume(data_req_o, data_gnt_i, data_rvalid_i); diff --git a/dv/formal/check/spec_instance.sv b/dv/formal/check/spec_instance.sv new file mode 100644 index 0000000000..33f52c65de --- /dev/null +++ b/dv/formal/check/spec_instance.sv @@ -0,0 +1,70 @@ +// Copyright lowRISC contributors. +// Copyright 2024 University of Oxford, see also CREDITS.md. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// Original author: Louis-Emile Ploix +// SPDX-License-Identifier: Apache-2.0 + +/* +The following instantiates the specification via the spec api. It also chooses the mode for the spec to run in (see main.sail +for more on that). + +It just wires up the pre_* state to the spec_post* state. +*/ + +t_MainMode main_mode; + +always_comb begin + if (wbexc_handling_irq) main_mode = MAIN_IRQ; + else if (`ID.instr_fetch_err_i) main_mode = MAIN_IFERR; + else main_mode = MAIN_IDEX; +end + +spec_api #( + .NREGS(32) +) spec_api_i ( + .int_err_o(spec_int_err), + .main_mode(main_mode), + + .insn_bits(ex_compressed_instr), + + .rx_a_en_o(spec_rx_a_en), + .rx_a_addr_o(spec_rx_a_addr), + .rx_a_i(spec_rx_a), + .rx_b_en_o(spec_rx_b_en), + .rx_b_addr_o(spec_rx_b_addr), + .rx_b_i(spec_rx_b), + + .wx_o(spec_post_wX), + .wx_addr_o(spec_post_wX_addr), + .wx_en_o(spec_post_wX_en), + + .mvendor_id_i(CSR_MVENDORID_VALUE), + .march_id_i(CSR_MARCHID_VALUE), + .mimp_id_i(CSR_MIMPID_VALUE), + .mhart_id_i(hart_id_i), + .mconfigptr_i(CSR_MCONFIGPTR_VALUE), + + .misa_i(`CSR.MISA_VALUE), + .mip_i(pre_mip), + .nextpc_i(pre_nextpc), + + `define X(n) .n``_i(pre_``n), .n``_o(spec_post_``n), + `X_EACH_CSR + `undef X + + .mem_read_o(spec_mem_read), + .mem_read_snd_gran_o(spec_mem_read_snd), + .mem_read_fst_addr_o(spec_mem_read_fst_addr), + .mem_read_snd_addr_o(spec_mem_read_snd_addr), + .mem_read_fst_rdata_i(spec_mem_read_fst_rdata), + .mem_read_snd_rdata_i(spec_mem_read_snd_rdata), + + .mem_write_o(spec_mem_write), + .mem_write_snd_gran_o(spec_mem_write_snd), + .mem_write_fst_addr_o(spec_mem_write_fst_addr), + .mem_write_snd_addr_o(spec_mem_write_snd_addr), + .mem_write_fst_wdata_o(spec_mem_write_fst_wdata), + .mem_write_snd_wdata_o(spec_mem_write_snd_wdata), + .mem_write_fst_be_o(spec_mem_write_fst_be), + .mem_write_snd_be_o(spec_mem_write_snd_be) +); diff --git a/dv/formal/check/top.sv b/dv/formal/check/top.sv new file mode 100644 index 0000000000..07253d6719 --- /dev/null +++ b/dv/formal/check/top.sv @@ -0,0 +1,475 @@ +// Copyright lowRISC contributors. +// Copyright 2024 University of Oxford, see also CREDITS.md. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// Original author: Louis-Emile Ploix +// SPDX-License-Identifier: Apache-2.0 + +/* +This is the top module. Everything else in check/* will be included into this file. +This module contains the ibex instance, the specification instance and the assertions +(included via the built psgen file), most of the non assertion code is setting up +infrastructure for those assertions. + +It has the same ports as the ibex top. +*/ + +// Preprocessor decoding of instructions. Could be replaced with internal signals of the Sail one day +`include "encodings.sv" + +`define CR ibex_top_i.u_ibex_core +`define CSR `CR.cs_registers_i +`define CSRG `CSR.gen_scr +`define CSRP `CSR.g_pmp_registers +`define LSU `CR.load_store_unit_i +`define ID `CR.id_stage_i +`define IDG `ID.gen_stall_mem +`define IDC `ID.controller_i +`define WB `CR.wb_stage_i +`define WBG `WB.g_writeback_stage +`define RF ibex_top_i.gen_regfile_ff.register_file_i +`define IF `CR.if_stage_i +`define IFP `IF.gen_prefetch_buffer.prefetch_buffer_i +`define MULT `CR.ex_block_i.gen_multdiv_fast.multdiv_i +`define MULTG `MULT.gen_mult_fast + +module top import ibex_pkg::*; #( + parameter int unsigned DmHaltAddr = 32'h1A110800, + parameter int unsigned DmExceptionAddr = 32'h1A110808, + parameter bit SecureIbex = 1'b0, + parameter bit WritebackStage = 1'b1, + parameter bit RV32E = 1'b0, + parameter int unsigned PMPNumRegions = 4 +) ( + // Clock and Reset + input logic clk_i, + input logic rst_ni, + + input logic test_en_i, // enable all clock gates for testing + input prim_ram_1p_pkg::ram_1p_cfg_t ram_cfg_i, + + input logic [31:0] hart_id_i, + input logic [31:0] boot_addr_i, + + // Instruction memory interface + output logic instr_req_o, + input logic instr_gnt_i, + input logic instr_rvalid_i, + output logic [31:0] instr_addr_o, + input logic [31:0] instr_rdata_i, + input logic [6:0] instr_rdata_intg_i, + input logic instr_err_i, + + // Data memory interface + output logic data_req_o, + output logic data_is_cap_o, + input logic data_gnt_i, + input logic data_rvalid_i, + output logic data_we_o, + output logic [3:0] data_be_o, + output logic [31:0] data_addr_o, + output logic [31:0] data_wdata_o, + output logic [6:0] data_wdata_intg_o, + input logic [31:0] data_rdata_i, + input logic [6:0] data_rdata_intg_i, + input logic data_err_i, + + // Interrupt inputs + input logic irq_software_i, + input logic irq_timer_i, + input logic irq_external_i, + input logic [14:0] irq_fast_i, + input logic irq_nm_i, // non-maskeable interrupt + + // Scrambling Interface + input logic scramble_key_valid_i, + input logic [SCRAMBLE_KEY_W-1:0] scramble_key_i, + input logic [SCRAMBLE_NONCE_W-1:0] scramble_nonce_i, + output logic scramble_req_o, + + // Debug Interface + input logic debug_req_i, + output crash_dump_t crash_dump_o, + output logic double_fault_seen_o, + + // CPU Control Signals + input ibex_mubi_t fetch_enable_i, + output logic core_sleep_o, + output logic alert_minor_o, + output logic alert_major_internal_o, + output logic alert_major_bus_o, + + + // DFT bypass controls + input logic scan_rst_ni +); + +default clocking @(posedge clk_i); endclocking + +ibex_top #( + .DmHaltAddr(DmHaltAddr), + .DmExceptionAddr(DmExceptionAddr), + .SecureIbex(SecureIbex), + .WritebackStage(WritebackStage), + .RV32E(RV32E), + .BranchTargetALU(1'b1), + .PMPEnable(1'b1), + .PMPNumRegions(PMPNumRegions) +) ibex_top_i(.*); + +// Core constraints +// 1. We do not allow going into debug mode +NotDebug: assume property (!ibex_top_i.u_ibex_core.debug_mode & !debug_req_i); +// 2. The boot address is constant +ConstantBoot: assume property (boot_addr_i == $past(boot_addr_i)); +// 3. Always fetch enable +FetchEnable: assume property (fetch_enable_i == IbexMuBiOn); +// 4. Never try to sleep if we couldn't ever wake up +WFIStart: assume property (`IDC.ctrl_fsm_cs == SLEEP |-> `CSR.mie_q.irq_software | `CSR.mie_q.irq_timer | `CSR.mie_q.irq_external); +// 5. Disable clock gating +TestEn: assume property (test_en_i); +// See protocol/* for further assumptions + +///////////////////////////////// Declarations ///////////////////////////////// + +// Helpful macros to define each relevant, checked CSR and their types. +// Some CSRs are not checked or tracked, see ex_is_checkable_csr below. +`define X_EACH_CSR \ + `ifndef X_DISABLE_PC `X(pc) `endif \ + `X(priv) \ + `X(mstatus) \ + `X(mie) \ + `X(mcause) \ + `X(mtval) \ + `X(mtvec) \ + `X(mscratch) \ + `X(mepc) \ + `X(mcounteren) \ + `X(pmp_cfg) \ + `X(pmp_addr) \ + `X(mseccfg) + +`define X_EACH_CSR_TYPED \ + logic [31:0] `X(pc); \ + t_Privilege `X(priv); \ + mstatus_t `X(mstatus); \ + logic [31:0] `X(mie); \ + logic [31:0] `X(mcause); \ + logic [31:0] `X(mtval); \ + logic [31:0] `X(mtvec); \ + logic [31:0] `X(mscratch); \ + logic [31:0] `X(mepc); \ + logic [63:0] `X(mcycle); \ + logic [31:0] `X(mshwmb); \ + logic [31:0] `X(mshwm); \ + logic [31:0] `X(mcounteren); \ + logic [7:0] `X(pmp_cfg)[PMPNumRegions]; \ + logic [31:0] `X(pmp_addr)[PMPNumRegions]; \ + logic [31:0] `X(mseccfg); + +////////////////////// Abstract State ////////////////////// + +// Pre state is the architectural state of Ibex at the start of the cycle +logic [31:0] pre_regs[31:0]; +logic [31:0] pre_nextpc; +logic [31:0] pre_mip; + +`define X(n) pre_``n +`X_EACH_CSR_TYPED +`undef X + +// Post state is the architectural state of Ibex at the end of this cycle, or the start of the next +`define X(n) post_``n +`X_EACH_CSR_TYPED +`undef X + +////////////////////// Following ////////////////////// +// Generally: +// - ex_P is 1 if P is true for the instruction in the ID/EX stage. +// - wbexc_P is 1 if P is true for the instruction in the WB/EXC (exception) stage. + +logic ex_is_wfi, ex_is_rtype, ex_is_div; +logic ex_is_pres_btype, ex_is_pres_jump; +logic ex_is_mem_instr, ex_is_load_instr, ex_is_store_instr; +logic ex_is_pres_mem_instr, ex_is_pres_load_instr, ex_is_pres_store_instr; + +// Have we branched, or are we branching in this cycle? +logic ex_has_branched_d, ex_has_branched_q; +logic [31:0] ex_branch_dst; +assign ex_branch_dst = `CR.branch_decision ? `CR.branch_target_ex : pre_nextpc; + +logic outstanding_mem; +assign outstanding_mem = `ID.outstanding_load_wb_i || `ID.outstanding_store_wb_i; + +logic has_resp_waiting_q, has_resp_waiting_d; +assign has_resp_waiting_q = data_mem_assume.outstanding_reqs_q != 8'h0; +assign has_resp_waiting_d = data_mem_assume.outstanding_reqs != 8'h0; + +logic has_one_resp_waiting_q, has_one_resp_waiting_d; +assign has_one_resp_waiting_q = data_mem_assume.outstanding_reqs_q == 8'h1; +assign has_one_resp_waiting_d = data_mem_assume.outstanding_reqs == 8'h1; + +logic has_two_resp_waiting_q, has_two_resp_waiting_d; +assign has_two_resp_waiting_q = data_mem_assume.outstanding_reqs_q == 8'h2; +assign has_two_resp_waiting_d = data_mem_assume.outstanding_reqs == 8'h2; + +logic wbexc_is_pres_load_instr, wbexc_is_pres_store_instr; +logic wbexc_is_load_instr, wbexc_is_store_instr; +logic wbexc_is_pres_mem_instr, wbexc_is_mem_instr; +logic wbexc_is_wfi; + +logic [31:0] ex_compressed_instr; +logic ex_has_compressed_instr; + +// Stored specification post state +logic wbexc_post_int_err; // Spec had an internal error + +logic [31:0] wbexc_post_wX; +logic [5:0] wbexc_post_wX_addr; +logic wbexc_post_wX_en; + +`define X(n) wbexc_post_``n +`X_EACH_CSR_TYPED +`undef X + +// Stored predetermined memory responses, see check/peek/mem.sv +logic [31:0] wbexc_spec_mem_read_fst_rdata; +logic [31:0] wbexc_spec_mem_read_snd_rdata; + +// Store DUT CSR post state +`define X(n) wbexc_dut_post_``n +`X_EACH_CSR_TYPED +`undef X + +logic [31:0] wbexc_instr; // original potentially compressed +logic [31:0] wbexc_decompressed_instr; // post decompression +logic wbexc_is_compressed; + +logic [31:0] wbexc_pc; + +logic mem_gnt_fst_d; // We are having or have had the first gnt +logic mem_gnt_fst_q; // We have had the first gnt before now +logic mem_gnt_snd_d; // We are having or have had the second gnt +logic mem_gnt_snd_q; // We have had the second gnt before now + +logic mem_req_fst_d; // We are having the first req +logic mem_req_snd_d; // We are having the second req + +logic wbexc_mem_had_snd_req; // During ID/EX there was a second request + +logic lsu_had_first_resp; +assign lsu_had_first_resp = `LSU.ls_fsm_cs == `LSU.WAIT_GNT && `LSU.split_misaligned_access; + +////////////////////// Wrap signals ////////////////////// + +logic spec_en; // The specification is being queried in this cycle +logic has_spec_past; // There is a previous step to compare against. Will become 0 on uncheckable CSRs and at reset. + +// The previous specification output to be compared with the new input +logic [31:0] spec_past_regs[31:0]; +logic [31:0] spec_past_has_reg; // Registers will have past values only when they are written to. +`define X(n) spec_past_``n +`X_EACH_CSR_TYPED +`undef X + +////////////////////// Pipeline signals ////////////////////// + +logic ex_success; // Execute stage succeeding +logic ex_err; // Execute stage erroring +logic ex_kill; // Execute stage killed +logic exc_finishing; // Exception finishing +logic wb_finishing; // WB finishing +logic wbexc_finishing; // WB/EXC finishing +logic wbexc_exists; // Instruction in WB/EXC +logic wbexc_ex_err; // EXC has an execute error +logic wbexc_fetch_err; // EXC has a fetch error +logic wbexc_illegal; // EXC has an illegal instruction +logic wbexc_compressed_illegal; // EXC has an illegal instruction +logic wbexc_err; // EXC has an error +logic instr_will_progress; // Instruction will finish EX +logic wfi_will_finish; // WFI instruction retire by flushing the pipeline, but that isn't an exception +logic wbexc_csr_pipe_flush; // The pipeline is being flushed due to a CSR write +logic wbexc_handling_irq; // Check the results of handling an IRQ + +////////////////////// CSR selection ////////////////////// +// Decide whether to compare wbexc_post_* and wbexc_dut_post_* or to use live versions. + +// WBEXC CSR dut post state +`define X(n) wbexc_dut_cmp_post_``n +`X_EACH_CSR_TYPED +`undef X + +// WBEXC CSR spec post state +`define X(n) wbexc_spec_cmp_post_``n +`X_EACH_CSR_TYPED +`undef X + +////////////////////// Spec Post ////////////////////// + +// These cause combinational cycles, but not severe ones. The problem is fixed in CherIoT-Ibex +logic spec_rx_a_en; +logic [4:0] spec_rx_a_addr; +logic [31:0] spec_rx_a; +assign spec_rx_a = pre_regs[spec_rx_a_addr]; + +logic spec_rx_b_en; +logic [4:0] spec_rx_b_addr; +logic [31:0] spec_rx_b; +assign spec_rx_b = pre_regs[spec_rx_b_addr]; + +logic [31:0] spec_post_wX; +logic [4:0] spec_post_wX_addr; +logic spec_post_wX_en; + +`define X(n) spec_post_``n +`X_EACH_CSR_TYPED +`undef X + +logic spec_int_err; + +logic spec_fetch_err; // The specification has experienced a fetch error, regardless of whether or not it was told to. +assign spec_fetch_err = + (main_mode == MAIN_IFERR && spec_api_i.main_result == MAINRES_OK) || + spec_api_i.main_result == MAINRES_IFERR_1 || spec_api_i.main_result == MAINRES_IFERR_2; + +logic spec_mem_read; +logic spec_mem_read_snd; +logic [31:0] spec_mem_read_fst_addr; +logic [31:0] spec_mem_read_snd_addr; +logic [31:0] spec_mem_read_fst_rdata; // Undriven +logic [31:0] spec_mem_read_snd_rdata; // Undriven + +logic spec_mem_write; +logic spec_mem_write_snd; +logic [31:0] spec_mem_write_fst_addr; +logic [31:0] spec_mem_write_snd_addr; +logic [31:0] spec_mem_write_fst_wdata; +logic [31:0] spec_mem_write_snd_wdata; +logic [3:0] spec_mem_write_fst_be; +logic [3:0] spec_mem_write_snd_be; + +logic spec_mem_en; +logic spec_mem_en_snd; +logic [31:0] spec_mem_fst_addr; +logic [31:0] spec_mem_snd_addr; +logic spec_has_pmp_err; + +assign spec_mem_en = spec_mem_read | spec_mem_write; +assign spec_mem_en_snd = spec_mem_read_snd | spec_mem_write_snd; +assign spec_mem_fst_addr = spec_mem_read ? spec_mem_read_fst_addr : spec_mem_write_fst_addr; +assign spec_mem_snd_addr = spec_mem_read ? spec_mem_read_snd_addr : spec_mem_write_snd_addr; +assign spec_has_pmp_err = ~spec_mem_en || (`LSU.split_misaligned_access && ~spec_mem_en_snd); + +logic [31:0] fst_mask, snd_mask; +assign fst_mask = { + {8{spec_mem_write_fst_be[3]}}, {8{spec_mem_write_fst_be[2]}}, + {8{spec_mem_write_fst_be[1]}}, {8{spec_mem_write_fst_be[0]}} +}; +assign snd_mask = { + {8{spec_mem_write_snd_be[3]}}, {8{spec_mem_write_snd_be[2]}}, + {8{spec_mem_write_snd_be[1]}}, {8{spec_mem_write_snd_be[0]}} +}; + +logic fst_mem_cmp; // Condition for the first outgoing request to be spec conformant +assign fst_mem_cmp = (spec_mem_write == data_we_o) && (spec_mem_read == ~data_we_o) && + (data_addr_o == spec_mem_fst_addr) && + (data_we_o -> + (data_wdata_o & fst_mask) == (spec_mem_write_fst_wdata & fst_mask)); +logic snd_mem_cmp; // Condition for the second outgoing request to be spec conformant +assign snd_mem_cmp = (spec_mem_write_snd == data_we_o) && (spec_mem_read_snd == ~data_we_o) && + (data_addr_o == spec_mem_snd_addr) && + (data_we_o -> + (data_wdata_o & snd_mask) == (spec_mem_write_snd_wdata & snd_mask)); + +logic lsu_waiting_for_misal; +assign lsu_waiting_for_misal = + ((`LSU.data_type_q == 2'b00) && (`LSU.rdata_offset_q != 2'b00)) || + ((`LSU.data_type_q == 2'b01) && (`LSU.rdata_offset_q == 2'b11)); + +logic addr_last_matches; +assign addr_last_matches = `ID.rf_rdata_a_fwd + (ex_is_store_instr? `ID.imm_s_type : `ID.imm_i_type) == `LSU.addr_last_q; + +logic addr_last_d_matches; +assign addr_last_d_matches = `ID.rf_rdata_a_fwd + (ex_is_store_instr? `ID.imm_s_type : `ID.imm_i_type) == `LSU.addr_last_d; + +////////////////////// Compare ////////////////////// + +logic addr_match; // Register write index match +logic data_match; // Register write data match +logic csrs_match; +logic ex_csrs_match; +logic csrs_match_non_exc; +logic ex_csrs_match_non_exc; +logic pc_match; +logic finishing_executed; // Finishing normal case + +`define INSTR `CR.instr_rdata_id + +logic wbexc_is_checkable_csr; +logic ex_is_checkable_csr; +assign ex_is_checkable_csr = ~( + ((CSR_MHPMCOUNTER3H <= `CSR_ADDR) && (`CSR_ADDR <= CSR_MHPMCOUNTER31H)) | + ((CSR_MHPMCOUNTER3 <= `CSR_ADDR) && (`CSR_ADDR <= CSR_MHPMCOUNTER31)) | + ((CSR_MHPMEVENT3 <= `CSR_ADDR) && (`CSR_ADDR <= CSR_MHPMEVENT31)) | + (`CSR_ADDR == CSR_CPUCTRLSTS) | (`CSR_ADDR == CSR_SECURESEED) | + (`CSR_ADDR == CSR_MIE) | + (`CSR_ADDR == CSR_MCYCLE) | (`CSR_ADDR == CSR_MCYCLEH) | + + // TODO: + (`CSR_ADDR == CSR_MINSTRET) | (`CSR_ADDR == CSR_MINSTRETH) | + (`CSR_ADDR == CSR_MCOUNTINHIBIT) +); + +`undef INSTR +`define INSTR wbexc_decompressed_instr + +// Illegal instructions arent checkable unless the relevant specifications are present. +logic can_check_illegal; +assign can_check_illegal = `SPEC_ILLEGAL & `SPEC_CSR & `SPEC_MRET & `SPEC_WFI; + +`undef INSTR + +////////////////////// Decompression Invariant Defs ////////////////////// +// These will be used to show that the decompressed instruction stored is in fact the decompressed version of the compressed instruction. + +logic [31:0] decompressed_instr; +logic decompressed_instr_illegal; +ibex_compressed_decoder decompression_assertion_decoder( + .clk_i, + .rst_ni, + .valid_i(1'b1), + .instr_i(ex_compressed_instr), + .instr_o(decompressed_instr), + .is_compressed_o(), + .illegal_instr_o(decompressed_instr_illegal) +); + +logic [31:0] decompressed_instr_2; +logic decompressed_instr_illegal_2; +ibex_compressed_decoder decompression_assertion_decoder_2( + .clk_i, + .rst_ni, + .valid_i(1'b1), + .instr_i(wbexc_instr), + .instr_o(decompressed_instr_2), + .is_compressed_o(wbexc_is_compressed), + .illegal_instr_o(decompressed_instr_illegal_2) +); + +////////////////////// IRQ + Memory Protocols ////////////////////// +`include "protocol/irqs.sv" +`include "protocol/mem.sv" + +////////////////////// Following ////////////////////// +`include "peek/abs.sv" // Abstract state +`include "peek/mem.sv" // Memory tracking +`include "peek/follower.sv" // Pipeline follower +`include "spec_instance.sv" // Instantiate the specification + +////////////////////// Proof helpers /////////////////////// +`include "peek/compare_helper.sv" + +////////////////////// Proof ////////////////////// +`define INSTR wbexc_decompressed_instr +`include "../build/psgen.sv" + +endmodule diff --git a/dv/formal/instrs.json b/dv/formal/instrs.json new file mode 100644 index 0000000000..e91bb23c3d --- /dev/null +++ b/dv/formal/instrs.json @@ -0,0 +1,58 @@ +[ + "UTYPE", + "ITYPE", + "RTYPE", + "FENCE", + "FENCEI", + "LOAD", + "STORE", + "SHIFTIOP", + "ILLEGAL", + "BTYPE", + "CSR", + "DIV", + "EBREAK", + "ECALL", + "MRET", + "MUL", + "REM", + "RISCV_JAL", + "RISCV_JALR", + "WFI", + + "C_ADD", + "C_ADDI", + "C_ADDI16SP", + "C_ADDI4SPN", + "C_ADDIW", + "C_ADDW", + "C_AND", + "C_ANDI", + "C_BEQZ", + "C_BNEZ", + "C_EBREAK", + "C_ILLEGAL", + "C_J", + "C_JAL", + "C_JALR", + "C_JR", + "C_LD", + "C_LDSP", + "C_LI", + "C_LUI", + "C_LW", + "C_LWSP", + "C_MV", + "C_NOP", + "C_OR", + "C_SD", + "C_SDSP", + "C_SLLI", + "C_SRAI", + "C_SRLI", + "C_SUB", + "C_SUBW", + "C_SW", + "C_SWSP", + "C_XOR" +] diff --git a/dv/formal/rtl-changes.patch b/dv/formal/rtl-changes.patch new file mode 100644 index 0000000000..5cd8179a93 --- /dev/null +++ b/dv/formal/rtl-changes.patch @@ -0,0 +1,46 @@ +diff --git a/rtl/ibex_id_stage.sv b/rtl/ibex_id_stage.sv +index 3a358af7..3c19d763 100644 +--- a/rtl/ibex_id_stage.sv ++++ b/rtl/ibex_id_stage.sv +@@ -522,7 +522,7 @@ module ibex_id_stage #( + // - When modifying any PMP CSR, PMP check of the next instruction might get invalidated. + // Hence, a pipeline flush is needed to instantiate another PMP check with the updated CSRs. + // - When modifying debug CSRs. +- if (csr_op_en_o == 1'b1 && (csr_op_o == CSR_OP_WRITE || csr_op_o == CSR_OP_SET)) begin ++ if (csr_op_en_o == 1'b1 && (csr_op_o == CSR_OP_WRITE || csr_op_o == CSR_OP_SET || csr_op_o == CSR_OP_CLEAR)) begin + if (csr_num_e'(instr_rdata_i[31:20]) == CSR_MSTATUS || + csr_num_e'(instr_rdata_i[31:20]) == CSR_MIE || + csr_num_e'(instr_rdata_i[31:20]) == CSR_MSECCFG || +diff --git a/rtl/ibex_top.sv b/rtl/ibex_top.sv +index 81e49633..17b36c17 100644 +--- a/rtl/ibex_top.sv ++++ b/rtl/ibex_top.sv +@@ -142,7 +142,7 @@ module ibex_top import ibex_pkg::*; #( + ); + + localparam bit Lockstep = SecureIbex; +- localparam bit ResetAll = Lockstep; ++ localparam bit ResetAll = 1; + localparam bit DummyInstructions = SecureIbex; + localparam bit RegFileECC = SecureIbex; + localparam bit RegFileWrenCheck = SecureIbex; +@@ -236,12 +236,13 @@ module ibex_top import ibex_pkg::*; #( + + assign core_sleep_o = ~clock_en; + +- prim_clock_gating core_clock_gate_i ( +- .clk_i (clk_i), +- .en_i (clock_en), +- .test_en_i(test_en_i), +- .clk_o (clk) +- ); ++ assign clk = clk_i; ++ // prim_clock_gating core_clock_gate_i ( ++ // .clk_i (clk_i), ++ // .en_i (clock_en), ++ // .test_en_i(test_en_i), ++ // .clk_o (clk) ++ // ); + + //////////////////////// + // Core instantiation // diff --git a/dv/formal/spec/fix_pmp_bug.py b/dv/formal/spec/fix_pmp_bug.py new file mode 100644 index 0000000000..90ed7a806b --- /dev/null +++ b/dv/formal/spec/fix_pmp_bug.py @@ -0,0 +1,28 @@ +''' +Fixes an issue where the Sail -> SV compiler references t_Pmpcfg_ent (in sail_genlib_ibexspec.sv) before it defines it (in ibexspec.sv) +by just moving that definition. +''' + +S = """ +typedef struct { + logic [7:0] bits; +} t_Pmpcfg_ent; +""" + +T = """ +typedef logic [127:0] sail_int; +""" + +with open("build/ibexspec.sv", "r") as f: + c = f.read() + +c = c.replace(S, "") + +with open("build/ibexspec.sv", "w") as f: + f.write(c) + +with open("build/sail_genlib_ibexspec.sv", "r") as f: + c = f.read() + +with open("build/sail_genlib_ibexspec.sv", "w") as f: + f.write(S + "\n" + c) diff --git a/dv/formal/spec/main.sail b/dv/formal/spec/main.sail new file mode 100644 index 0000000000..794e15a304 --- /dev/null +++ b/dv/formal/spec/main.sail @@ -0,0 +1,94 @@ +// Copyright lowRISC contributors. +// Copyright 2024 University of Oxford, see also CREDITS.md. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// Original author: Louis-Emile Ploix +// SPDX-License-Identifier: Apache-2.0 + +/* +This file contains the main function which is 'invoked' by the SystemVerilog. +main is equivalent to the Sail step function, it has some differences however: +1. It uses the decompress function we added. +2. It can operate in one of three modes, based on what it has been told to do by the SV. + This is done for a couple of reasons: + 1. It's difficult to compare IRQ handling, since ibex takes them later than the Sail would. This is OK since it's not really + fair to enforce that IRQs are handled between any two specific instructions, so long as it is eventually. + 2. The three modes are a useful case analysis we can make. This means that to prove correctness of an I-Type instruction, for example, we + can more easily seperate out the check for instruction fetch correctness, which makes things faster and avoids repeated work. +*/ + +union FetchResult = { + F_Ext_Error : ext_fetch_addr_error, /* For extensions */ + F_Base : word, /* Base ISA */ + F_RVC : half, /* Compressed ISA */ + F_Error : (ExceptionType, xlenbits) /* standard exception and PC */ +} + +function isRVC(h : bits(16)) -> bool = ~ (h[1 .. 0] == 0b11) + +// modified version of what one can find in riscv_fetch.sail +val altFetch : (bits(16), bits(16)) -> FetchResult +function altFetch(ilo: bits(16), ihi: bits(16)) -> FetchResult = + match mem_read_i(Execute(), PC, if isRVC(ilo) then 2 else 4) { + MemException(a, e) => F_Error(e, a), + MemValue(()) => if isRVC(ilo) then F_RVC(ilo) else F_Base(ihi @ ilo) + } + + +enum MainMode = { MAIN_IDEX, MAIN_IFERR, MAIN_IRQ } +enum MainResult = { MAINRES_OK, MAINRES_IFERR_1, MAINRES_IFERR_2, MAINRES_NOIFERR, MAINRES_IRQ, MAINRES_NOIRQ } + +function main(insn_bits, mode) : (bits(32), MainMode) -> MainResult = { + let insn = if isRVC(insn_bits[15..0]) then { + instbits = 0x0000 @ insn_bits[15..0]; + encdec_compressed(insn_bits[15..0]) + } else { + instbits = insn_bits; + encdec(insn_bits) + }; + + let irq = dispatchInterrupt(cur_privilege); + let f : FetchResult = altFetch(insn_bits[15..0], insn_bits[31..16]); + + let res : MainResult = match mode { + MAIN_IDEX => { + match decompress(insn) { + Some(decompressed) => { + let _ = execute(decompressed); + }, + None() => () + }; + + match f { + F_Ext_Error(e) => MAINRES_IFERR_1, + F_Error(e, addr) => MAINRES_IFERR_2, + F_RVC(h) => MAINRES_OK, + F_Base(w) => MAINRES_OK + } + }, + MAIN_IFERR => { + match f { + F_Ext_Error(e) => { + ext_handle_fetch_check_error(e); + MAINRES_OK + }, + F_Error(e, addr) => { + handle_mem_exception(addr, e); + MAINRES_OK + }, + F_RVC(h) => MAINRES_NOIFERR, + F_Base(w) => MAINRES_NOIFERR + } + }, + MAIN_IRQ => { + match irq { + Some(intr, priv) => { + handle_interrupt(intr, priv); + MAINRES_OK + }, + None() => MAINRES_NOIRQ + } + } + }; + + res +} diff --git a/dv/formal/spec/spec_api.sv b/dv/formal/spec/spec_api.sv new file mode 100644 index 0000000000..3d24a0dfcd --- /dev/null +++ b/dv/formal/spec/spec_api.sv @@ -0,0 +1,368 @@ +// Copyright lowRISC contributors. +// Copyright 2024 University of Oxford, see also CREDITS.md. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// Original author: Louis-Emile Ploix +// SPDX-License-Identifier: Apache-2.0 + +/* +This module contains the actual instance of the specification. It's quite ugly. Mostly it's just forwaring things to +different names and ignoring registers we don't care about. +*/ + +typedef struct packed { + logic mie; + logic mpie; + logic tw; + logic mprv; + logic [1:0] mpp; +} mstatus_t; + +module spec_api #( + parameter int unsigned NREGS = 32, + parameter int unsigned PMPNumRegions = 4 +) ( + input t_MainMode main_mode, + + output logic rx_a_en_o, + output logic [4:0] rx_a_addr_o, + input logic [31:0] rx_a_i, + output logic rx_b_en_o, + output logic [4:0] rx_b_addr_o, + input logic [31:0] rx_b_i, + + output logic wx_en_o, + output logic [31:0] wx_o, + output logic [4:0] wx_addr_o, + + input logic [31:0] nextpc_i, + input logic [31:0] pc_i, + output logic [31:0] pc_o, + + input logic [31:0] misa_i, + input logic [31:0] mip_i, + + input logic [31:0] mvendor_id_i, + input logic [31:0] march_id_i, + input logic [31:0] mimp_id_i, + input logic [31:0] mhart_id_i, + input logic [31:0] mconfigptr_i, + + input logic [31:0] mseccfg_i, + output logic [31:0] mseccfg_o, + input logic [7:0] pmp_cfg_i[PMPNumRegions], + output logic [7:0] pmp_cfg_o[PMPNumRegions], + input logic [31:0] pmp_addr_i[PMPNumRegions], + output logic [31:0] pmp_addr_o[PMPNumRegions], + + input logic [31:0] mie_i, + output logic [31:0] mie_o, + input t_Privilege priv_i, + output t_Privilege priv_o, + input mstatus_t mstatus_i, + output mstatus_t mstatus_o, + input logic [31:0] mcause_i, + output logic [31:0] mcause_o, + input logic [63:0] mcycle_i, + output logic [63:0] mcycle_o, + input logic [31:0] mtval_i, + output logic [31:0] mtval_o, + input logic [31:0] mtvec_i, + output logic [31:0] mtvec_o, + input logic [31:0] mscratch_i, + output logic [31:0] mscratch_o, + input logic [31:0] mepc_i, + output logic [31:0] mepc_o, + input logic [31:0] mshwmb_i, + output logic [31:0] mshwmb_o, + input logic [31:0] mshwm_i, + output logic [31:0] mshwm_o, + input logic [31:0] mcounteren_i, + output logic [31:0] mcounteren_o, + + input logic [31:0] insn_bits, + output logic int_err_o, + + output logic mem_read_o, + output logic mem_read_snd_gran_o, + output logic[31:0] mem_read_fst_addr_o, + output logic[31:0] mem_read_snd_addr_o, + input logic[31:0] mem_read_fst_rdata_i, + input logic[31:0] mem_read_snd_rdata_i, + + output logic mem_write_o, + output logic mem_write_snd_gran_o, + output logic[31:0] mem_write_fst_addr_o, + output logic[31:0] mem_write_snd_addr_o, + output logic[31:0] mem_write_fst_wdata_o, + output logic[31:0] mem_write_snd_wdata_o, + output logic [3:0] mem_write_fst_be_o, + output logic [3:0] mem_write_snd_be_o +); + +bit rX_sail_invoke[1:0]; +logic [31:0] rX_sail_invoke_ret[1:0]; +logic [63:0] rX_sail_invoke_arg_0[1:0]; +assign rx_a_en_o = rX_sail_invoke[0]; +assign rx_a_addr_o = rX_sail_invoke_arg_0[0][4:0]; +assign rX_sail_invoke_ret[0] = rx_a_i; +assign rx_b_en_o = rX_sail_invoke[1]; +assign rx_b_addr_o = rX_sail_invoke_arg_0[1][4:0]; +assign rX_sail_invoke_ret[1] = rx_b_i; + +logic wX_sail_invoke[0:0]; +logic [63:0] wX_sail_invoke_arg_0[0:0]; +logic [31:0] wX_sail_invoke_arg_1[0:0]; +assign wx_en_o = wX_sail_invoke[0]; +assign wx_addr_o = wX_sail_invoke_arg_0[0][4:0]; +assign wx_o = wX_sail_invoke_arg_1[0]; + +logic write_ram_sail_invoke[1:0]; +logic [31:0] write_ram_sail_invoke_arg_1[1:0]; +logic [31:0] write_ram_sail_invoke_arg_2[1:0]; +logic [3:0] write_ram_sail_invoke_arg_3[1:0]; +assign mem_write_o = write_ram_sail_invoke[0]; +assign mem_write_snd_gran_o = write_ram_sail_invoke[1]; +assign mem_write_fst_addr_o = write_ram_sail_invoke_arg_1[0]; +assign mem_write_snd_addr_o = write_ram_sail_invoke_arg_1[1]; +assign mem_write_fst_wdata_o = write_ram_sail_invoke_arg_2[0]; +assign mem_write_snd_wdata_o = write_ram_sail_invoke_arg_2[1]; +assign mem_write_fst_be_o = write_ram_sail_invoke_arg_3[0]; +assign mem_write_snd_be_o = write_ram_sail_invoke_arg_3[1]; + +logic read_ram_sail_invoke[1:0]; +logic [31:0] read_ram_sail_invoke_ret[1:0]; +logic [31:0] read_ram_sail_invoke_arg_1[1:0]; +assign mem_read_o = read_ram_sail_invoke[0]; +assign mem_read_snd_gran_o = read_ram_sail_invoke[1]; +assign mem_read_fst_addr_o = read_ram_sail_invoke_arg_1[0]; +assign mem_read_snd_addr_o = read_ram_sail_invoke_arg_1[1]; +assign read_ram_sail_invoke_ret[0] = mem_read_fst_rdata_i; +assign read_ram_sail_invoke_ret[1] = mem_read_snd_rdata_i; + +t_MainResult main_result; + +t_Mstatus mstatus_out; +assign mstatus_o.mie = mstatus_out.bits[3]; +assign mstatus_o.mpie = mstatus_out.bits[7]; +assign mstatus_o.tw = mstatus_out.bits[21]; +assign mstatus_o.mprv = mstatus_out.bits[17]; +assign mstatus_o.mpp = mstatus_out.bits[12:11]; + +t_Mcause mcause_out; +assign mcause_o = mcause_out.bits; + +t_Minterrupts mie_out; +assign mie_o = mie_out.bits; + +t_Counteren mcounteren_out; +assign mcounteren_o = mcounteren_out.bits; + +t_Mtvec mtvec_out; +assign mtvec_o = mtvec_out.bits; + +t_Pmpcfg_ent pmpcfg_n_in[63:0]; +logic [31:0] pmpaddr_n_in[63:0]; +t_Pmpcfg_ent pmpcfg_n_out[63:0]; +logic [31:0] pmpaddr_n_out[63:0]; +for (genvar i = 0; i < 64; i++) begin: g_pmp_bind + if (i < PMPNumRegions) begin: g_pmp_bind_real + assign pmpcfg_n_in[i] = '{bits: pmp_cfg_i[i]}; + assign pmpaddr_n_in[i] = pmp_addr_i[i]; + + assign pmp_cfg_o[i] = pmpcfg_n_out[i].bits[7:0]; + assign pmp_addr_o[i] = pmpaddr_n_out[i]; + end else begin: g_pmp_bind_zero + // These shouldn't ever be used anyway + assign pmpcfg_n_in[i] = '{bits: 0}; + assign pmpaddr_n_in[i] = 0; + end +end + +t_Mseccfg_ent mseccfg_out; +assign mseccfg_o = mseccfg_out.bits; + +sail_ibexspec spec_i( + .cur_inst_in(insn_bits), + .cur_inst_out(), + .cur_privilege_in(priv_i), + .cur_privilege_out(priv_o), + .instbits_in(insn_bits), + .instbits_out(), + .marchid_in(march_id_i), + .marchid_out(), + .mcause_in('{bits: mcause_i}), + .mcause_out, + .mconfigptr_in(mconfigptr_i), + .mconfigptr_out(), + .mcounteren_in('{ bits: mcounteren_i }), + .mcounteren_out, + .mcountinhibit_in(), + .mcountinhibit_out(), + .mcycle_in(mcycle_i), + .mcycle_out(mcycle_o), + .medeleg_in('{bits: 32'h0}), + .medeleg_out(), + .menvcfg_in('{bits: 32'h0}), + .menvcfg_out(), + .mepc_in(mepc_i), + .mepc_out(mepc_o), + .mhartid_in(mhart_id_i), + .mhartid_out(mhart_id_o), + .mideleg_in(), + .mideleg_out(), + .mie_in('{bits: mie_i}), + .mie_out, + .mimpid_in(mimp_id_i), + .mimpid_out(mimp_id_o), + .minstret_in(minstret_i), + .minstret_out(minstret_o), + .minstret_increment_in(), + .minstret_increment_out(), + .mip_in('{bits: mip_i}), + .mip_out(), + .misa_in('{bits: misa_i}), + .misa_out(), + .mscratch_in(mscratch_i), + .mscratch_out(mscratch_o), + .mstatus_in('{bits: + (32'(mstatus_i.mie) << 3) | + (32'(mstatus_i.mpie) << 7) | + (32'(mstatus_i.tw) << 21) | + (32'(mstatus_i.mprv) << 17) | + (32'(mstatus_i.mpp) << 11) + }), + .mstatus_out, + .mstatush_in('{bits: 32'b0}), + .mstatush_out(), + .mtval_in(mtval_i), + .mtval_out(mtval_o), + .mtvec_in('{bits: mtvec_i}), + .mtvec_out, + .mvendorid_in(mvendor_id_i), + .mvendorid_out(mvendor_id_o), + .nextPC_in(nextpc_i), + .nextPC_out(pc_o), + .PC_in(pc_i), + .PC_out(), + .mseccfg_in('{bits: mseccfg_i}), + .mseccfg_out, + .mseccfgh_in(32'h0), + .mseccfgh_out(), + .pmpaddr_n_in, + .pmpaddr_n_out, + .pmpcfg_n_in, + .pmpcfg_n_out, + .scause_in(), + .scause_out(), + .scounteren_in(), + .scounteren_out(), + .sedeleg_in(), + .sedeleg_out(), + .senvcfg_in(), + .senvcfg_out(), + .sepc_in(), + .sepc_out(), + .sideleg_in(), + .sideleg_out(), + .sscratch_in(), + .sscratch_out(), + .stval_in(), + .stval_out(), + .stvec_in(), + .stvec_out(), + .tselect_in(), + .tselect_out(), + .x1_in(), + .x1_out(), + .x2_in(), + .x2_out(), + .x3_in(), + .x3_out(), + .x4_in(), + .x4_out(), + .x5_in(), + .x5_out(), + .x6_in(), + .x6_out(), + .x7_in(), + .x7_out(), + .x8_in(), + .x8_out(), + .x9_in(), + .x9_out(), + .x10_in(), + .x10_out(), + .x11_in(), + .x11_out(), + .x12_in(), + .x12_out(), + .x13_in(), + .x13_out(), + .x14_in(), + .x14_out(), + .x15_in(), + .x15_out(), + .x16_in(), + .x16_out(), + .x17_in(), + .x17_out(), + .x18_in(), + .x18_out(), + .x19_in(), + .x19_out(), + .x20_in(), + .x20_out(), + .x21_in(), + .x21_out(), + .x22_in(), + .x22_out(), + .x23_in(), + .x23_out(), + .x24_in(), + .x24_out(), + .x25_in(), + .x25_out(), + .x26_in(), + .x26_out(), + .x27_in(), + .x27_out(), + .x28_in(), + .x28_out(), + .x29_in(), + .x29_out(), + .x30_in(), + .x30_out(), + .x31_in(), + .x31_out(), + + .wX_sail_invoke, + .wX_sail_invoke_ret(), + .wX_sail_invoke_arg_0, + .wX_sail_invoke_arg_1, + + .rX_sail_invoke, + .rX_sail_invoke_ret, + .rX_sail_invoke_arg_0, + + .write_ram_sail_invoke, + .write_ram_sail_invoke_ret(), + .write_ram_sail_invoke_arg_0(), + .write_ram_sail_invoke_arg_1, + .write_ram_sail_invoke_arg_2, + .write_ram_sail_invoke_arg_3, + + .read_ram_sail_invoke, + .read_ram_sail_invoke_ret, + .read_ram_sail_invoke_arg_0(), + .read_ram_sail_invoke_arg_1, + + .main_result(main_result), + .insn_bits(insn_bits), + .mode(main_mode) +); + +assign int_err_o = spec_i.sail_reached_unreachable | + spec_i.sail_have_exception | + (main_result != MAINRES_OK); + +endmodule diff --git a/dv/formal/spec/stub.sv b/dv/formal/spec/stub.sv new file mode 100644 index 0000000000..7fe3631e40 --- /dev/null +++ b/dv/formal/spec/stub.sv @@ -0,0 +1,80 @@ +// Copyright lowRISC contributors. +// Copyright 2024 University of Oxford, see also CREDITS.md. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// Original author: Louis-Emile Ploix +// SPDX-License-Identifier: Apache-2.0 + +/* +Provides stubs (mostly) for the native functions the Sail expects to see, mostly +handling config stuff. +*/ + +function automatic logic[127:0] sail_emod_int(logic[127:0] x, logic[127:0] y); + // This one is unsigned, but the rest should be signed + return x % y; +endfunction + +function automatic logic[127:0] sail_pow_int(logic[127:0] x, logic[127:0] y); + // These are Sail integers, so operations on them should be signed + return signed'(x) ** signed'(y); +endfunction + +function automatic logic[127:0] sail_tmod_int(logic[127:0] x, logic[127:0] y); + return signed'(x) % signed'(y); +endfunction + +function automatic logic[127:0] sail_tdiv_int(logic[127:0] x, logic[127:0] y); + return signed'(x) / signed'(y); +endfunction + +function automatic sail_bits sail_shift_bits_left(sail_bits x, sail_bits y); + return {x.size, x.bits << y.bits}; +endfunction + +function automatic sail_bits sail_shift_bits_right(sail_bits x, sail_bits y); + return {x.size, x.bits >> y.bits}; +endfunction + +function automatic sail_unit sail_decimal_string_of_bits(logic [11:0] x); + return SAIL_UNIT; +endfunction +function automatic logic sail_elf_tohost(sail_unit u); return 0; endfunction +function automatic logic sail_get_config_print_mem(sail_unit u); return 0; endfunction +function automatic logic sail_get_config_print_platform(sail_unit u); return 0; endfunction +function automatic logic sail_get_config_print_reg(sail_unit u); return 0; endfunction +function automatic logic sail_get_config_print_instr(sail_unit u); return 0; endfunction +function automatic logic sail_get_config_print_exception(sail_unit u); return 0; endfunction +function automatic logic[31:0] sail_plat_clint_base(sail_unit u); return 0; endfunction +function automatic logic[31:0] sail_plat_clint_size(sail_unit u); return 0; endfunction +function automatic logic sail_plat_enable_dirty_update(sail_unit u); return 0; endfunction +function automatic logic sail_plat_enable_misaligned_access(sail_unit u); return 1; endfunction +function automatic logic sail_plat_enable_pmp(sail_unit u); return 0; endfunction +function automatic sail_unit sail_platform_barrier(t_barrier_kind k); return SAIL_UNIT; endfunction +function automatic sail_unit sail_platform_write_mem_ea( + t_write_kind a, logic [63:0] b, sail_bits c, logic [127:0] d +); return SAIL_UNIT; endfunction +function automatic logic[15:0] sail_plat_get_16_random_bits(sail_unit u); return 0; endfunction +function automatic logic sail_plat_htif_tohost(sail_unit u); return 0; endfunction +function automatic logic sail_plat_mtval_has_illegal_inst_bits(sail_unit u); return 1; endfunction +function automatic logic[31:0] sail_plat_ram_base(sail_unit u); return 0; endfunction +function automatic logic[31:0] sail_plat_ram_size(sail_unit u); return 0; endfunction +function automatic logic[31:0] sail_plat_rom_base(sail_unit u); return 0; endfunction +function automatic logic[31:0] sail_plat_rom_size(sail_unit u); return 0; endfunction +function automatic sail_unit sail_plat_term_write(logic[7:0] u); return SAIL_UNIT; endfunction +function automatic sail_unit sail_print_mem_access(sail_unit u); return SAIL_UNIT; endfunction +function automatic sail_unit sail_print_platform(sail_unit u); return SAIL_UNIT; endfunction +function automatic sail_unit sail_print_reg(sail_unit u); return SAIL_UNIT; endfunction +function automatic sail_unit sail_string_of_int(logic[127:0] u); return SAIL_UNIT; endfunction +function automatic logic sail_sys_enable_fdext(sail_unit u); return 0; endfunction +function automatic logic sail_sys_enable_rvc(sail_unit u); return 1; endfunction +function automatic logic sail_sys_enable_writable_misa(sail_unit u); return 0; endfunction +function automatic logic sail_sys_enable_zfinx(sail_unit u); return 0; endfunction +function automatic sail_unit sail_cancel_reservation(sail_unit u); return SAIL_UNIT; endfunction +function automatic logic[31:0] sail_plat_uart_base(sail_unit u); return 0; endfunction +function automatic logic[31:0] sail_plat_uart_size(sail_unit u); return 0; endfunction +function automatic logic[6:0] sail_sys_pmp_count(sail_unit u); return 7'd4; endfunction +function automatic logic[6:0] sail_sys_pmp_max_count(sail_unit u); return 7'd16; endfunction +function automatic sail_unit sail_load_reservation(logic[31:0] u); return SAIL_UNIT; endfunction +function automatic logic sail_speculate_conditional(sail_unit u); return 0; endfunction +function automatic logic[63:0] sail_sys_pmp_grain(sail_unit u); return 0; endfunction +function automatic logic sail_sys_enable_writable_fiom(sail_unit u); return 0; endfunction diff --git a/dv/formal/thm/btype.proof b/dv/formal/thm/btype.proof new file mode 100644 index 0000000000..fed98f9237 --- /dev/null +++ b/dv/formal/thm/btype.proof @@ -0,0 +1,48 @@ +# Copyright lowRISC contributors. +# Copyright 2024 University of Oxford, see also CREDITS.md. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# Original author: Louis-Emile Ploix +# SPDX-License-Identifier: Apache-2.0 + +# This file implements two small graph inductions required by btype and jtype instructions to +# deal with the fact that the PC changes at inconvenient times. + +lemma btype + in (instr_will_progress & ex_is_pres_btype & ~ex_err) + FinishFirstCycle: have (`ID.instr_first_cycle) + / + BInd: + graph_induction +rev + cond (`CR.instr_valid_id & ex_is_pres_btype & ~ex_err & ~ex_kill) + inv nobranch (~ex_has_branched_d) + inv eq (ex_has_branched_d == `CR.branch_decision && post_pc == ex_branch_dst) + + entry (`CR.instr_new_id) -> stall oma progress + + node stall nobranch (`ID.stall_ld_hz) => stall progress + node oma eq (`IDG.outstanding_memory_access & ~`ID.stall_ld_hz) => oma progress + split_bool (`CR.branch_decision) + node progress eq (instr_will_progress) + / + in (instr_will_progress & ex_is_pres_btype & ~ex_err) + NoBranch: have (~`CR.branch_decision |-> spec_post_pc == pre_nextpc) + Branch: have (`CR.branch_decision |-> spec_post_pc == `CR.branch_target_ex) + +lemma jump + in (instr_will_progress & ex_is_pres_jump & ~ex_err) + FinishFirstCycle: have (`ID.instr_first_cycle) + / + JInd: + graph_induction +rev + cond (`CR.instr_valid_id & ex_is_pres_jump & ~ex_err & ~ex_kill) + inv nobranch (~ex_has_branched_d) + inv eq (ex_has_branched_d && post_pc[31:1] == `CR.branch_target_ex[31:1]) + + entry (`CR.instr_new_id) -> stall oma progress + + node stall nobranch (`ID.stall_ld_hz) => stall progress + node oma eq (`IDG.outstanding_memory_access & ~`ID.stall_ld_hz) => oma progress + node progress eq (instr_will_progress) + / + in (instr_will_progress & ex_is_pres_jump & ~ex_err) + Branch: have (spec_post_pc[31:1] == `CR.branch_target_ex[31:1]) diff --git a/dv/formal/thm/ibex.proof b/dv/formal/thm/ibex.proof new file mode 100644 index 0000000000..14318d1082 --- /dev/null +++ b/dv/formal/thm/ibex.proof @@ -0,0 +1,326 @@ +# Copyright lowRISC contributors. +# Copyright 2024 University of Oxford, see also CREDITS.md. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# Original author: Louis-Emile Ploix +# SPDX-License-Identifier: Apache-2.0 + +# This file contains mostly just small helper invariants about all sorts. They are all generally easy to prove. +# It also contains the first memory graph induction, it's consequences, and liveness. + +lemma ibex + NotDataIndTiming: have (~`CSR.data_ind_timing_o) + + PrivMorUMpp: have ((`CSR.mstatus_q.mpp == PRIV_LVL_U) || (`CSR.mstatus_q.mpp == PRIV_LVL_M)) + PrivMorUCur: have ((`CSR.priv_lvl_q == PRIV_LVL_U) || (`CSR.priv_lvl_q == PRIV_LVL_M)) + + PCBit0: have (~`CR.pc_id[0]) + MtvecLow: have (pre_mtvec[7:0] == 8'h01) + NmiMode: have (~`IDC.nmi_mode_q) + EBreakIntoDebug: have (~`IDC.ebreak_into_debug) + NoEnterDebugMode: have (~`IDC.enter_debug_mode) + IfBusErr: have (`IF.if_id_pipe_reg_we |-> ~`IF.if_instr_bus_err) + + WfiKill: have (wbexc_exists & wbexc_is_wfi |-> ex_kill) + ErrKill: have (wbexc_exists & wbexc_err |-> ex_kill) + + ReqRequiresInstr: have (data_req_o |-> ex_has_compressed_instr) + ReqRequiresNotIllegal: have (data_req_o |-> ~`CR.illegal_insn_id & ~`CR.illegal_c_insn_id) + + AluInstrMatch: have (`CR.instr_rdata_id == `CR.instr_rdata_alu_id) + + IdExNotReq: have (~ex_is_mem_instr -> ~data_req_o) + IdExNotMemMuteIncr: have (`CR.instr_valid_id & ~ex_is_mem_instr -> ~`ID.lsu_addr_incr_req_i & ~`ID.lsu_req_done_i) + ExecNoSpecialReq: have (`ID.instr_executing & ~instr_will_progress |-> ~`IDC.special_req) + StallIdFSM1: have (`ID.instr_executing && `ID.id_fsm_d != 0 |-> ~instr_will_progress) + + WbexcErrMonotonic: have (wbexc_exists & wbexc_err & ~instr_will_progress & ~wbexc_finishing |=> wbexc_err) + + NonCompressedMatch: have (wbexc_finishing && wbexc_instr[1:0] == 2'b11 |-> wbexc_instr == wbexc_decompressed_instr) + CompressedMatch: have (ex_has_compressed_instr |-> ex_compressed_instr[15:0] == `CR.instr_rdata_c_id) + + PostFlushNoInstr: have (`IDC.ctrl_fsm_cs == `IDC.FLUSH |=> ~`CR.instr_valid_id) + + DecompressionIllegalIdEx: have (ex_has_compressed_instr |-> decompressed_instr_illegal == `CR.illegal_c_insn_id) + DecompressionMatchIdEx: have (ex_has_compressed_instr & ~`CR.illegal_insn_id & ~`CR.illegal_c_insn_id |-> decompressed_instr == `CR.instr_rdata_id) + DecompressionIllegalWbexc: have (wbexc_exists |-> decompressed_instr_illegal_2 == wbexc_compressed_illegal) + DecompressionMatchWbexc: have (wbexc_exists & ~wbexc_illegal & ~wbexc_compressed_illegal |-> decompressed_instr_2 == wbexc_decompressed_instr) + + HasCompressed: have (`CR.instr_valid_id |-> ex_has_compressed_instr) + LSUInstrStable: have (`LSU.ls_fsm_cs != 0 |-> $stable(`CR.instr_rdata_id)) + + NoneIdleIsDecode: have (`LSU.ls_fsm_cs != 0 |-> `IDC.ctrl_fsm_cs == 5) + LSUFinishWaitRvalidMisGntsDone: have (`LSU.ls_fsm_cs == 4 && data_rvalid_i |-> instr_will_progress) + LSUFinishWaitRvalidMis: have (`LSU.ls_fsm_cs == 2 && data_rvalid_i && data_gnt_i |-> instr_will_progress) + LSUFinishWaitGnt: have (`LSU.ls_fsm_cs == 3 && data_gnt_i |-> instr_will_progress) + LSUFinishFast: have (`LSU.ls_fsm_cs == 0 && data_gnt_i && `LSU.ls_fsm_ns == 0 |-> instr_will_progress) + + SndGntReqFstGnt: have (mem_gnt_snd_d |-> mem_gnt_fst_q) + + WBOutstandingNoReq: have (outstanding_mem & ~`LSU.lsu_resp_valid_o |-> ~data_req_o) + + NotIdleReqDec: have (`LSU.ls_fsm_cs != `LSU.IDLE |-> `ID.lsu_req_dec) + NotIdleNoExErr: have (`LSU.ls_fsm_cs != `LSU.IDLE |-> ~ex_err) + + ProgressNoWbStall: have (instr_will_progress |-> ~`IDC.stall_wb_i) + + NoStoreWb: have (`WBG.wb_instr_type_q == WB_INSTR_STORE |-> ~`WB.rf_we_wb_o) + WbInstrDefined: have (`WBG.wb_instr_type_q != 2'b11) + + RfWriteWb: have (`CR.rf_write_wb & wbexc_finishing |-> `WB.rf_we_wb_o) + + CtrlWbexc: have (wbexc_exists |-> `IDC.ctrl_fsm_cs == `IDC.DECODE || `IDC.ctrl_fsm_cs == `IDC.FLUSH) + ProgressDecode: have (instr_will_progress |-> `IDC.ctrl_fsm_cs == `IDC.DECODE) + + BranchedProg: have (ex_has_branched_d & ~instr_will_progress |=> ex_has_branched_d | `IDC.wb_exception_o) + + IDCFsmAny: have (`IDC.ctrl_fsm_cs inside {`IDC.RESET, `IDC.BOOT_SET, `IDC.WAIT_SLEEP, `IDC.SLEEP, `IDC.FIRST_FETCH, `IDC.DECODE, `IDC.IRQ_TAKEN, `IDC.FLUSH}) + IDCFsmNotBoot: have (##3 ~(`IDC.ctrl_fsm_cs inside {`IDC.RESET, `IDC.BOOT_SET})) + + MemInstrEx: have (`LSU.ls_fsm_cs != `LSU.IDLE |-> ex_is_mem_instr) + MemInstrWbLoad: have (`WB.outstanding_load_wb_o |-> wbexc_is_load_instr) + MemInstrWbStore: have (`WB.outstanding_store_wb_o |-> wbexc_is_store_instr) + MemClockEn: have (`LSU.ls_fsm_cs != `LSU.IDLE |-> ibex_top_i.core_busy_q) + + ClockEn: have (instr_will_progress |-> ibex_top_i.clock_en) + EnWbProgress: have (`WB.en_wb_i |-> instr_will_progress) + DoneFin: have (`WBG.wb_done & `WBG.wb_valid_q & ~wbexc_err |-> wbexc_finishing) + ValidExists: have (`WBG.wb_valid_q |-> wbexc_exists) + + UnCheckableNoPresent: have (wbexc_exists & `ISS_CSR & ~wbexc_is_checkable_csr |-> ~has_spec_past) + + MemInstrWbWrite: have ( + wbexc_exists & wbexc_is_store_instr & ~wbexc_err |-> + ~`WBG.rf_we_wb_q & (`WBG.wb_instr_type_q != WB_INSTR_LOAD) + ) + ValidToBranch: have (ex_has_branched_d |-> `CR.instr_valid_id) + + LsuWeq: block + Ex: have (`LSU.ls_fsm_cs != `LSU.IDLE && mem_gnt_fst_q |-> ex_is_store_instr == `LSU.data_we_q) + / + Wb: have (outstanding_mem |-> wbexc_is_store_instr == `LSU.data_we_q) + + block + LSUEmpty: have (`LSU.ls_fsm_cs != `LSU.IDLE |-> ~wbexc_exists & ~ex_kill) + / + LSUEnd: have (`LSU.lsu_req_done_o |-> instr_will_progress) + + block + NoFinishingIRQ: have (wbexc_exists |-> ~wbexc_handling_irq) + / + SpecPastReg: have (wbexc_exists & wbexc_post_wX_en |-> spec_past_regs[wbexc_post_wX_addr] == wbexc_post_wX) + SpecPastWbexc: + each X Priv:(priv) Mstatus:(mstatus) Mie:(mie) Mcause:(mcause) Mtval:(mtval) Mtvec:(mtvec) \ + Mscratch:(mscratch) Mepc:(mepc) Mcounteren:(mcounteren) \ + Pmp_cfg:(pmp_cfg) Pmp_addr:(pmp_addr) Mseccfg:(mseccfg) \ + Pc:(pc) + have (wbexc_exists |-> spec_past_``X == wbexc_post_``X) + + / + + # This graph induction bounds the number of waiting responses, + # and establishes conditions for PMP errors, and relates the presence + # of waiting responses to the outstanding_mem signal. + # It includes all LSU states and a couple of extra states for + # once the instruction moves/has moved to writeback. + Memory: graph_induction +rev + inv idle ( + ~`LSU.handle_misaligned_q && + `CR.lsu_resp_valid == outstanding_mem + ) + inv idle_active ( + data_mem_assume.outstanding_reqs == data_gnt_i && + ~`LSU.pmp_err_q && ~`LSU.lsu_err_d && ~`LSU.handle_misaligned_q && + `CR.lsu_resp_valid == outstanding_mem + ) + inv wait_gnt_mis ( + $stable(data_addr_o) && ~has_resp_waiting_q && ~`LSU.lsu_err_q && + (data_req_o == ~`LSU.pmp_err_q) && (`LSU.pmp_err_q == `CR.pmp_req_err[2]) + ) + inv wait_gnt ( + $stable(data_addr_o) && ~has_resp_waiting_q && + (data_req_o == ~`LSU.pmp_err_q) && (`LSU.pmp_err_q == `CR.pmp_req_err[2]) + ) + inv wait_rvalid_mis ( + ($stable(`LSU.ls_fsm_cs) -> $stable(data_addr_o)) && + ~`LSU.lsu_err_q && + ((~`LSU.pmp_err_q && has_one_resp_waiting_q) || (`LSU.pmp_err_q && ~has_resp_waiting_q)) + ) + inv wait_rvalid_mis_gnts_done ( + $stable(data_addr_o) && + (`LSU.pmp_err_q == `CR.pmp_req_err[2]) && + ( + (~`LSU.lsu_err_q && ~`LSU.pmp_err_q && has_two_resp_waiting_q) || + ((`LSU.lsu_err_q != `LSU.pmp_err_q) && has_one_resp_waiting_q) || + (`LSU.lsu_err_q && `LSU.pmp_err_q && ~has_resp_waiting_q) + ) + ) + + inv step (`LSU.ls_fsm_ns == `LSU.IDLE && `CR.instr_type_wb != WB_INSTR_OTHER && has_one_resp_waiting_d) + inv step_fail (`LSU.ls_fsm_ns == `LSU.IDLE && `CR.instr_type_wb != WB_INSTR_OTHER && ~has_resp_waiting_d && `CR.pmp_req_err[2]) + inv wait (outstanding_mem && has_one_resp_waiting_q && ~`LSU.lsu_req_i && wbexc_exists) + inv end (outstanding_mem && has_one_resp_waiting_q && wbexc_exists) + inv fail (outstanding_mem && ~has_resp_waiting_q && wbexc_exists) + + entry ($rose(rst_ni)) -> idle + + node idle idle (`LSU.ls_fsm_cs == `LSU.IDLE && data_mem_assume.outstanding_reqs == data_gnt_i) + edge idle => idle + edge idle -> idle_active + + node idle_active idle_active (`LSU.ls_fsm_cs == `LSU.IDLE && `CR.lsu_req) + edge idle_active => wait_rvalid_mis wait_gnt_mis wait_gnt + edge idle_active -> step step_fail + + node wait_gnt_mis wait_gnt_mis (`LSU.ls_fsm_cs == `LSU.WAIT_GNT_MIS) + edge wait_gnt_mis => wait_gnt_mis wait_rvalid_mis + + node wait_rvalid_mis wait_rvalid_mis (`LSU.ls_fsm_cs == `LSU.WAIT_RVALID_MIS) + edge wait_rvalid_mis => wait_rvalid_mis wait_rvalid_mis_gnts_done wait_gnt + edge wait_rvalid_mis -> step step_fail + + node wait_gnt wait_gnt (`LSU.ls_fsm_cs == `LSU.WAIT_GNT) + edge wait_gnt => wait_gnt + edge wait_gnt -> step step_fail + + node wait_rvalid_mis_gnts_done wait_rvalid_mis_gnts_done (`LSU.ls_fsm_cs == `LSU.WAIT_RVALID_MIS_GNTS_DONE) + edge wait_rvalid_mis_gnts_done => wait_rvalid_mis_gnts_done + edge wait_rvalid_mis_gnts_done -> step step_fail + + node step step (`LSU.lsu_req_done_o && ~`LSU.pmp_err_d) + edge step => wait end + + node step_fail step_fail (`LSU.lsu_req_done_o && `LSU.pmp_err_d) + edge step_fail => fail + + node wait wait (has_resp_waiting_q && ~`CR.lsu_resp_valid && `LSU.ls_fsm_cs == `LSU.IDLE && ~instr_will_progress) + edge wait => wait end + + node end end (`CR.lsu_resp_valid && `LSU.ls_fsm_cs == `LSU.IDLE && data_rvalid_i) + edge end -> idle + + node fail fail (`CR.lsu_resp_valid && `LSU.ls_fsm_cs == `LSU.IDLE && ~data_rvalid_i) + edge fail -> idle + / + NoMemAccessNoRValid: have (`LSU.lsu_resp_valid_o -> outstanding_mem) + StallNoChangeA: have (`LSU.ls_fsm_cs != `LSU.IDLE && ($past(`LSU.ls_fsm_cs) != `LSU.IDLE || $past(`LSU.lsu_req_i)) |-> $stable(`ID.rf_rdata_a_fwd)) + StallNoChangeB: have (data_we_o && `LSU.ls_fsm_cs != `LSU.IDLE && ($past(`LSU.ls_fsm_cs) != `LSU.IDLE || $past(`LSU.lsu_req_i)) |-> $stable(`ID.rf_rdata_b_fwd)) + + BecameDecodeIsInstrStart: have (`IDC.ctrl_fsm_cs == `IDC.DECODE && !$stable(`IDC.ctrl_fsm_cs) |-> ~`ID.instr_valid_i | `CR.instr_new_id) + BecameDecodeIsEmptyWbexc: have (`IDC.ctrl_fsm_cs == `IDC.DECODE && !$stable(`IDC.ctrl_fsm_cs) |-> ~wbexc_exists) + FetchErrIsErr: have (wbexc_fetch_err & wbexc_exists |-> wbexc_err & `IDC.instr_fetch_err) + + MemOpRequiresValid: have (`LSU.ls_fsm_cs != `LSU.IDLE || `CR.lsu_req |-> `ID.instr_valid_i) + + MultEndState: have (instr_will_progress |=> `MULTG.mult_state_q == `MULTG.ALBL) + + / + + MemErrKind: have (finishing_executed && wbexc_is_mem_instr && ~wbexc_illegal && wbexc_err |-> `IDC.store_err_q | `IDC.load_err_q) + MemErrStructure: have (finishing_executed && wbexc_is_mem_instr && ~wbexc_illegal && wbexc_err |-> $past(instr_will_progress, 2) | $past(data_rvalid_i)) + MemNoErrStructure: have (finishing_executed && wbexc_is_mem_instr && ~wbexc_illegal && ~wbexc_err |-> data_rvalid_i) + + WbexcMemErrKindLoad: have (`IDC.load_err_q |-> wbexc_exists & wbexc_is_load_instr) + WbexcMemErrKindStore: have (`IDC.store_err_q |-> wbexc_exists & wbexc_is_store_instr) + + WbexcNotMemMuteLSU: have (~wbexc_is_mem_instr -> ~`CR.rf_we_lsu & ~`CR.lsu_resp_valid & ~`CR.lsu_load_err & ~`CR.lsu_store_err) + WbexcNotMemMuteMemErr: have (~wbexc_is_mem_instr -> ~`IDC.load_err_q & ~`IDC.store_err_q) + StallIdFSM2: have (`ID.instr_executing && ~instr_will_progress |=> `ID.instr_executing) + NewIdFSM: have (`CR.instr_new_id |-> `ID.id_fsm_q == 0) + PreNextPcMatch: have (instr_will_progress & ~ex_has_branched_d & ~`IDC.instr_fetch_err -> pre_nextpc == `CR.pc_if) # Slow! + StallNoChangeLsuWData: have ((data_we_o && `LSU.ls_fsm_cs != `LSU.IDLE && ($past(`LSU.ls_fsm_cs) != `LSU.IDLE || $past(`LSU.lsu_req_i)) |-> $stable(`LSU.lsu_wdata_i))) + + # These properties take some time to prove, but do prove with low proof effort. Just run them with individual Hp instances. + SpecStableLoad: have (ex_is_pres_load_instr && `LSU.ls_fsm_cs != `LSU.IDLE && ($past(`LSU.ls_fsm_cs) != `LSU.IDLE || $past(`LSU.lsu_req_i)) |-> $stable(spec_mem_read)) + SpecStableLoadSnd: have (ex_is_pres_load_instr && `LSU.ls_fsm_cs != `LSU.IDLE && ($past(`LSU.ls_fsm_cs) != `LSU.IDLE || $past(`LSU.lsu_req_i)) |-> $stable(spec_mem_read_snd)) + SpecStableLoadAddr: have (ex_is_pres_load_instr && `LSU.ls_fsm_cs != `LSU.IDLE && ($past(`LSU.ls_fsm_cs) != `LSU.IDLE || $past(`LSU.lsu_req_i)) |-> $stable(spec_mem_read_fst_addr)) + SpecStableLoadSndAddr: have (ex_is_pres_load_instr && `LSU.ls_fsm_cs != `LSU.IDLE && ($past(`LSU.ls_fsm_cs) != `LSU.IDLE || $past(`LSU.lsu_req_i)) |-> $stable(spec_mem_read_snd_addr)) + + SpecStableStore: have (ex_is_pres_store_instr && `LSU.ls_fsm_cs != `LSU.IDLE && ($past(`LSU.ls_fsm_cs) != `LSU.IDLE || $past(`LSU.lsu_req_i)) |-> $stable(spec_mem_write)) + SpecStableStoreSnd: have (ex_is_pres_store_instr && `LSU.ls_fsm_cs != `LSU.IDLE && ($past(`LSU.ls_fsm_cs) != `LSU.IDLE || $past(`LSU.lsu_req_i)) |-> $stable(spec_mem_write_snd)) + SpecStableStoreAddr: have (ex_is_pres_store_instr && `LSU.ls_fsm_cs != `LSU.IDLE && ($past(`LSU.ls_fsm_cs) != `LSU.IDLE || $past(`LSU.lsu_req_i)) |-> $stable(spec_mem_write_fst_addr)) + SpecStableStoreSndAddr: have (ex_is_pres_store_instr && `LSU.ls_fsm_cs != `LSU.IDLE && ($past(`LSU.ls_fsm_cs) != `LSU.IDLE || $past(`LSU.lsu_req_i)) |-> $stable(spec_mem_write_snd_addr)) + SpecStableStoreData: have (ex_is_pres_store_instr && `LSU.ls_fsm_cs != `LSU.IDLE && ($past(`LSU.ls_fsm_cs) != `LSU.IDLE || $past(`LSU.lsu_req_i)) |-> $stable(spec_mem_write_fst_wdata)) + SpecStableStoreSndData: have (ex_is_pres_store_instr && `LSU.ls_fsm_cs != `LSU.IDLE && ($past(`LSU.ls_fsm_cs) != `LSU.IDLE || $past(`LSU.lsu_req_i)) |-> $stable(spec_mem_write_snd_wdata)) + + LoadNotSpecWrite: have (ex_is_pres_load_instr |-> ~spec_mem_write) + StoreNotSpecRead: have (ex_is_pres_store_instr |-> ~spec_mem_read) + + FirstCycleNoGnt: have (`ID.instr_first_cycle |-> ~mem_gnt_fst_q) + MemStartFirstCycle: have (`LSU.ls_fsm_cs == `LSU.IDLE && `CR.lsu_req |-> `ID.instr_first_cycle) + # The below is slow! + DivInstrStable: have (`MULT.md_state_q != `MULT.MD_IDLE |-> $stable(`CR.instr_rdata_id) && `CR.instr_valid_id && (~`ID.stall_multdiv -> `MULT.md_state_q == `MULT.MD_FINISH) && `MULTG.mult_state_q == `MULTG.ALBL && `MULT.div_en_internal && (~wbexc_exists | wbexc_finishing)) + + InstrReqCount: have ( + (instr_mem_assume.outstanding_reqs_q == 2) == (`IFP.rdata_outstanding_q[1] && `IFP.rdata_outstanding_q[0]) && + (instr_mem_assume.outstanding_reqs_q == 1) == (~`IFP.rdata_outstanding_q[1] && `IFP.rdata_outstanding_q[0]) && + (instr_mem_assume.outstanding_reqs_q == 0) == (~`IFP.rdata_outstanding_q[1] && ~`IFP.rdata_outstanding_q[0]) + ) + + # Slow! + FetchErrRoot: have (`ID.instr_valid_i && (`IDC.ctrl_fsm_cs == `IDC.FLUSH -> ~$past(`IDC.csr_pipe_flush)) |-> spec_fetch_err == `ID.instr_fetch_err_i) + / + + MepcEn: have (`CSR.mepc_en |-> instr_will_progress | wbexc_finishing | wbexc_handling_irq) + DivNoKill: have (`MULT.md_state_q != `MULT.MD_IDLE |-> ~ex_kill) + + RTypeFirstCycle: have (`CR.instr_valid_id & ex_is_rtype |-> `ID.instr_first_cycle_id_o) + + DataMemGntMaxDelay: have (data_req_o |-> ##[0:`TIME_LIMIT] data_gnt_i) + DataMemRvalidMaxDelay: have (data_gnt_i |=> ##[0:`TIME_LIMIT] data_rvalid_i) + InstrMemGntMaxDelay: have (instr_req_o |-> ##[0:`TIME_LIMIT] instr_gnt_i) + InstrMemRvalidMaxDelay: have (instr_gnt_i |=> ##[0:`TIME_LIMIT] instr_rvalid_i) + + NormalMainResMatch: have (`ID.instr_valid_i && ~ex_kill && main_mode == MAIN_IDEX |-> spec_api_i.main_result == MAINRES_OK) + FetchErrMainResMatch: have (`ID.instr_valid_i && ~ex_kill && main_mode == MAIN_IFERR |-> spec_api_i.main_result == MAINRES_OK) + IRQMainResMatch: have (wbexc_handling_irq && main_mode == MAIN_IRQ |-> spec_api_i.main_result == MAINRES_OK) + + / + SpecEnUnreach: have (spec_en |-> ~spec_int_err) + +# Liveness proofs, currently only work if TIME_LIMIT = 1 and WFI_BOUND = 20. +# They work by adding up worst case bounds to arrive a weak but finite bound between calls to +# instr_will_progress. +lemma live + DivMiddleStep: have (`MULT.md_state_q == `MULT.MD_COMP && $stable(`MULT.md_state_q) |-> `MULT.div_counter_q == $past(`MULT.div_counter_q) - 1) + IF: have (always (##[0:10] `IF.if_instr_valid)) + PCSet: have (`CR.pc_set |-> ##[0:10] ~`CR.pc_set) + / + + DivStart: have (`CR.instr_new_id & `CR.instr_valid_id & ex_is_div |-> ##[0:7] (`MULT.div_counter_q == 5'd31 && `MULT.md_state_q == `MULT.MD_COMP) | instr_will_progress | (ex_kill & `CR.instr_valid_id)) + DivMiddle: have (`MULT.div_counter_q == 5'd31 && `MULT.md_state_q == `MULT.MD_COMP |-> ##30 `MULT.div_counter_q == 5'd1 && `MULT.md_state_q == `MULT.MD_COMP) + DivEnd: have (`MULT.div_counter_q == 5'd1 && `MULT.md_state_q == `MULT.MD_COMP |-> ##3 instr_will_progress) + + WFIStart: have (instr_will_progress & ex_is_wfi & ~ex_err |-> ##[0:5] `IDC.ctrl_fsm_cs == `IDC.SLEEP) + WFIMiddle: have (`IDC.ctrl_fsm_cs == `IDC.SLEEP |-> ##[0:20] `IDC.ctrl_fsm_cs == `IDC.SLEEP && `IDC.ctrl_fsm_ns == `IDC.FIRST_FETCH) + WFIEnd: have (`IDC.ctrl_fsm_cs == `IDC.SLEEP && `IDC.ctrl_fsm_ns == `IDC.FIRST_FETCH |-> ##[0:5] `IF.id_in_ready_i) + + NewProgNormal: have (`CR.instr_new_id & `CR.instr_valid_id & ~ex_is_div & ~ex_is_mem_instr |-> ##[0:5] (instr_will_progress | (ex_kill & `CR.instr_valid_id))) + NewProgMem: have (`CR.instr_new_id & `CR.instr_valid_id & ex_is_mem_instr |-> ##[0:10] (instr_will_progress | (ex_kill & `CR.instr_valid_id))) + + ProgReadyNormal: have (instr_will_progress & (~ex_is_wfi | ex_err) |-> ##[0:5] `IF.id_in_ready_i) + ProgReadyWFI: have (instr_will_progress & ex_is_wfi & ~ex_err |-> ##[0:30] `IF.id_in_ready_i) + KillReady: have (ex_kill & `CR.instr_valid_id |-> ##[1:35] `IF.id_in_ready_i && ~wbexc_exists && ~`CR.instr_valid_id) + + ReadyNew: have (`IF.id_in_ready_i |-> ##[1:11] `CR.instr_new_id & `CR.instr_valid_id) + Initial: have ($rose(rst_ni) |-> ##[1:15] `CR.instr_new_id & `CR.instr_valid_id) + FlushedNoKill: have (`CR.instr_new_id & `CR.instr_valid_id & ~wbexc_exists |-> ~ex_kill until_with instr_will_progress) + / + ReadyFlushed: have (`IF.id_in_ready_i && ~wbexc_exists && ~`CR.instr_valid_id |-> ##[1:11] `CR.instr_new_id & `CR.instr_valid_id & ~wbexc_exists) + / + DivNew1: have (`CR.instr_new_id & `CR.instr_valid_id & ex_is_div |-> ##[0:37] ((`MULT.div_counter_q == 5'd1 && `MULT.md_state_q == `MULT.MD_COMP) | instr_will_progress | (ex_kill & `CR.instr_valid_id))) + / + NewProgDiv: have (`CR.instr_new_id & `CR.instr_valid_id & ex_is_div |-> ##[0:40] (instr_will_progress | (ex_kill & `CR.instr_valid_id))) + ProgReady: have (instr_will_progress |-> ##[0:30] `IF.id_in_ready_i) + / + NewProg: have (`CR.instr_new_id & `CR.instr_valid_id |-> ##[0:40] (instr_will_progress | (ex_kill & `CR.instr_valid_id))) + / + FlushedProg: have (`CR.instr_new_id & `CR.instr_valid_id & ~wbexc_exists |-> ##[0:40] instr_will_progress) + / + KillSpecEn: have (ex_kill & `CR.instr_valid_id |-> ##[0:86] spec_en) + / + NewReady: have (`CR.instr_new_id & `CR.instr_valid_id |-> ##[0:75] `IF.id_in_ready_i) + NewSpecEn: have (`CR.instr_new_id & `CR.instr_valid_id |-> ##[0:40+86] spec_en) + / + NewLoop: have (`CR.instr_new_id & `CR.instr_valid_id |-> ##[1:86] `CR.instr_new_id & `CR.instr_valid_id) + / + New: have (always (##[1:86] `CR.instr_new_id & `CR.instr_valid_id)) diff --git a/dv/formal/thm/mem.proof b/dv/formal/thm/mem.proof new file mode 100644 index 0000000000..26fab31bba --- /dev/null +++ b/dv/formal/thm/mem.proof @@ -0,0 +1,169 @@ +# Copyright lowRISC contributors. +# Copyright 2024 University of Oxford, see also CREDITS.md. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# Original author: Louis-Emile Ploix +# SPDX-License-Identifier: Apache-2.0 + +lemma mem + MemNotWFI: have (wbexc_exists && wbexc_is_mem_instr |-> ~wbexc_is_wfi) + MemFin: have (finishing_executed && wbexc_is_mem_instr && ~wbexc_err |-> data_rvalid_i) + + RespWait: have (wbexc_exists && wbexc_is_mem_instr && ~wbexc_err && ~data_rvalid_i |-> `LSU.ls_fsm_cs == `LSU.IDLE && ~`LSU.lsu_req_i) + + EarlyLSUCtrlMatch: have ( + `LSU.ls_fsm_cs != `LSU.IDLE & spec_post_wX_en & mem_gnt_fst_q |-> + `LSU.rdata_offset_q == `LSU.data_offset && `LSU.data_type_q == `LSU.lsu_type_i && `LSU.data_sign_ext_q == `LSU.lsu_sign_ext_i && `LSU.data_we_q == `LSU.lsu_we_i + ) + + MisStates: have (`LSU.ls_fsm_cs == `LSU.WAIT_GNT_MIS || `LSU.ls_fsm_cs == `LSU.WAIT_RVALID_MIS || `LSU.ls_fsm_cs == `LSU.WAIT_RVALID_MIS_GNTS_DONE |-> `LSU.split_misaligned_access) + + LoadPMPErrorWx: have (`CR.instr_valid_id & ex_is_pres_load_instr |-> spec_post_wX_en == ~spec_has_pmp_err) + + # Another graph induction for LSU states which specifically bind what is + # happening to the specification. + MemSpec: graph_induction +rev + cond (ex_is_pres_mem_instr && (`LSU.ls_fsm_cs != `LSU.IDLE || `CR.lsu_req)) + + inv fst_req ( + (data_req_o -> (mem_req_fst_d && fst_mem_cmp)) && + (~data_req_o -> (~spec_mem_en_snd && spec_has_pmp_err && spec_post_mtval == `LSU.addr_last_d)) && + ~mem_gnt_fst_q && addr_last_d_matches + ) + inv fst_req_1 ( + (data_req_o -> (mem_req_fst_d && fst_mem_cmp && ~spec_has_pmp_err)) && + (~data_req_o -> (~spec_mem_en && spec_has_pmp_err && spec_post_mtval == `LSU.addr_last_d)) && + ~mem_gnt_fst_q && ~spec_mem_en_snd && ~`LSU.lsu_err_q && addr_last_d_matches + ) + inv fst_req_2 ( + (data_req_o -> (mem_req_fst_d && fst_mem_cmp)) && + (~data_req_o -> (~spec_mem_en_snd && spec_has_pmp_err && spec_post_mtval == `LSU.addr_last_d)) && + ~mem_gnt_fst_q && ~`LSU.lsu_err_q && + `LSU.split_misaligned_access && addr_last_d_matches + ) + inv snd_req_1 ( + ((`LSU.pmp_err_q && `LSU.data_pmp_err_i) -> (~spec_mem_en & ~mem_gnt_fst_q && ~spec_mem_en_snd && spec_has_pmp_err)) && + ((`LSU.pmp_err_q && data_req_o) -> (mem_req_fst_d && fst_mem_cmp && ~spec_mem_en_snd && spec_has_pmp_err)) && + ((~`LSU.pmp_err_q && `LSU.data_pmp_err_i) -> (mem_gnt_fst_q && ~spec_mem_en_snd && spec_has_pmp_err)) && + ((~`LSU.pmp_err_q && data_req_o) -> (mem_req_snd_d && snd_mem_cmp && ~spec_has_pmp_err)) && + `LSU.split_misaligned_access && addr_last_matches && + (`LSU.pmp_err_q -> (spec_post_mtval == `LSU.addr_last_q)) && + ((~`LSU.pmp_err_q && `LSU.data_pmp_err_i) -> (spec_post_mtval == `LSU.addr_last_d)) + ) + inv snd_req_2 ( + ((`LSU.lsu_err_q && `LSU.pmp_err_q) -> (~spec_mem_en & ~mem_gnt_fst_q && ~spec_mem_en_snd && spec_has_pmp_err)) && + ((`LSU.lsu_err_q && data_req_o) -> (mem_req_fst_d && fst_mem_cmp && ~spec_mem_en_snd && spec_has_pmp_err)) && + ((~`LSU.lsu_err_q && `LSU.pmp_err_q) -> (mem_gnt_fst_q && ~spec_mem_en_snd && spec_has_pmp_err)) && + ((~`LSU.lsu_err_q && data_req_o) -> (mem_req_snd_d && snd_mem_cmp && ~spec_has_pmp_err)) && + ((~`LSU.lsu_err_q && ex_is_load_instr) -> (`LSU.rdata_q == spec_mem_read_fst_rdata[31:8])) && + `LSU.split_misaligned_access && addr_last_matches && + (`LSU.lsu_err_q -> (spec_post_mtval == `LSU.addr_last_q)) && + ((~`LSU.lsu_err_q && `LSU.pmp_err_q) -> (spec_post_mtval == `LSU.addr_last_d)) + ) + inv req_done ( + ~data_req_o && mem_gnt_snd_q && ~spec_has_pmp_err && spec_mem_en && spec_mem_en_snd && + ~`LSU.lsu_err_q && ~`LSU.pmp_err_q && addr_last_matches + ) + + entry (`LSU.ls_fsm_cs == `LSU.IDLE && `CR.lsu_req) -> idle_active + + node idle_active fst_req (`LSU.ls_fsm_cs == `LSU.IDLE && `CR.lsu_req) + edge idle_active => wait_rvalid_mis wait_gnt_mis wait_gnt + edge idle_active -> step + + node wait_gnt_mis fst_req_2 (`LSU.ls_fsm_cs == `LSU.WAIT_GNT_MIS) + edge wait_gnt_mis => wait_gnt_mis wait_rvalid_mis + + node wait_rvalid_mis snd_req_1 (`LSU.ls_fsm_cs == `LSU.WAIT_RVALID_MIS) + edge wait_rvalid_mis => wait_rvalid_mis wait_rvalid_mis_gnts_done wait_gnt_split + edge wait_rvalid_mis -> step + + node wait_gnt_split snd_req_2 (`LSU.ls_fsm_cs == `LSU.WAIT_GNT && `LSU.split_misaligned_access) + edge wait_gnt_split => wait_gnt_split + edge wait_gnt_split -> step + + node wait_gnt fst_req_1 (`LSU.ls_fsm_cs == `LSU.WAIT_GNT && ~`LSU.split_misaligned_access) + edge wait_gnt => wait_gnt + edge wait_gnt -> step + + node wait_rvalid_mis_gnts_done req_done (`LSU.ls_fsm_cs == `LSU.WAIT_RVALID_MIS_GNTS_DONE) + edge wait_rvalid_mis_gnts_done => wait_rvalid_mis_gnts_done + edge wait_rvalid_mis_gnts_done -> step + + node step (1) (`LSU.lsu_req_done_o) +exit + / + LSULateRespFinishing: have (late_resp |-> wbexc_finishing || wbexc_err) + LSUEarlyRequestSplit: have (early_resp |-> has_snd_req) + LSUHold: have (data_req_o & ~data_gnt_i |=> data_addr_o == $past(data_addr_o) && data_we_o == $past(data_we_o)) + LSUHoldWrite: have (data_req_o & ~data_gnt_i & data_we_o |=> data_be_o == $past(data_be_o) && data_wdata_o == $past(data_wdata_o)) + LSUMaxTwoReqs: have (mem_gnt_snd_q |-> ~data_gnt_i) + LSU2ndReqStep: have (data_req_o & $past(data_gnt_i) & ~$past(instr_will_progress) |-> data_we_o == $past(data_we_o) && data_addr_o == $past(data_addr_o) + 4) + LSUInternalHold: have (`LSU.data_req_o && ~data_gnt_i && ~`LSU.pmp_err_q |=> $stable(data_addr_o)) + + NoMem: have (~ex_is_pres_mem_instr & instr_will_progress |-> ~mem_gnt_fst_d) # Top level property! Non memory instructions don't do memory requests! + + PMPErrMatch: have (ex_is_pres_mem_instr & instr_will_progress & ~`CR.illegal_insn_id & ~`CR.instr_fetch_err |-> spec_has_pmp_err == (`LSU.lsu_err_d | `LSU.pmp_err_d)) + + PCMaintainEx1: have (`ID.instr_valid_i & ex_is_pres_mem_instr & ~ex_err & ~ex_kill |-> pre_nextpc == post_pc) + PCMaintainEx2: have (`ID.instr_valid_i & ex_is_pres_mem_instr & ~ex_err & ~ex_kill & ~spec_has_pmp_err |-> pre_nextpc == spec_post_pc) + CSRMaintainEx: have (`ID.instr_valid_i & ex_is_pres_mem_instr & ~ex_err & ~ex_kill & ~spec_has_pmp_err |-> ex_csrs_match) + ExcCSRMaintainEx: have (`ID.instr_valid_i & ex_is_pres_mem_instr & ~ex_err & ~ex_kill & spec_has_pmp_err |-> ex_csrs_match_non_exc) + AltLSUVeryEarly: have (`LSU.ls_fsm_cs != `LSU.IDLE & spec_post_wX_en & ~lsu_had_first_resp |-> spec_post_wX == alt_lsu_very_early_res) + / + PCNoChangeNoBranch: have (wbexc_exists & wbexc_is_pres_mem_instr & ~wbexc_err & ~ex_has_branched_d |-> (`ID.instr_valid_i ? pre_pc : `CR.pc_if) == wbexc_dut_post_pc) + AltLSUEarly: have (`LSU.ls_fsm_cs != `LSU.IDLE & spec_post_wX_en & lsu_had_first_resp |-> spec_post_wX == alt_lsu_early_res) + / + AltLSU: have (wbexc_exists & wbexc_is_pres_load_instr & ~wbexc_err & wbexc_post_wX_en |-> wbexc_post_wX == alt_lsu_late_res) + PCNoChangeBranch: have (wbexc_exists & wbexc_is_pres_mem_instr & ~wbexc_err & ex_has_branched_d |-> pre_pc == wbexc_dut_post_pc) + PCExc: have (wbexc_exists & wbexc_is_pres_mem_instr & `LSU.data_or_pmp_err |-> wbexc_post_pc == { `IF.csr_mtvec_i[31:8], 8'h00 }) + + CSRMaintain: have (wbexc_exists & wbexc_is_pres_mem_instr & ~wbexc_err & ~`LSU.data_or_pmp_err |-> csrs_match) + CSRNoChange: have (wbexc_exists & wbexc_is_pres_mem_instr & ~wbexc_err |-> csrs_didnt_change) + PCMaintainWb: have (wbexc_exists & wbexc_is_pres_mem_instr & ~wbexc_err & ~`LSU.data_or_pmp_err |-> wbexc_post_pc == wbexc_dut_post_pc) + LoadAddrMaintain: have (wbexc_exists & wbexc_is_pres_load_instr & ~wbexc_err & ~`LSU.data_or_pmp_err |-> wbexc_post_wX_en && `WBG.rf_waddr_wb_q == wbexc_post_wX_addr && ~`WBG.rf_we_wb_q) + StoreAddrMaintain: have (wbexc_exists & wbexc_is_pres_store_instr & ~wbexc_err |-> ~wbexc_post_wX_en && ~`WBG.rf_we_wb_q) + ExcAddrMaintain: have (wbexc_exists & wbexc_is_pres_mem_instr & `LSU.data_or_pmp_err |-> ~wbexc_post_wX_en) + + ExcCSRMaintainMCause: have (wbexc_exists & wbexc_is_pres_mem_instr & `LSU.data_or_pmp_err & ~wbexc_fetch_err & ~wbexc_illegal |-> wbexc_post_mcause == (wbexc_is_store_instr ? ExcCauseStoreAccessFault : ExcCauseLoadAccessFault)) + ExcCSRMaintainMTVal: have (wbexc_exists & wbexc_is_pres_mem_instr & `LSU.data_or_pmp_err & ~wbexc_fetch_err & ~wbexc_illegal |-> wbexc_post_mtval == `LSU.addr_last_o) + ExcCSRMaintainMStatus: have (wbexc_exists & wbexc_is_pres_mem_instr & `LSU.data_or_pmp_err & ~wbexc_fetch_err & ~wbexc_illegal |-> + wbexc_post_mstatus.mie == 1'b0 && + wbexc_post_mstatus.mpie == pre_mstatus.mie && + wbexc_post_mstatus.mpp == (pre_priv == Machine ? PRIV_LVL_M : PRIV_LVL_U) + ) + ExcCSRMaintainMepc: have (wbexc_exists & wbexc_is_pres_mem_instr & `LSU.data_or_pmp_err & ~wbexc_fetch_err & ~wbexc_illegal |-> wbexc_post_mepc == `CR.pc_wb) + ExcCSRMaintainPriv: have (wbexc_exists & wbexc_is_pres_mem_instr & `LSU.data_or_pmp_err & ~wbexc_fetch_err & ~wbexc_illegal |-> wbexc_post_priv == Machine) + ExcCSRMaintainRest: have (wbexc_exists & wbexc_is_pres_mem_instr & `LSU.data_or_pmp_err & ~wbexc_fetch_err & ~wbexc_illegal |-> csrs_match_non_exc) + +# Top level load properties +lemma load + cond (ex_is_pres_load_instr) + + NoWe: have (spec_mem_read |-> ~data_we_o) + En: have (data_req_o |-> spec_mem_read) + / + SndEn: have (mem_req_snd_d |-> spec_mem_read_snd) + / # Fixme: Is all of this sequencing actually necessary? + FstAddr: have (mem_req_fst_d |-> spec_mem_read_fst_addr == data_addr_o) + SndAddr: have (mem_req_snd_d |-> spec_mem_read_snd_addr == data_addr_o) + / + End: in (instr_will_progress) + Fst: have (spec_mem_read |-> mem_gnt_fst_d) + Snd: have (spec_mem_read_snd |-> mem_gnt_snd_d) + +# Top level store properties +lemma store + cond (ex_is_pres_store_instr) + + We: have (spec_mem_write |-> data_we_o) + En: have (data_req_o |-> spec_mem_write) + / + SndEn: have (mem_req_snd_d |-> spec_mem_write_snd) + / + FstAddr: have (mem_req_fst_d |-> spec_mem_write_fst_addr == data_addr_o) + FstWData: have (mem_req_fst_d |-> (spec_mem_write_fst_wdata & fst_mask) == (data_wdata_o & fst_mask)) + SndAddr: have (mem_req_snd_d |-> spec_mem_write_snd_addr == data_addr_o) + SndWData: have (mem_req_snd_d |-> (spec_mem_write_snd_wdata & snd_mask) == (data_wdata_o & snd_mask)) + / + End: in (instr_will_progress) + Fst: have (spec_mem_write |-> mem_gnt_fst_d) + Snd: have (spec_mem_write_snd |-> mem_gnt_snd_d) diff --git a/dv/formal/thm/riscv.proof b/dv/formal/thm/riscv.proof new file mode 100644 index 0000000000..7c023a8dfc --- /dev/null +++ b/dv/formal/thm/riscv.proof @@ -0,0 +1,194 @@ +# Copyright lowRISC contributors. +# Copyright 2024 University of Oxford, see also CREDITS.md. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# Original author: Louis-Emile Ploix +# SPDX-License-Identifier: Apache-2.0 + +# This is the 'entry point' of the proofs, it invokes all the other proofs in thm/ + +def csr_split + each X Priv:(priv) Mstatus:(mstatus) Mie:(mie) Mcause:(mcause) Mtval:(mtval) Mtvec:(mtvec) \ + Mscratch:(mscratch) Mepc:(mepc) Mcounteren:(mcounteren) \ + Pmp_cfg:(pmp_cfg) Pmp_addr:(pmp_addr) Mseccfg:(mseccfg) + have (wbexc_dut_cmp_post_``X == wbexc_spec_cmp_post_``X) + +def spec_compliant_raw_csr_split + Addr: have (addr_match) + Data: have (data_match) + PC: have (pc_match) + use csr_split + +def spec_compliant_raw + Addr: have (addr_match) + Data: have (data_match) + CSR: have (csrs_match) + PC: have (pc_match) + +def spec_compliant + cond (finishing_executed & ~wbexc_illegal) + use spec_compliant_raw + +def spec_compliant_no_err + cond (finishing_executed & ~wbexc_illegal) + NoErr: have (~wbexc_err) + / + use spec_compliant_raw + +def structure_fast + cond (finishing_executed & ~wbexc_illegal) + Fast: have ($past(instr_will_progress)) + +def structure_fast_err + cond (finishing_executed & ~wbexc_illegal & wbexc_err) + Fast: have ($past(instr_will_progress)) + +lemma riscv + Ibex: lemma ibex + / + + Arith: + in I:(`ISS_ADDI | `ISS_SLTI | `ISS_SLTIU | `ISS_XORI | `ISS_ORI | `ISS_ANDI) \ + R:(`ISS_ADD | `ISS_SUB | `ISS_SLL | `ISS_SLT | `ISS_SLTU | `ISS_XOR | `ISS_SRL | `ISS_SRA | `ISS_OR | `ISS_AND) \ + Shift:(`ISS_SHIFTIOP) + use structure_fast + / + use spec_compliant_no_err + + # Never actually proved, just assumed to be true instead. + MType: + in Mul:(`ISS_MUL) MulH:(`ISS_MULH) MulHSH:(`ISS_MULHSH) MulHU:(`ISS_MULHU) Div:(`ISS_DIV) DivU:(`ISS_DIVU) Rem:(`ISS_REM) RemU:(`ISS_REMU) + use spec_compliant_no_err + + CSR: + in (`ISS_CSR & wbexc_is_checkable_csr) + use structure_fast + / + use spec_compliant + split_bool Err:(wbexc_err) + + BType: + in (`ISS_BTYPE) + lemma btype + use structure_fast + / + use spec_compliant + + JType: block + lemma jump + / + in Jal:(`ISS_JAL) Jalr:(`ISS_JALR) + use structure_fast + / + use spec_compliant + + UType: + in Lui:(`ISS_LUI) Auipc:(`ISS_AUIPC) + use structure_fast + / + use spec_compliant + + Fence: + in Fence:(`ISS_FENCE) FenceI:(`ISS_FENCEI) + use structure_fast + / + use spec_compliant + + Special: + in ECall:(`ISS_ECALL) EBreak:(`ISS_EBREAK) MRet:(`ISS_MRET) + use structure_fast + / + use spec_compliant + + WFI: + in (`ISS_WFI && finishing_executed && ~wbexc_illegal) + use structure_fast + / + use spec_compliant_raw + split_bool Err:(wbexc_err) + + Mem: block + lemma mem + / + Load: lemma load + Store: lemma store + / + in L:(wbexc_is_pres_load_instr) S:(wbexc_is_pres_store_instr) + use spec_compliant + split_bool Err:(wbexc_err) + + IRQ: in (wbexc_handling_irq) + PC: have (pc_match) + CSR: have (csrs_match) + + Illegal: in (wbexc_illegal & can_check_illegal & wbexc_finishing & ~wbexc_fetch_err & (`ISS_CSR -> wbexc_is_checkable_csr)) + Fast: have ($past(instr_will_progress)) + / + use spec_compliant_raw + + FetchErr: in (wbexc_finishing & wbexc_fetch_err) + Fast: have ($past(instr_will_progress)) + / + use spec_compliant_raw + + / + + # Uncomment this for liveness proofs, see ibex.proof for a description of when this can be done. + # Liveness: lemma live + + Top: in (wbexc_finishing & (`ISS_CSR -> wbexc_is_checkable_csr)) + use spec_compliant_raw + + / + + RegMatch: + each i 1:(1) 2:(2) 3:(3) 4:(4) 5:(5) 6:(6) 7:(7) 8:(8) 9:(9) 10:(10) 11:(11) 12:(12) 13:(13) 14:(14) \ + 15:(15) 16:(16) 17:(17) 18:(18) 19:(19) 20:(20) 21:(21) 22:(22) 23:(23) 24:(24) 25:(25) 26:(26) \ + 27:(27) 28:(28) 29:(29) 30:(30) 31:(31) + have ((~`CR.rf_write_wb || `CR.rf_waddr_wb != i) & spec_past_has_reg[i] |-> pre_regs[i] == spec_past_regs[i]) + + SpecPastNoWbexc: + in (has_spec_past & ~wbexc_exists) + each X Priv:(priv) Mstatus:(mstatus) Mie:(mie) Mcause:(mcause) Mtval:(mtval) Mtvec:(mtvec) \ + Mscratch:(mscratch) Mepc:(mepc) Mcounteren:(mcounteren) \ + Pmp_cfg:(pmp_cfg) Pmp_addr:(pmp_addr) Mseccfg:(mseccfg) + have (spec_past_``X == pre_``X) + + SleepSpecPastPC: have (has_spec_past & (`IDC.ctrl_fsm_cs == `IDC.WAIT_SLEEP || `IDC.ctrl_fsm_cs == `IDC.SLEEP) |-> spec_past_pc == `CR.pc_if) + + / + + # If you find a visualize SST trace for this property of length 5, you will be told none exists. + # If you find an SST trace via prove -sst, one will be found, but upon visualizing it JG will report that an + # assumption was violated. This leads me to believe that this property is provable by 5-induction, but JG has + # a bug preventing it from seeing so. It proves instead via engine D. + SpecPastNoWbexcPC: have (has_spec_past & ~wbexc_exists |-> spec_past_pc == (`ID.instr_valid_i ? pre_pc : `CR.pc_if)) + / + + # Prove these with engine D (apart from Live which needs Oh) + Wrap: in (spec_en) + each X Priv:(priv) Mstatus:(mstatus) Mie:(mie) Mcause:(mcause) Mtval:(mtval) Mtvec:(mtvec) \ + Mscratch:(mscratch) Mepc:(mepc) Mcounteren:(mcounteren) \ + Pmp_cfg:(pmp_cfg) Pmp_addr:(pmp_addr) Mseccfg:(mseccfg) \ + Pc:(pc) + have (has_spec_past |-> pre_``X == spec_past_``X) + + RegA: have (spec_rx_a_en && (spec_rx_a_addr != 0) && spec_past_has_reg[spec_rx_a_addr] |-> spec_rx_a == spec_past_regs[spec_rx_a_addr]) + RegB: have (spec_rx_b_en && (spec_rx_b_addr != 0) && spec_past_has_reg[spec_rx_b_addr] |-> spec_rx_b == spec_past_regs[spec_rx_b_addr]) + + # Live: have (always (##[1:157 + 2*`WFI_BOUND + 17*`TIME_LIMIT] spec_en)) + + Mem: + block + En: have (data_req_o |-> spec_mem_en) + SndEn: have (mem_req_snd_d |-> spec_mem_en_snd) + + We: have (data_req_o |-> data_we_o == spec_mem_write && data_we_o == ~spec_mem_read) + + FstAddr: have (mem_req_fst_d |-> spec_mem_fst_addr == data_addr_o) + SndAddr: have (mem_req_snd_d |-> spec_mem_snd_addr == data_addr_o) + + FstWData: have (mem_req_fst_d & data_we_o |-> (spec_mem_write_fst_wdata & fst_mask) == (data_wdata_o & fst_mask)) + SndWData: have (mem_req_snd_d & data_we_o |-> (spec_mem_write_snd_wdata & snd_mask) == (data_wdata_o & snd_mask)) + + FstEnd: have (spec_en & spec_mem_en |-> mem_gnt_fst_d) + SndEnd: have (spec_en & spec_mem_en_snd |-> mem_gnt_snd_d) diff --git a/dv/formal/verify.tcl b/dv/formal/verify.tcl new file mode 100644 index 0000000000..d2206dbe1a --- /dev/null +++ b/dv/formal/verify.tcl @@ -0,0 +1,271 @@ +# Copyright lowRISC contributors. +# Copyright 2024 University of Oxford, see also CREDITS.md. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# The underlying commands and reports of this script are copyrighted by Cadence. +# We thank Cadence for granting permission to share our research to help +# promote and foster the next generation of innovators. +# Original author: Louis-Emile Ploix +# SPDX-License-Identifier: Apache-2.0 + +set_prove_cache_path jgproofs +set_prove_cache on +set_prove_cache_mode coi + +set_prove_per_property_time_limit 10s + +set ibex_root ../../ +set sail_lib_dir $env(SAIL_DIR)/lib/sv +set prim $ibex_root/vendor/lowrisc_ip/ip/prim/rtl/ +set dv $ibex_root/vendor/lowrisc_ip/dv/sv/dv_utils/ + +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_pkg.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_alu.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_compressed_decoder.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_controller.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_cs_registers.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_decoder.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_ex_block.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_id_stage.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_if_stage.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_wb_stage.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_load_store_unit.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_multdiv_slow.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_counter.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_multdiv_fast.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_prefetch_buffer.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_fetch_fifo.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_pmp.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_core.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_csr.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_register_file_ff.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/vendor/lowrisc_ip/ip/prim/rtl/prim_secded_pkg.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/vendor/lowrisc_ip/ip/prim/rtl/prim_assert.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/vendor/lowrisc_ip/ip/prim/rtl/prim_ram_1p_pkg.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/vendor/lowrisc_ip/ip/prim_generic/rtl/prim_generic_buf.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/vendor/lowrisc_ip/ip/prim_generic/rtl/prim_generic_clock_gating.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/dv/uvm/core_ibex/common/prim/prim_buf.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/dv/uvm/core_ibex/common/prim/prim_pkg.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/dv/uvm/core_ibex/common/prim/prim_clock_gating.sv +analyze -sv12 +define+SYNTHESIS -incdir $prim -incdir $dv $ibex_root/rtl/ibex_top.sv + +analyze -sv12 -incdir $sail_lib_dir build/ibexspec.sv +analyze -sv12 spec/stub.sv + +analyze -sv12 spec/spec_api.sv + +# analyze -sv12 bound/binder.sv +# analyze -sv12 bound/if.sv +analyze -sv12 check/peek/alt_lsu.sv + +analyze -sv12 check/top.sv + +elaborate -top top -disable_auto_bbox +clock clk_i +reset ~rst_ni + +set_proofgrid_max_local_jobs 10 +set_proofgrid_per_engine_max_local_jobs 8 + +# AMcustom1 +custom_engine -add -code hT3N1rhP11/52HrFRS21ROp2LOjVTgPvT8L8BGXHgLhaIuqtT4nARFjUqrBL+7pLmaTOzBepZW/Jm8SSrHDybSQtoNiO3y43wk+dEoWlsZizu97Fih6O6lPVG/LpWP5SsUPwlGagLNa1FKEFvwVXyX7//8prySbvSxIHXr5er+z4RAEA + +# AMcustom2 +custom_engine -add -code hT3Ng7hP/feYQOTDZ3qhYOwGAM51eA+J/FjkM5shLioAsqhgLR4Ft+O1BuKG6ilQ83B9tLXSmmqwm7g9AQA + +# Ncustom3 +custom_engine -add -code hT3OXrhPByJp3TrFSTLhUmMH4KVtJgmTCnNDF46yMXOKY48m4LS5nE7yBzFjA7kDuwO/GhGUpEPiky3p3wmPn3dJZHxFMsafSoObRzSC+tn7sEY0WbTdZ/FV4hL3MYH/b1CIUvXSWR4wqEoVLsmMOD4xIPT4lI1LO6ZCO7PnnWQuLwetnvKlrXx6wCW/A+x+enqslg1YPobi4wEF/EvbzOvcTYdJvl2s4H2yZg2b2ofAVN5WvhWk1HoBAA + +# ADcustom4 +custom_engine -add -code hT3Nv7hPv1752HrFRa2kROx2f/ECJeZB2AZsLdlO8VwmIuqtT4nIDFXclhg+O+o+DMmQCekbheGk0kK28laA9gaOFDXsQp29J3X615HY1IPHJWd6FUFvCHjO+p1k652b5JJvZlShNpGlGSXAiQe/mEAj6tEBAA + +# Ncustom5 +custom_engine -add -code hT3Ng7hPPfiYQOTDZ3qhYOwGAM51eA+J/FjkM5shLioAsqhgLR4Ft+O1BuKG6ilQ83B9tLXSl+CwjiTMAQA + +# Mpcustom6 +custom_engine -add -code hT3NZbhP9fmY2AbBQnsjfOxn6c+6e6yL+/e8fZFmaQrnlgEA + +# prove -bg -all -covers + +proc enter_stopat {} { + stopat -reset {*} +} + +proc exit_stopat {} { + stopat -reset -clear +} + +proc assume_mtypes {} { + assume -from_assert {Step10::top.MType_*_Data} +} + +proc prove_lemmas {} { + assume_mtypes + + prove -task Step0 + report -task Step0 + + prove -task Step1 + report -task Step1 + + prove -task Step2 + report -task Step2 + + prove -task Step3 + report -task Step3 + + prove -task Step4 + report -task Step4 + + prove -task Step5 + report -task Step5 + + prove -property Step6::top.Ibex_FetchErrRoot + prove -property {Step6::top.Ibex_SpecStableLoad Step6::top.Ibex_SpecStableLoadSnd Step6::top.Ibex_SpecStableLoadAddr Step6::top.Ibex_SpecStableLoadSndAddr Step6::top.Ibex_SpecStableStore Step6::top.Ibex_SpecStableStoreSnd Step6::top.Ibex_SpecStableStoreAddr Step6::top.Ibex_SpecStableStoreSndAddr Step6::top.Ibex_SpecStableStoreData Step6::top.Ibex_SpecStableStoreSndData} -engine_mode Hp + prove -task Step6 + report -task Step6 + + prove -task Step7 + report -task Step7 + + prove -task Step8 + report -task Step8 + + prove -property {Step9::top.Arith_I_Fast Step9::top.Arith_R_Fast Step9::top.Arith_Shift_Fast Step9::top.CSR_Fast Step9::top.BType_FinishFirstCycle Step9::top.BType_Fast Step9::top.JType_FinishFirstCycle Step9::top.UType_Lui_Fast Step9::top.UType_Auipc_Fast Step9::top.Fence_Fence_Fast Step9::top.Fence_FenceI_Fast Step9::top.Special_ECall_Fast Step9::top.Special_EBreak_Fast Step9::top.Special_MRet_Fast Step9::top.WFI_Fast} + prove -property {Step9::top.Mem_MemSpec_Initial Step9::top.Mem_MemSpec_Initial_IdleActive Step9::top.Mem_MemSpec_WaitRvalidMis_Step Step9::top.Mem_MemSpec_WaitRvalidMis_WaitRvalidMis_Inv Step9::top.Mem_MemSpec_WaitRvalidMis_WaitRvalidMisGntsDone_Inv Step9::top.Mem_MemSpec_WaitRvalidMis_WaitGntSplit_Inv Step9::top.Mem_MemSpec_WaitRvalidMis_Step_Inv Step9::top.Mem_MemSpec_WaitGntSplit_Step Step9::top.Mem_MemSpec_WaitGntSplit_WaitGntSplit_Inv Step9::top.Mem_MemSpec_WaitGntSplit_Step_Inv Step9::top.Mem_MemSpec_WaitGnt_Step Step9::top.Mem_MemSpec_WaitGnt_WaitGnt_Inv Step9::top.Mem_MemSpec_WaitGnt_Step_Inv Step9::top.Mem_MemSpec_WaitRvalidMisGntsDone_Step Step9::top.Mem_MemSpec_WaitRvalidMisGntsDone_WaitRvalidMisGntsDone_Inv Step9::top.Mem_MemSpec_WaitRvalidMisGntsDone_Step_Inv Step9::top.Mem_MemSpec_IdleActive_Step Step9::top.Mem_MemSpec_IdleActive_WaitRvalidMis_Inv Step9::top.Mem_MemSpec_IdleActive_WaitGntMis_Inv Step9::top.Mem_MemSpec_IdleActive_WaitGnt_Inv Step9::top.Mem_MemSpec_IdleActive_Step_Inv Step9::top.Mem_MemSpec_WaitGntMis_Step Step9::top.Mem_MemSpec_WaitGntMis_WaitGntMis_Inv Step9::top.Mem_MemSpec_WaitGntMis_WaitRvalidMis_Inv} + prove -property {Step9::top.IRQ_PC Step9::top.IRQ_CSR} + prove -property {Step9::top.Illegal_Fast Step9::top.FetchErr_Fast} + prove -task Step9 + report -task Step9 + + prove -property {Step10::top.Arith_I_NoErr Step10::top.Arith_R_NoErr Step10::top.Arith_Shift_NoErr Step10::top.CSR_Addr_NotErr Step10::top.CSR_Data_NotErr Step10::top.CSR_CSR_NotErr Step10::top.CSR_PC_NotErr Step10::top.CSR_Addr_Err Step10::top.CSR_Data_Err Step10::top.CSR_CSR_Err Step10::top.CSR_PC_Err} + prove -property {Step10::top.BType_BInd_Initial Step10::top.BType_BInd_Initial_Stall Step10::top.BType_BInd_Initial_Oma Step10::top.BType_BInd_Initial_Progress Step10::top.BType_BInd_Stall_Step Step10::top.BType_BInd_Stall_Stall_Inv Step10::top.BType_BInd_Stall_Progress_Inv Step10::top.BType_BInd_Oma_Step_0 Step10::top.BType_BInd_Oma_Oma_Inv_0 Step10::top.BType_BInd_Oma_Progress_Inv_0 Step10::top.BType_BInd_Oma_Step_1 Step10::top.BType_BInd_Oma_Oma_Inv_1 Step10::top.BType_BInd_Oma_Progress_Inv_1} + prove -property {Step10::top.JType_JInd_Initial Step10::top.JType_JInd_Initial_Stall Step10::top.JType_JInd_Initial_Oma Step10::top.JType_JInd_Initial_Progress Step10::top.JType_JInd_Stall_Step Step10::top.JType_JInd_Stall_Stall_Inv Step10::top.JType_JInd_Stall_Progress_Inv Step10::top.JType_JInd_Oma_Step Step10::top.JType_JInd_Oma_Oma_Inv Step10::top.JType_JInd_Oma_Progress_Inv} + prove -property {Step10::top.UType_Lui_Addr Step10::top.UType_Lui_Data Step10::top.UType_Lui_CSR Step10::top.UType_Lui_PC Step10::top.UType_Auipc_Addr Step10::top.UType_Auipc_Data Step10::top.UType_Auipc_CSR Step10::top.UType_Auipc_PC} + prove -property {Step10::top.Fence_Fence_Addr Step10::top.Fence_Fence_Data Step10::top.Fence_Fence_CSR Step10::top.Fence_Fence_PC Step10::top.Fence_FenceI_Addr Step10::top.Fence_FenceI_Data Step10::top.Fence_FenceI_CSR Step10::top.Fence_FenceI_PC} + prove -property {Step10::top.Special_ECall_Addr Step10::top.Special_ECall_Data Step10::top.Special_ECall_CSR Step10::top.Special_ECall_PC Step10::top.Special_EBreak_Addr Step10::top.Special_EBreak_Data Step10::top.Special_EBreak_CSR Step10::top.Special_EBreak_PC Step10::top.Special_MRet_Addr Step10::top.Special_MRet_Data Step10::top.Special_MRet_CSR Step10::top.Special_MRet_PC} + prove -property {Step10::top.WFI_Addr_NotErr Step10::top.WFI_Data_NotErr Step10::top.WFI_PC_NotErr Step10::top.WFI_CSR_NotErr} + prove -property {Step10::top.WFI_Addr_Err Step10::top.WFI_Data_Err Step10::top.WFI_PC_Err Step10::top.WFI_CSR_Err} + prove -property {Step10::top.Mem_MemSpec_IdleActive_Rev Step10::top.Mem_MemSpec_WaitGntMis_Rev Step10::top.Mem_MemSpec_WaitRvalidMis_Rev Step10::top.Mem_MemSpec_WaitGntSplit_Rev Step10::top.Mem_MemSpec_WaitGnt_Rev Step10::top.Mem_MemSpec_WaitRvalidMisGntsDone_Rev Step10::top.Mem_MemSpec_Step_Rev} + prove -property {Step10::top.Illegal_Addr Step10::top.Illegal_Data Step10::top.Illegal_PC Step10::top.Illegal_CSR} + prove -property {Step10::top.FetchErr_Addr Step10::top.FetchErr_Data Step10::top.FetchErr_PC Step10::top.FetchErr_CSR} + prove -property {Step10::top.MType_Mul_Addr Step10::top.MType_Mul_CSR Step10::top.MType_Mul_PC Step10::top.MType_MulH_Addr Step10::top.MType_MulH_CSR Step10::top.MType_MulH_PC Step10::top.MType_MulHSH_Addr Step10::top.MType_MulHSH_CSR Step10::top.MType_MulHSH_CSR Step10::top.MType_MulHSH_PC Step10::top.MType_MulHU_Addr Step10::top.MType_MulHU_CSR Step10::top.MType_MulHU_PC} + prove -property {Step10::top.MType_Div_Addr Step10::top.MType_Div_CSR Step10::top.MType_Div_PC Step10::top.MType_DivU_Addr Step10::top.MType_DivU_CSR Step10::top.MType_DivU_PC Step10::top.MType_Rem_Addr Step10::top.MType_Rem_CSR Step10::top.MType_Rem_PC Step10::top.MType_RemU_Addr Step10::top.MType_RemU_CSR Step10::top.MType_RemU_PC} + prove -task Step10 + report -task Step10 + + prove -property {Step11::top.Arith_I_Addr Step11::top.Arith_I_Data Step11::top.Arith_I_CSR Step11::top.Arith_I_PC} + prove -property {Step11::top.Arith_R_Addr Step11::top.Arith_R_Data Step11::top.Arith_R_CSR Step11::top.Arith_R_PC} + prove -property {Step11::top.Arith_Shift_Addr Step11::top.Arith_Shift_Data Step11::top.Arith_Shift_CSR Step11::top.Arith_Shift_PC} + prove -property {Step11::top.BType_BInd_Stall_Rev Step11::top.BType_BInd_Oma_Rev_0 Step11::top.BType_BInd_Oma_Rev_1 Step11::top.BType_BInd_Progress_Rev Step11::top.JType_JInd_Progress_Rev Step11::top.JType_JInd_Stall_Rev Step11::top.JType_JInd_Oma_Rev} + prove -property {Step11::top.Mem_MemSpec_IdleActive Step11::top.Mem_MemSpec_WaitGntMis Step11::top.Mem_MemSpec_WaitRvalidMis Step11::top.Mem_MemSpec_WaitGntSplit Step11::top.Mem_MemSpec_WaitGnt Step11::top.Mem_MemSpec_WaitRvalidMisGntsDone Step11::top.Mem_MemSpec_Step} + prove -task Step11 + report -task Step11 + + prove -task Step12 + report -task Step12 + + prove -task Step13 + report -task Step13 + + prove -task Step14 + report -task Step14 + + prove -task Step15 + report -task Step15 + + prove -task Step16 + report -task Step16 + + prove -task Step17 + report -task Step17 + + prove -task Step18 + report -task Step18 + + prove -property {Step19::top.Mem_L_*_Err} + prove -property {Step19::top.Mem_L_*_NoErr} + prove -property {Step19::top.Mem_L_*} + prove -property {Step19::top.Mem_S_*_Err} + prove -property {Step19::top.Mem_S_*_NoErr} + prove -property {Step19::top.Mem_S_*} + prove -task Step19 + report -task Step19 +} + +proc prove_no_liveness {} { + prove_lemmas + + prove -task Step20 + report -task Step20 + + prove -task Step21 -engine_mode D + report -task Step21 + + prove -task Step22 + report -task Step22 + + prove -task Step23 + report -task Step23 +} + +# TODO fix step numbers in here +proc prove_liveness {} { + prove_lemmas + + prove -property {Step18::top.Liveness_*} + prove -task Step18 + report -task Step18 + + prove -property Step19::top.Liveness_DivStart + prove -property Step19::top.Liveness_DivMiddle + prove -property Step19::top.Liveness_DivEnd + prove -property Step19::top.Liveness_WFIStart + prove -property Step19::top.Liveness_WFIMiddle + prove -property Step19::top.Liveness_WFIEnd + prove -property Step19::top.Liveness_NewProgNormal + prove -property Step19::top.Liveness_NewProgMem + prove -property Step19::top.Liveness_ProgReadyNormal + prove -property Step19::top.Liveness_ProgReadyWFI + prove -property Step19::top.Liveness_KillReady + prove -property Step19::top.Liveness_ReadyNew + prove -property Step19::top.Liveness_Initial + prove -property Step19::top.Liveness_FlushedNoKill + prove -task Step19 + report -task Step19 + + prove -task Step20 + report -task Step20 + + prove -task Step21 + report -task Step21 + + prove -task Step22 + report -task Step22 + + prove -task Step23 + report -task Step23 + + prove -task Step24 + report -task Step24 + + prove -task Step25 + report -task Step25 + + prove -task Step26 + report -task Step26 + + prove -task Step27 + report -task Step27 + + prove -task Step28 + report -task Step28 + + prove -task Step29 + report -task Step29 + + prove -property Step30::top.Live + prove -task Step30 -engine_mode D + report -task Step30 +} + +source build/psgen.tcl