cores/microwatt: add support for concurrent execution of formal checks.

main
Jean-François Nguyen 2 years ago
parent 6ae4978f0c
commit 5c9bc3e68c

@ -12,58 +12,18 @@ git clone git@git.openpower.foundation:jfng/microwatt -b powerfv


## Usage ## Usage


### Enter/exit the Python virtualenv

``` ```
# Enter virtual environment
poetry shell poetry shell


python ./run.py --help python ./run.py --help


# Exit virtual environment
exit exit
``` ```


## Example ### Run the checks locally

Let's try the [uniqueness check](https://git.openpower.foundation/cores/power-fv/src/branch/main/power_fv/checks/unique.py) on Microwatt.

First, we find the minimal number of clock cycles needed to observe at least two retired instructions:

```
python run.py unique --mode=cover --post=15
```
```
SBY 15:28:31 [unique_cover_tb] engine_0: ## 0:00:29 Reached cover statement at /home/jf/src/power-fv/power_fv/checks/unique.py:64 ($12) in step 10.
SBY 15:28:31 [unique_cover_tb] engine_0: ## 0:00:29 Writing trace to VCD file: engine_0/trace0.vcd
SBY 15:28:53 [unique_cover_tb] engine_0: ## 0:00:51 Writing trace to Verilog testbench: engine_0/trace0_tb.v
SBY 15:28:53 [unique_cover_tb] engine_0: ## 0:00:51 Writing trace to constraints file: engine_0/trace0.smtc
SBY 15:28:53 [unique_cover_tb] engine_0: ## 0:00:51 Checking cover reachability in step 10..
SBY 15:28:55 [unique_cover_tb] engine_0: ## 0:00:52 Checking cover reachability in step 11..
SBY 15:29:09 [unique_cover_tb] engine_0: ## 0:01:07 Reached cover statement at /home/jf/src/power-fv/power_fv/checks/unique.py:65 ($15) in step 11.
SBY 15:29:09 [unique_cover_tb] engine_0: ## 0:01:07 Writing trace to VCD file: engine_0/trace1.vcd
SBY 15:29:33 [unique_cover_tb] engine_0: ## 0:01:31 Writing trace to Verilog testbench: engine_0/trace1_tb.v
SBY 15:29:33 [unique_cover_tb] engine_0: ## 0:01:31 Writing trace to constraints file: engine_0/trace1.smtc
```

It takes at least 10 clock cycles to observe one retired instruction, and at least 11 for two instructions.


In BMC mode, let's set the pre-condition trigger to 10 cycles, and the post-condition to 12. We should really use higher values to cover a larger state space, but we just want to minimize execution time for this demo.

```
python ./run.py unique --mode=bmc --pre=10 --post=12
```
``` ```
SBY 16:14:26 [unique_bmc_tb] engine_0: starting process "cd unique_bmc_tb; yosys-smtbmc --presat --unroll --noprogress -t 12:13 --append 0 --dump-vcd engine_0/trace.vcd --dump-vlogtb engine_0/trace_tb.v --dump-smtc engine_0/trace.smtc model/design_smt2.smt2" python run.py --jobs=$(nproc)
SBY 16:14:26 [unique_bmc_tb] engine_0: ## 0:00:00 Solver: yices
SBY 16:14:37 [unique_bmc_tb] engine_0: ## 0:00:11 Skipping step 0..
# ...
SBY 16:14:42 [unique_bmc_tb] engine_0: ## 0:00:16 Skipping step 11..
SBY 16:14:43 [unique_bmc_tb] engine_0: ## 0:00:16 Checking assumptions in step 12..
SBY 16:15:12 [unique_bmc_tb] engine_0: ## 0:00:46 Checking assertions in step 12..
SBY 16:15:13 [unique_bmc_tb] engine_0: ## 0:00:46 Status: passed
SBY 16:15:13 [unique_bmc_tb] engine_0: finished (returncode=0)
SBY 16:15:13 [unique_bmc_tb] engine_0: Status returned by engine: pass
SBY 16:15:13 [unique_bmc_tb] summary: Elapsed clock time [H:MM:SS (secs)]: 0:01:07 (67)
SBY 16:15:13 [unique_bmc_tb] summary: Elapsed process time [H:MM:SS (secs)]: 0:01:09 (69)
SBY 16:15:13 [unique_bmc_tb] summary: engine_0 (smtbmc) returned pass
SBY 16:15:13 [unique_bmc_tb] DONE (PASS, rc=0)
``` ```

