cortex-runtime 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cortex/__init__.py +51 -0
- cortex/__main__.py +13 -0
- cortex/client.py +263 -0
- cortex/compat.py +20 -0
- cortex/exceptions.py +57 -0
- cortex/plugin.py +50 -0
- cortex/py.typed +1 -0
- cortex/schema/__init__.py +29 -0
- cortex/schema/events.py +127 -0
- cortex/tools/__init__.py +3 -0
- cortex/tools/cli/__init__.py +3 -0
- cortex/tools/cli/main.py +139 -0
- cortex/tools/cli/runner.py +94 -0
- cortex/tools/cli/scaffolder.py +109 -0
- cortex/tools/gen_test_bin.py +35 -0
- cortex/tools/kernel/__init__.py +3 -0
- cortex/tools/kernel/actors/__init__.py +3 -0
- cortex/tools/kernel/actors/executor.py +44 -0
- cortex/tools/kernel/actors/planner.py +38 -0
- cortex/tools/kernel/context.py +22 -0
- cortex/tools/kernel/drivers/__init__.py +3 -0
- cortex/tools/kernel/drivers/mock_robot.py +84 -0
- cortex/tools/kernel/drivers/rtl_verilator.py +49 -0
- cortex/tools/kernel/graph/__init__.py +3 -0
- cortex/tools/kernel/graph/analyzer.py +52 -0
- cortex/tools/kernel/graph/execution_graph.py +44 -0
- cortex/tools/kernel/mailbox.py +35 -0
- cortex/tools/kernel/plugin/__init__.py +3 -0
- cortex/tools/kernel/plugin/loader.py +87 -0
- cortex/tools/kernel/plugin/manifest.py +53 -0
- cortex/tools/kernel/registry.py +36 -0
- cortex/tools/kernel/schema/__init__.py +3 -0
- cortex/tools/kernel/schema/contract.py +16 -0
- cortex/tools/kernel/schema/event.py +54 -0
- cortex/tools/kernel/schema/message.py +105 -0
- cortex/tools/kernel/schema/workflow.py +38 -0
- cortex/tools/kernel/services/__init__.py +3 -0
- cortex/tools/kernel/services/event_store.py +25 -0
- cortex/tools/kernel/services/execution_intelligence.py +49 -0
- cortex/tools/kernel/services/graph_builder.py +81 -0
- cortex/tools/kernel/services/replay.py +50 -0
- cortex/tools/kernel/services/verification.py +85 -0
- cortex/tools/kernel/transport.py +50 -0
- cortex/tools/run_phase2_verification.sh +20 -0
- cortex/tools/verification/__init__.py +5 -0
- cortex/tools/verification/adapters/__init__.py +3 -0
- cortex/tools/verification/adapters/base.py +14 -0
- cortex/tools/verification/adapters/coq.py +73 -0
- cortex/tools/verification/adapters/rtl.py +77 -0
- cortex/tools/verification/adapters/rust.py +72 -0
- cortex/tools/verification/archive.py +58 -0
- cortex/tools/verification/bus.py +28 -0
- cortex/tools/verification/contract.py +70 -0
- cortex/tools/verification/engine.py +121 -0
- cortex/tools/verification/generator/__init__.py +3 -0
- cortex/tools/verification/generator/composer.py +36 -0
- cortex/tools/verification/generator/program.py +46 -0
- cortex/tools/verification/generator/state.py +54 -0
- cortex/tools/verification/invariants/__init__.py +3 -0
- cortex/tools/verification/invariants/capability.py +62 -0
- cortex/tools/verification/metrics/__init__.py +3 -0
- cortex/tools/verification/metrics/base.py +18 -0
- cortex/tools/verification/metrics/opcode.py +25 -0
- cortex/tools/verification/metrics/state_space.py +26 -0
- cortex/tools/verification/metrics/trap.py +26 -0
- cortex/tools/verification/mutation.py +48 -0
- cortex/tools/verification/oracle.py +164 -0
- cortex/tools/verification/schema/__init__.py +44 -0
- cortex/tools/verification/schema/event.py +44 -0
- cortex/tools/verification/shrink.py +26 -0
- cortex/tools/verify.py +64 -0
- cortex_runtime-0.2.0.dist-info/METADATA +220 -0
- cortex_runtime-0.2.0.dist-info/RECORD +76 -0
- cortex_runtime-0.2.0.dist-info/WHEEL +4 -0
- cortex_runtime-0.2.0.dist-info/entry_points.txt +2 -0
- cortex_runtime-0.2.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
YAML Contract Parser & Schema Validator for Verification Runs
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class VerificationContract:
|
|
12
|
+
schema_version: str
|
|
13
|
+
contract_id: str
|
|
14
|
+
description: str
|
|
15
|
+
toolchain_requirements: dict[str, str]
|
|
16
|
+
fuzzing_parameters: dict[str, Any]
|
|
17
|
+
targets: dict[str, str]
|
|
18
|
+
oracle: dict[str, Any]
|
|
19
|
+
output_requirements: dict[str, str]
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def load(cls, contract_path: str) -> "VerificationContract":
|
|
23
|
+
if not os.path.exists(contract_path):
|
|
24
|
+
raise FileNotFoundError(f"Contract file not found: {contract_path}")
|
|
25
|
+
|
|
26
|
+
# Basic lightweight YAML parser without third-party requirement
|
|
27
|
+
data: dict[str, Any] = {}
|
|
28
|
+
current_key = None
|
|
29
|
+
|
|
30
|
+
with open(contract_path, "r") as f:
|
|
31
|
+
for line in f:
|
|
32
|
+
line = line.strip()
|
|
33
|
+
if not line or line.startswith("#"):
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
if ":" in line and not line.startswith("-"):
|
|
37
|
+
parts = line.split(":", 1)
|
|
38
|
+
key = parts[0].strip()
|
|
39
|
+
val = parts[1].strip()
|
|
40
|
+
|
|
41
|
+
if val == "":
|
|
42
|
+
current_key = key
|
|
43
|
+
data[current_key] = {}
|
|
44
|
+
else:
|
|
45
|
+
# strip quotes if present
|
|
46
|
+
if (val.startswith('"') and val.endswith('"')) or (val.startswith("'") and val.endswith("'")):
|
|
47
|
+
val = val[1:-1]
|
|
48
|
+
elif val.lower() == "true":
|
|
49
|
+
val = True
|
|
50
|
+
elif val.lower() == "false":
|
|
51
|
+
val = False
|
|
52
|
+
elif val.isdigit():
|
|
53
|
+
val = int(val)
|
|
54
|
+
|
|
55
|
+
if current_key and line.startswith(" "):
|
|
56
|
+
data[current_key][key] = val
|
|
57
|
+
else:
|
|
58
|
+
data[key] = val
|
|
59
|
+
current_key = None
|
|
60
|
+
|
|
61
|
+
return cls(
|
|
62
|
+
schema_version=data.get("schema_version", "1.0.0"),
|
|
63
|
+
contract_id=data.get("contract_id", "default"),
|
|
64
|
+
description=data.get("description", ""),
|
|
65
|
+
toolchain_requirements=data.get("toolchain_requirements", {}),
|
|
66
|
+
fuzzing_parameters=data.get("fuzzing_parameters", {}),
|
|
67
|
+
targets=data.get("targets", {}),
|
|
68
|
+
oracle=data.get("oracle", {}),
|
|
69
|
+
output_requirements=data.get("output_requirements", {})
|
|
70
|
+
)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core Verification Engine Orchestrator
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from cortex.tools.verification.adapters.coq import CoqAdapter
|
|
10
|
+
from cortex.tools.verification.adapters.rtl import RTLAdapter
|
|
11
|
+
from cortex.tools.verification.adapters.rust import RustAdapter
|
|
12
|
+
from cortex.tools.verification.archive import CounterexampleArchive
|
|
13
|
+
from cortex.tools.verification.contract import VerificationContract
|
|
14
|
+
from cortex.tools.verification.generator.composer import ScenarioComposer
|
|
15
|
+
from cortex.tools.verification.metrics.opcode import OpcodeMetric
|
|
16
|
+
from cortex.tools.verification.metrics.state_space import StateSpaceMetric
|
|
17
|
+
from cortex.tools.verification.metrics.trap import TrapMetric
|
|
18
|
+
from cortex.tools.verification.mutation import FaultMutationEngine
|
|
19
|
+
from cortex.tools.verification.oracle import VerificationOracle
|
|
20
|
+
from cortex.tools.verification.shrink import SemanticShrinker
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class VerificationEngine:
|
|
24
|
+
def __init__(self, contract: VerificationContract, seed_val: int):
|
|
25
|
+
self.contract = contract
|
|
26
|
+
self.seed_val = seed_val
|
|
27
|
+
self.oracle = VerificationOracle(
|
|
28
|
+
version=contract.oracle.get("version", "v2.1.0"),
|
|
29
|
+
strict_trap_matching=contract.oracle.get("strict_trap_cause_matching", True)
|
|
30
|
+
)
|
|
31
|
+
self.shrinker = SemanticShrinker(
|
|
32
|
+
max_shrunk_steps=contract.fuzzing_parameters.get("max_shrunk_steps", 50)
|
|
33
|
+
)
|
|
34
|
+
self.archiver = CounterexampleArchive(
|
|
35
|
+
archive_dir=contract.output_requirements.get("counterexample_directory", "artifacts/counterexamples/")
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
self.opcode_metric = OpcodeMetric()
|
|
39
|
+
self.trap_metric = TrapMetric()
|
|
40
|
+
self.state_metric = StateSpaceMetric()
|
|
41
|
+
|
|
42
|
+
def run_verification(
|
|
43
|
+
self,
|
|
44
|
+
iterations: int = 100,
|
|
45
|
+
inject_failure: str | None = None
|
|
46
|
+
) -> dict[str, Any]:
|
|
47
|
+
coq_adapter = CoqAdapter()
|
|
48
|
+
rust_adapter = RustAdapter()
|
|
49
|
+
rtl_adapter = RTLAdapter()
|
|
50
|
+
|
|
51
|
+
total_steps_evaluated = 0
|
|
52
|
+
|
|
53
|
+
for i in range(iterations):
|
|
54
|
+
iter_seed = self.seed_val + i
|
|
55
|
+
composer = ScenarioComposer(iter_seed)
|
|
56
|
+
scenario = composer.compose_scenario(num_instructions=6)
|
|
57
|
+
|
|
58
|
+
# Generate temp artifacts
|
|
59
|
+
os.makedirs("artifacts/temp/", exist_ok=True)
|
|
60
|
+
composer.export_artifacts(
|
|
61
|
+
scenario,
|
|
62
|
+
"artifacts/temp/test_scenario.json",
|
|
63
|
+
"artifacts/temp/test_payload.bin"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Parse traces
|
|
67
|
+
coq_trace = coq_adapter.parse_trace("Research/artifacts/phase2/coq_trace.json")
|
|
68
|
+
rust_trace = rust_adapter.parse_trace("Research/artifacts/phase2/emulator_trace.json")
|
|
69
|
+
rtl_trace = rtl_adapter.parse_trace("Research/artifacts/phase2/rtl_trace.json")
|
|
70
|
+
|
|
71
|
+
# Apply mutation if injected
|
|
72
|
+
if inject_failure:
|
|
73
|
+
mutation_engine = FaultMutationEngine(inject_failure)
|
|
74
|
+
rtl_trace = mutation_engine.apply_mutation(rtl_trace)
|
|
75
|
+
|
|
76
|
+
# Record metrics
|
|
77
|
+
for step in coq_trace:
|
|
78
|
+
self.opcode_metric.record_step(step)
|
|
79
|
+
self.trap_metric.record_step(step)
|
|
80
|
+
self.state_metric.record_step(step)
|
|
81
|
+
total_steps_evaluated += 1
|
|
82
|
+
|
|
83
|
+
# Evaluate equivalence
|
|
84
|
+
diagnostic = self.oracle.evaluate_equivalence(coq_trace, rust_trace, rtl_trace)
|
|
85
|
+
|
|
86
|
+
if diagnostic["status"] == "FAIL":
|
|
87
|
+
failing_step = diagnostic.get("failing_step", 1)
|
|
88
|
+
shrunk_scenario = self.shrinker.shrink_scenario(scenario, failing_step)
|
|
89
|
+
case_hash = self.archiver.archive_failure(
|
|
90
|
+
shrunk_scenario,
|
|
91
|
+
diagnostic,
|
|
92
|
+
seed=f"0x{iter_seed:08X}"
|
|
93
|
+
)
|
|
94
|
+
return {
|
|
95
|
+
"status": "FAIL",
|
|
96
|
+
"iteration": i + 1,
|
|
97
|
+
"seed": f"0x{iter_seed:08X}",
|
|
98
|
+
"diagnostic": diagnostic,
|
|
99
|
+
"counterexample_hash": case_hash
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# Generate run summary JSON
|
|
103
|
+
summary = {
|
|
104
|
+
"seed": f"0x{self.seed_val:08X}",
|
|
105
|
+
"iterations": iterations,
|
|
106
|
+
"total_steps_evaluated": total_steps_evaluated,
|
|
107
|
+
"status": "PASSED",
|
|
108
|
+
"metrics": {
|
|
109
|
+
"opcode_coverage": self.opcode_metric.get_summary(),
|
|
110
|
+
"trap_coverage": self.trap_metric.get_summary(),
|
|
111
|
+
"state_space_explored": self.state_metric.get_summary()
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
output_dir = self.contract.output_requirements.get("archive_directory", "artifacts/phase3a/")
|
|
116
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
117
|
+
summary_path = os.path.join(output_dir, "run_summary.json")
|
|
118
|
+
with open(summary_path, "w") as f:
|
|
119
|
+
json.dump(summary, f, indent=2)
|
|
120
|
+
|
|
121
|
+
return summary
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scenario Composer: Merges Machine State and Instruction Stream into Reproducible Payloads
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import struct
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from cortex.tools.verification.generator.program import ProgramGenerator
|
|
10
|
+
from cortex.tools.verification.generator.state import StateGenerator
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ScenarioComposer:
|
|
14
|
+
def __init__(self, seed: int):
|
|
15
|
+
self.seed = seed
|
|
16
|
+
self.state_gen = StateGenerator(seed)
|
|
17
|
+
self.prog_gen = ProgramGenerator(seed)
|
|
18
|
+
|
|
19
|
+
def compose_scenario(self, num_instructions: int = 8) -> dict[str, Any]:
|
|
20
|
+
initial_state = self.state_gen.generate_initial_state()
|
|
21
|
+
program = self.prog_gen.generate_program(num_instructions)
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
"seed": f"0x{self.seed:08X}",
|
|
25
|
+
"initial_state": initial_state,
|
|
26
|
+
"program": program
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
def export_artifacts(self, scenario: dict[str, Any], json_path: str, bin_path: str):
|
|
30
|
+
with open(json_path, "w") as f:
|
|
31
|
+
json.dump(scenario, f, indent=2)
|
|
32
|
+
|
|
33
|
+
with open(bin_path, "wb") as f:
|
|
34
|
+
for inst in scenario["program"]:
|
|
35
|
+
# Big-endian 32-bit instruction word
|
|
36
|
+
f.write(struct.pack(">I", inst["raw_uint32"]))
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Instruction Stream Fuzzing Generator for Cortex Capabilities
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import random
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ProgramGenerator:
|
|
10
|
+
def __init__(self, seed: int):
|
|
11
|
+
self.rng = random.Random(seed)
|
|
12
|
+
|
|
13
|
+
def generate_program(self, num_instructions: int = 8) -> list[dict[str, Any]]:
|
|
14
|
+
instructions = []
|
|
15
|
+
|
|
16
|
+
opcodes = [
|
|
17
|
+
("INVOKE_CAP", 0x01, 0x04000000),
|
|
18
|
+
("RESTRICT_CAP", 0x03, 0x0C000000),
|
|
19
|
+
("REVOKE_CAP", 0x05, 0x14000000),
|
|
20
|
+
("GRANT_CAP", 0x02, 0x08000000),
|
|
21
|
+
("ILLEGAL_OP", 0x0F, 0x00000000),
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
for _ in range(num_instructions):
|
|
25
|
+
op_name, op_code, raw_base = self.rng.choice(opcodes)
|
|
26
|
+
src_reg = self.rng.randint(0, 3)
|
|
27
|
+
dst_reg = self.rng.randint(0, 3)
|
|
28
|
+
imm = self.rng.choice([0x0000, 0x1000, 0x2000, 0x4000, 0x7000, 0xFFFF])
|
|
29
|
+
|
|
30
|
+
if op_name == "RESTRICT_CAP":
|
|
31
|
+
raw = raw_base | (src_reg << 16) | imm
|
|
32
|
+
elif op_name == "INVOKE_CAP" or op_name == "REVOKE_CAP":
|
|
33
|
+
raw = raw_base | (src_reg << 16)
|
|
34
|
+
elif op_name == "GRANT_CAP":
|
|
35
|
+
raw = raw_base | (src_reg << 16) | (dst_reg << 20) | imm
|
|
36
|
+
else:
|
|
37
|
+
raw = 0x00000000
|
|
38
|
+
|
|
39
|
+
instructions.append({
|
|
40
|
+
"opcode_name": op_name,
|
|
41
|
+
"opcode_val": op_code,
|
|
42
|
+
"raw_hex": f"0x{raw:08x}",
|
|
43
|
+
"raw_uint32": raw
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
return instructions
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Initial STCR & Machine Register State Fuzzing Generator
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import random
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class StateGenerator:
|
|
10
|
+
def __init__(self, seed: int):
|
|
11
|
+
self.rng = random.Random(seed)
|
|
12
|
+
|
|
13
|
+
def generate_initial_state(self) -> dict[str, Any]:
|
|
14
|
+
stcr_file = []
|
|
15
|
+
|
|
16
|
+
# STCR0 always starts as root execution capability by default or random
|
|
17
|
+
root_v = True
|
|
18
|
+
root_mask = 0x7000 # READ | WRITE | EXEC (0x4000 | 0x2000 | 0x1000)
|
|
19
|
+
root_base = 0x2000
|
|
20
|
+
root_epoch = 0
|
|
21
|
+
|
|
22
|
+
stcr_file.append({
|
|
23
|
+
"index": 0,
|
|
24
|
+
"valid": root_v,
|
|
25
|
+
"permissions": root_mask,
|
|
26
|
+
"base_address": root_base,
|
|
27
|
+
"epoch": root_epoch
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
for reg_id in range(1, 32):
|
|
31
|
+
is_valid = self.rng.choice([True, False, False, False]) # 25% chance of valid STCR slot
|
|
32
|
+
if is_valid:
|
|
33
|
+
mask = self.rng.choice([0x4000, 0x2000, 0x1000, 0x7000, 0x0000])
|
|
34
|
+
base = self.rng.choice([0x0, 0x1000, 0x2000, 0x8000, 0xFFFF0000])
|
|
35
|
+
epoch = self.rng.choice([0, 1, 100, 65534, 65535]) # include boundary epoch limits
|
|
36
|
+
else:
|
|
37
|
+
mask = 0
|
|
38
|
+
base = 0
|
|
39
|
+
epoch = 0
|
|
40
|
+
|
|
41
|
+
stcr_file.append({
|
|
42
|
+
"index": reg_id,
|
|
43
|
+
"valid": is_valid,
|
|
44
|
+
"permissions": mask,
|
|
45
|
+
"base_address": base,
|
|
46
|
+
"epoch": epoch
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
"pc": 0x1000,
|
|
51
|
+
"privilege_mode": "Machine",
|
|
52
|
+
"reg_hec": self.rng.choice([0, 1, 65535]),
|
|
53
|
+
"stcr_registers": stcr_file
|
|
54
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Capability Safety Invariants Engine
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from cortex.tools.verification.schema.event import CommitEventV1
|
|
8
|
+
|
|
9
|
+
# Define invariant boundary constants to avoid magic numbers
|
|
10
|
+
MIN_EPOCH = 0
|
|
11
|
+
MAX_EPOCH = 65535
|
|
12
|
+
NEUTRAL_TRAP_EXPECTED_VAL = 0
|
|
13
|
+
MIN_RETIREMENT_STEP = 1
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class InvariantResult:
|
|
17
|
+
passed: bool
|
|
18
|
+
invariant_id: str
|
|
19
|
+
error_message: str = ""
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def PASS(cls, invariant_id: str) -> "InvariantResult":
|
|
23
|
+
return cls(passed=True, invariant_id=invariant_id)
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def FAIL(cls, invariant_id: str, error_message: str) -> "InvariantResult":
|
|
27
|
+
return cls(passed=False, invariant_id=invariant_id, error_message=error_message)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class EpochMonotonicityInvariant:
|
|
31
|
+
"""INV_03: Capability epochs must remain in valid 16-bit range."""
|
|
32
|
+
def check(self, event: CommitEventV1) -> InvariantResult:
|
|
33
|
+
if event.architectural is None:
|
|
34
|
+
return InvariantResult.FAIL("INV_03_ATOMIC_STCR_UPDATE", "Missing architectural state")
|
|
35
|
+
for stcr in event.architectural.stcr:
|
|
36
|
+
epoch = stcr.get("epoch", stcr.get("max_epoch", 0))
|
|
37
|
+
if not isinstance(epoch, int) or epoch < MIN_EPOCH or epoch > MAX_EPOCH:
|
|
38
|
+
return InvariantResult.FAIL("INV_03_ATOMIC_STCR_UPDATE", f"Epoch {epoch} out of 16-bit bounds")
|
|
39
|
+
return InvariantResult.PASS("INV_03_ATOMIC_STCR_UPDATE")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class NeutralTrapInvariant:
|
|
43
|
+
"""INV_02: Verifies that committed neutral traps maintain trap_val == 0."""
|
|
44
|
+
def check(self, event: CommitEventV1) -> InvariantResult:
|
|
45
|
+
if event.architectural is None:
|
|
46
|
+
return InvariantResult.FAIL("INV_02_NEUTRAL_TRAP_ZERO_VAL", "Missing architectural state")
|
|
47
|
+
trap = event.architectural.trap
|
|
48
|
+
is_triggered = trap.get("triggered", False)
|
|
49
|
+
trap_val = trap.get("trap_val", 0)
|
|
50
|
+
if is_triggered and trap_val != NEUTRAL_TRAP_EXPECTED_VAL:
|
|
51
|
+
return InvariantResult.FAIL("INV_02_NEUTRAL_TRAP_ZERO_VAL", f"Non-zero trap_val {trap_val} on neutral trap")
|
|
52
|
+
return InvariantResult.PASS("INV_02_NEUTRAL_TRAP_ZERO_VAL")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class SingleRetirementInvariant:
|
|
56
|
+
"""INV_01: Exactly one architectural retirement event per execution step."""
|
|
57
|
+
def check(self, event: CommitEventV1) -> InvariantResult:
|
|
58
|
+
if event.observation is None:
|
|
59
|
+
return InvariantResult.FAIL("INV_01_SINGLE_RETIREMENT", "Missing observation metadata")
|
|
60
|
+
if event.observation.step < MIN_RETIREMENT_STEP:
|
|
61
|
+
return InvariantResult.FAIL("INV_01_SINGLE_RETIREMENT", f"Invalid retirement step {event.observation.step}")
|
|
62
|
+
return InvariantResult.PASS("INV_01_SINGLE_RETIREMENT")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base Metric Plugin Interface
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from cortex.tools.verification.schema import CanonicalState
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseMetric(ABC):
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def record_step(self, step: CanonicalState):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def get_summary(self) -> dict[str, Any]:
|
|
18
|
+
pass
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Instruction Opcode Coverage Metric Tracker
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from cortex.tools.verification.metrics.base import BaseMetric
|
|
8
|
+
from cortex.tools.verification.schema import CanonicalState
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class OpcodeMetric(BaseMetric):
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.seen_opcodes: set[str] = set()
|
|
14
|
+
self.total_opcodes = 18
|
|
15
|
+
|
|
16
|
+
def record_step(self, step: CanonicalState):
|
|
17
|
+
self.seen_opcodes.add(step.instruction)
|
|
18
|
+
|
|
19
|
+
def get_summary(self) -> dict[str, Any]:
|
|
20
|
+
count = len(self.seen_opcodes)
|
|
21
|
+
pct = min(100.0, (count / float(self.total_opcodes)) * 100.0)
|
|
22
|
+
return {
|
|
23
|
+
"unique_opcodes_seen": count,
|
|
24
|
+
"coverage_percentage": round(pct, 1)
|
|
25
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unique Architectural State Explorer Metric Tracker
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from cortex.tools.verification.metrics.base import BaseMetric
|
|
10
|
+
from cortex.tools.verification.schema import CanonicalState
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class StateSpaceMetric(BaseMetric):
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self.visited_states: set[str] = set()
|
|
16
|
+
|
|
17
|
+
def record_step(self, step: CanonicalState):
|
|
18
|
+
state_dict = step.to_dict()
|
|
19
|
+
state_str = json.dumps(state_dict, sort_keys=True).encode("utf-8")
|
|
20
|
+
state_hash = hashlib.sha256(state_str).hexdigest()[:16]
|
|
21
|
+
self.visited_states.add(state_hash)
|
|
22
|
+
|
|
23
|
+
def get_summary(self) -> dict[str, Any]:
|
|
24
|
+
return {
|
|
25
|
+
"unique_states_explored": len(self.visited_states)
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Trap Cause Path Coverage Metric Tracker
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from cortex.tools.verification.metrics.base import BaseMetric
|
|
8
|
+
from cortex.tools.verification.schema import CanonicalState
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TrapMetric(BaseMetric):
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.seen_trap_codes: set[int] = set()
|
|
14
|
+
self.total_trap_codes = 5
|
|
15
|
+
|
|
16
|
+
def record_step(self, step: CanonicalState):
|
|
17
|
+
if step.trap.triggered:
|
|
18
|
+
self.seen_trap_codes.add(step.trap.cause_code)
|
|
19
|
+
|
|
20
|
+
def get_summary(self) -> dict[str, Any]:
|
|
21
|
+
count = len(self.seen_trap_codes)
|
|
22
|
+
pct = min(100.0, (count / float(self.total_trap_codes)) * 100.0)
|
|
23
|
+
return {
|
|
24
|
+
"trap_cause_codes_seen": sorted(list(self.seen_trap_codes)),
|
|
25
|
+
"coverage_percentage": round(pct, 1)
|
|
26
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from dataclasses import replace
|
|
2
|
+
|
|
3
|
+
from cortex.tools.verification.schema import CanonicalState
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FaultMutationEngine:
|
|
7
|
+
failure_vector: str
|
|
8
|
+
|
|
9
|
+
def __init__(self, failure_vector: str):
|
|
10
|
+
self.failure_vector = failure_vector
|
|
11
|
+
|
|
12
|
+
def apply_mutation(self, rtl_trace: list[CanonicalState]) -> list[CanonicalState]:
|
|
13
|
+
"""
|
|
14
|
+
Injects synthetic faults into engine traces to verify pipeline sensitivity.
|
|
15
|
+
"""
|
|
16
|
+
if not rtl_trace:
|
|
17
|
+
return rtl_trace
|
|
18
|
+
|
|
19
|
+
mutated: list[CanonicalState] = []
|
|
20
|
+
for step in rtl_trace:
|
|
21
|
+
stcr_clone = [replace(s) for s in step.stcr]
|
|
22
|
+
trap_clone = replace(step.trap)
|
|
23
|
+
c_step = CanonicalState(
|
|
24
|
+
step=step.step,
|
|
25
|
+
pc=step.pc,
|
|
26
|
+
instruction=step.instruction,
|
|
27
|
+
privilege_mode=step.privilege_mode,
|
|
28
|
+
reg_hec=step.reg_hec,
|
|
29
|
+
registers=dict(step.registers),
|
|
30
|
+
stcr=stcr_clone,
|
|
31
|
+
trap=trap_clone
|
|
32
|
+
)
|
|
33
|
+
mutated.append(c_step)
|
|
34
|
+
|
|
35
|
+
if self.failure_vector == "rtl.trap_suppress":
|
|
36
|
+
# Suppress trap flag on step 3
|
|
37
|
+
for step in mutated:
|
|
38
|
+
if step.step == 3:
|
|
39
|
+
step.trap.triggered = False
|
|
40
|
+
step.trap.cause_code = 0
|
|
41
|
+
step.trap.cause_name = "None"
|
|
42
|
+
elif self.failure_vector == "rtl.stcr0.epoch_mismatch":
|
|
43
|
+
# Off-by-one epoch mismatch on step 2
|
|
44
|
+
for step in mutated:
|
|
45
|
+
if step.step == 2 and step.stcr:
|
|
46
|
+
step.stcr[0].epoch += 1
|
|
47
|
+
|
|
48
|
+
return mutated
|