@ -1,43 +1,18 @@
import argparse import argparse
import importlib
import os import os
import multiprocessing


from amaranth import * from amaranth import *
from amaranth.asserts import *


from power_fv import pfv
from power_fv.checks import *
from power_fv.tb import Testbench from power_fv.tb import Testbench
from power_fv.build import SymbiYosysPlatform from power_fv.build import SymbiYosysPlatform


from _wrapper import MicrowattWrapper from _wrapper import MicrowattWrapper




if __name__ == "__main__": def microwatt_files():
parser = argparse.ArgumentParser() _filenames = (
parser.add_argument("check", help="check", type=str, choices=("unique", "ia_fwd", "gpr", "cr", "spr"))
parser.add_argument("--mode", help="mode", type=str, choices=("cover", "bmc"), default="bmc")
parser.add_argument("--pre", help="pre-condition step, in clock cycles (default: 15)", type=int, default=15)
parser.add_argument("--post", help="post-condition step, in clock cycles (default: 15)", type=int, default=15)

args = parser.parse_args()

check = None
if args.check == "unique":
check = UniquenessCheck() if args.mode == "bmc" else UniquenessCover()
if args.check == "ia_fwd":
check = IAForwardCheck() if args.mode == "bmc" else IAForwardCover()
if args.check == "gpr":
check = GPRCheck()
if args.check == "cr":
check = CRCheck()
if args.check == "spr":
check = SPRCheck() if args.mode == "bmc" else SPRCover()

cpu = MicrowattWrapper()
testbench = Testbench(check, cpu, t_pre=args.pre, t_post=args.post)
platform = SymbiYosysPlatform()

microwatt_files = [
"cache_ram.vhdl", "cache_ram.vhdl",
"common.vhdl", "common.vhdl",
"control.vhdl", "control.vhdl",
@ -64,28 +39,66 @@ if __name__ == "__main__":
"nonrandom.vhdl", "nonrandom.vhdl",
"plru.vhdl", "plru.vhdl",
"pmu.vhdl", "pmu.vhdl",
"powerfv_types.vhdl",
"powerfv.vhdl",
"ppc_fx_insns.vhdl", "ppc_fx_insns.vhdl",
"register_file.vhdl", "register_file.vhdl",
"rotator.vhdl", "rotator.vhdl",
"utils.vhdl", "utils.vhdl",
"wishbone_types.vhdl", "wishbone_types.vhdl",
"writeback.vhdl", "writeback.vhdl",
)


"powerfv_types.vhdl", for filename in _filenames:
"powerfv.vhdl", contents = open(os.path.join(os.curdir, "microwatt", filename), "r").read()
] yield filename, contents

top_filename = "microwatt_top.vhdl"
top_contents = open(os.path.join(os.curdir, top_filename), "r").read()
yield top_filename, top_contents


for filename in microwatt_files:
file = open(os.path.join(os.curdir, "microwatt", filename), "r")
platform.add_file(filename, file.read())


platform.add_file("microwatt_top.vhdl", open(os.path.join(os.curdir, "microwatt_top.vhdl"), "r")) def run_check(module_name, pre, post):
module = importlib.import_module(f".{module_name}", package="power_fv.checks")
check = module.Check()
cpu = MicrowattWrapper()
top = Testbench(check, cpu, t_pre=pre, t_post=post)


platform.build(testbench, platform = SymbiYosysPlatform()
name = f"{args.check}_{args.mode}_tb", for filename, contents in microwatt_files():
build_dir = f"build/{args.check}_{args.mode}", platform.add_file(filename, contents)
mode = args.mode,
top_name = "{}_tb".format(module_name)
build_dir = "build/{}".format(top_name)

platform.build(
top = top,
name = top_name,
build_dir = build_dir,
mode = "bmc",
ghdl_opts = "--std=08", ghdl_opts = "--std=08",
# TODO: investigate why combinatorial loops appear with `prep -flatten -nordff`
prep_opts = "-nordff", prep_opts = "-nordff",
) )


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-j", "--jobs",
help="Number of worker processes (default: os.cpu_count())",
type=int,
default=os.cpu_count(),
)

args = parser.parse_args()

checks = [
# name pre post
("unique", 12, 15),
("ia_fwd", None, 15),
("gpr", None, 15),
("cr", None, 15),
("spr", None, 15),
]

with multiprocessing.Pool(processes=args.jobs) as pool:
pool.starmap(run_check, checks)

@ -4,18 +4,20 @@ from amaranth.asserts import *
from .. import pfv from .. import pfv




__all__ = ["CRCheck"] __all__ = ["Check"]




class CRCheck(Elaboratable): class Check(Elaboratable):
"""Condition Register check. """Condition Register check.


Checks that reads from CR fields are consistent with the last value that was written to them. Checks that reads from CR fields are consistent with the last value that was written to them.
""" """
def __init__(self): def __init__(self):
self.pre = Signal()
self.post = Signal()
self.pfv = pfv.Interface() self.pfv = pfv.Interface()
self.trig = Record([
("pre", 1),
("post", 1),
])


def elaborate(self, platform): def elaborate(self, platform):
m = Module() m = Module()
@ -54,7 +56,7 @@ class CRCheck(Elaboratable):
cr_field.shadow .eq(cr_field.pfv.w_data), cr_field.shadow .eq(cr_field.pfv.w_data),
] ]


with m.If(self.post): with m.If(self.trig.post):
m.d.sync += [ m.d.sync += [
Assume(Past(self.pfv.stb)), Assume(Past(self.pfv.stb)),
Assume(Past(self.pfv.order) == spec_order), Assume(Past(self.pfv.order) == spec_order),

@ -4,10 +4,10 @@ from amaranth.asserts import *
from .. import pfv from .. import pfv




__all__ = ["GPRCheck"] __all__ = ["Check"]




class GPRCheck(Elaboratable): class Check(Elaboratable):
"""General Purpose Registers check. """General Purpose Registers check.


Checks that reads from GPRs are consistent with the last value that was written to them. Checks that reads from GPRs are consistent with the last value that was written to them.
@ -15,9 +15,11 @@ class GPRCheck(Elaboratable):
with update) do not target the same register. with update) do not target the same register.
""" """
def __init__(self): def __init__(self):
self.pre = Signal()
self.post = Signal()
self.pfv = pfv.Interface() self.pfv = pfv.Interface()
self.trig = Record([
("pre", 1),
("post", 1),
])


def elaborate(self, platform): def elaborate(self, platform):
m = Module() m = Module()
@ -55,19 +57,19 @@ class GPRCheck(Elaboratable):
with m.Else(): with m.Else():
m.d.sync += gpr_shadow.eq(self.pfv.rt.w_data) m.d.sync += gpr_shadow.eq(self.pfv.rt.w_data)


with m.If(self.post): with m.If(self.trig.post):
m.d.sync += [ m.d.sync += [
Assume(Past(self.pfv.stb)), Assume(Past(self.pfv.stb)),
Assume(Past(self.pfv.order) == spec_order), Assume(Past(self.pfv.order) == spec_order),
Assert(gpr_written.implies(~gpr_conflict)), Assert(gpr_written.implies(~gpr_conflict)),
] ]
with m.If(Past(gpr_ra_read)): with m.If(gpr_written & Past(gpr_ra_read)):
m.d.sync += Assert(Past(gpr_shadow) == Past(self.pfv.ra.r_data)) m.d.sync += Assert(Past(gpr_shadow) == Past(self.pfv.ra.r_data))
with m.If(Past(gpr_rb_read)): with m.If(gpr_written & Past(gpr_rb_read)):
m.d.sync += Assert(Past(gpr_shadow) == Past(self.pfv.rb.r_data)) m.d.sync += Assert(Past(gpr_shadow) == Past(self.pfv.rb.r_data))
with m.If(Past(gpr_rs_read)): with m.If(gpr_written & Past(gpr_rs_read)):
m.d.sync += Assert(Past(gpr_shadow) == Past(self.pfv.rs.r_data)) m.d.sync += Assert(Past(gpr_shadow) == Past(self.pfv.rs.r_data))
with m.If(Past(gpr_rt_read)): with m.If(gpr_written & Past(gpr_rt_read)):
m.d.sync += Assert(Past(gpr_shadow) == Past(self.pfv.rt.r_data)) m.d.sync += Assert(Past(gpr_shadow) == Past(self.pfv.rt.r_data))


return m return m

@ -4,19 +4,21 @@ from amaranth.asserts import *
from .. import pfv from .. import pfv




__all__ = ["IAForwardCheck", "IAForwardCover"] __all__ = ["Check"]




class IAForwardCheck(Elaboratable): class Check(Elaboratable):
"""IA forward check. """IA forward check.


Given two instructions retiring in order, check that the NIA of the first matches the CIA Given two instructions retiring in order, check that the NIA of the first matches the CIA
of the second. of the second.
""" """
def __init__(self): def __init__(self):
self.pre = Signal()
self.post = Signal()
self.pfv = pfv.Interface() self.pfv = pfv.Interface()
self.trig = Record([
("pre", 1),
("post", 1),
])


def elaborate(self, platform): def elaborate(self, platform):
m = Module() m = Module()
@ -31,7 +33,7 @@ class IAForwardCheck(Elaboratable):
pred_nia.eq(self.pfv.nia) pred_nia.eq(self.pfv.nia)
] ]


with m.If(self.post): with m.If(self.trig.post):
m.d.sync += [ m.d.sync += [
Assume(self.pfv.stb), Assume(self.pfv.stb),
Assume(self.pfv.order == pred_order + 1), Assume(self.pfv.order == pred_order + 1),
@ -40,35 +42,3 @@ class IAForwardCheck(Elaboratable):
] ]


return m return m


class IAForwardCover(Elaboratable):
def __init__(self):
self.pre = Signal()
self.post = Signal()
self.pfv = pfv.Interface()

def elaborate(self, platform):
m = Module()

insn_count = Signal(range(4))
pred_branch = Signal()

with m.If(self.pfv.stb):
m.d.sync += [
insn_count .eq(insn_count + 1),
pred_branch.eq(self.pfv.nia != (self.pfv.cia + 4)),
]

cover_1 = Signal()
cover_2 = Signal()

m.d.comb += [
cover_1.eq((insn_count > 1) & pred_branch),
cover_2.eq(cover_1 & self.pfv.stb),

Cover(cover_1),
Cover(cover_2),
]

return m

@ -4,19 +4,21 @@ from amaranth.asserts import *
from .. import pfv from .. import pfv




__all__ = ["SPRCheck", "SPRCover"] __all__ = ["Check"]




class SPRCheck(Elaboratable): class Check(Elaboratable):
"""Special Purpose Registers check. """Special Purpose Registers check.


Checks that reads from supported SPRs are consistent with the last value that was written to Checks that reads from supported SPRs are consistent with the last value that was written to
them. them.
""" """
def __init__(self): def __init__(self):
self.pre = Signal()
self.post = Signal()
self.pfv = pfv.Interface() self.pfv = pfv.Interface()
self.trig = Record([
("pre", 1),
("post", 1),
])


def elaborate(self, platform): def elaborate(self, platform):
m = Module() m = Module()
@ -54,62 +56,18 @@ class SPRCheck(Elaboratable):
tar_shadow .eq(self.pfv.tar.w_data), tar_shadow .eq(self.pfv.tar.w_data),
] ]


with m.If(self.post): with m.If(self.trig.post):
m.d.sync += [ m.d.sync += [
Assume(Past(self.pfv.stb)), Assume(Past(self.pfv.stb)),
Assume(Past(self.pfv.order) == spec_order), Assume(Past(self.pfv.order) == spec_order),
] ]
with m.If(Past(self.pfv.lr.r_stb)): with m.If(lr_written & Past(self.pfv.lr.r_stb)):
m.d.sync += Assert(Past(lr_shadow) == Past(self.pfv.lr.r_data)) m.d.sync += Assert(Past(lr_shadow) == Past(self.pfv.lr.r_data))
with m.If(Past(self.pfv.ctr.r_stb)): with m.If(ctr_written & Past(self.pfv.ctr.r_stb)):
m.d.sync += Assert(Past(ctr_shadow) == Past(self.pfv.ctr.r_data)) m.d.sync += Assert(Past(ctr_shadow) == Past(self.pfv.ctr.r_data))
with m.If(Past(self.pfv.xer.r_stb)): with m.If(xer_written & Past(self.pfv.xer.r_stb)):
m.d.sync += Assert(Past(xer_shadow) == Past(self.pfv.xer.r_data)) m.d.sync += Assert(Past(xer_shadow) == Past(self.pfv.xer.r_data))
with m.If(Past(self.pfv.tar.r_stb)): with m.If(tar_written & Past(self.pfv.tar.r_stb)):
m.d.sync += Assert(Past(tar_shadow) == Past(self.pfv.tar.r_data)) m.d.sync += Assert(Past(tar_shadow) == Past(self.pfv.tar.r_data))


return m return m


class SPRCover(Elaboratable):
def __init__(self):
self.pre = Signal()
self.post = Signal()
self.pfv = pfv.Interface()

def elaborate(self, platform):
m = Module()

insn_count = Signal(range(4))
lr_written = Signal()
ctr_written = Signal()
xer_written = Signal()
tar_written = Signal()

with m.If(self.pfv.stb):
m.d.sync += [
insn_count .eq(insn_count + 1),
lr_written .eq(self.pfv.lr .w_stb),
ctr_written.eq(self.pfv.ctr.w_stb),
xer_written.eq(self.pfv.xer.w_stb),
tar_written.eq(self.pfv.tar.w_stb),
]

cover_1 = Signal()
cover_2 = Signal()
cover_3 = Signal()
cover_4 = Signal()

m.d.comb += [
cover_1.eq((insn_count > 1) & lr_written),
cover_2.eq((insn_count > 1) & ctr_written),
cover_3.eq((insn_count > 1) & xer_written),
cover_4.eq((insn_count > 1) & tar_written),

Cover(cover_1),
Cover(cover_2),
Cover(cover_3),
Cover(cover_4),
]

return m

@ -4,18 +4,20 @@ from amaranth.asserts import *
from .. import pfv from .. import pfv




__all__ = ["UniquenessCheck", "UniquenessCover"] __all__ = ["Check"]




class UniquenessCheck(Elaboratable): class Check(Elaboratable):
"""Uniqueness check. """Uniqueness check.


Check that the core cannot retire two instructions with the same `pfv.order` identifier. Check that the core cannot retire two instructions with the same `pfv.order` identifier.
""" """
def __init__(self): def __init__(self):
self.pre = Signal()
self.post = Signal()
self.pfv = pfv.Interface() self.pfv = pfv.Interface()
self.trig = Record([
("pre", 1),
("post", 1),
])


def elaborate(self, platform): def elaborate(self, platform):
m = Module() m = Module()
@ -23,7 +25,7 @@ class UniquenessCheck(Elaboratable):
spec_order = AnyConst(self.pfv.order.shape()) spec_order = AnyConst(self.pfv.order.shape())
duplicate = Signal() duplicate = Signal()


with m.If(self.pre): with m.If(self.trig.pre):
m.d.sync += [ m.d.sync += [
Assume(self.pfv.stb), Assume(self.pfv.stb),
Assume(spec_order == self.pfv.order), Assume(spec_order == self.pfv.order),
@ -32,35 +34,7 @@ class UniquenessCheck(Elaboratable):
with m.Elif(self.pfv.stb & (self.pfv.order == spec_order)): with m.Elif(self.pfv.stb & (self.pfv.order == spec_order)):
m.d.sync += duplicate.eq(1) m.d.sync += duplicate.eq(1)


with m.If(self.post): with m.If(self.trig.post):
m.d.sync += Assert(~duplicate) m.d.sync += Assert(~duplicate)


return m return m


class UniquenessCover(Elaboratable):
def __init__(self):
self.pre = Signal()
self.post = Signal()
self.pfv = pfv.Interface()

def elaborate(self, platform):
m = Module()

insn_count = Signal(range(4))

with m.If(self.pfv.stb):
m.d.sync += insn_count.eq(insn_count + 1)

cover_1 = Signal()
cover_2 = Signal()

m.d.comb += [
cover_1.eq(insn_count == 1),
cover_2.eq(insn_count == 2),

Cover(cover_1),
Cover(cover_2),
]

return m

@ -42,9 +42,9 @@ class Testbench(Elaboratable):
m.submodules.dut = self.dut m.submodules.dut = self.dut


m.d.comb += [ m.d.comb += [
self.check.pre .eq(timer == self.t_pre), self.check.pfv.eq(self.dut.pfv),
self.check.post.eq(timer == self.t_post), self.check.trig.pre .eq(timer == self.t_pre),
self.check.pfv .eq(self.dut.pfv), self.check.trig.post.eq(timer == self.t_post),
] ]


m.d.comb += Assume(ResetSignal() == Initial()) m.d.comb += Assume(ResetSignal() == Initial())

Loading…
Cancel
Save