Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion chipflow_lib/common/sim/models.cc
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ void fetch_actions_into_queue() {
void open_input_commands(const std::string &filename) {
std::ifstream f(filename);
if (!f) {
throw std::runtime_error("failed to open event log for writing!");
std::cerr << "WARNING: failed to open input event stream. Add one for design validation - see [chipflow.test] configuration option." << std::endl;
return;
}
json data = json::parse(f);
input_cmds = data["commands"];
Expand Down
6 changes: 5 additions & 1 deletion chipflow_lib/config_models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
from enum import Enum
from pathlib import Path
from typing import Dict, Optional, Any, List, Annotated

from pydantic import (
Expand Down Expand Up @@ -62,6 +63,9 @@ class CompilerConfig(BaseModel):
class SoftwareConfig(BaseModel):
riscv: CompilerConfig = CompilerConfig(cpu="baseline_rv32-a-c-d", abi="ilp32")

class TestConfig(BaseModel):
event_reference: Path

class ChipFlowConfig(BaseModel):
"""Root configuration for chipflow.toml."""
project_name: str
Expand All @@ -71,7 +75,7 @@ class ChipFlowConfig(BaseModel):
simulation: SimulationConfig = SimulationConfig()
software: SoftwareConfig = SoftwareConfig()
clock_domains: Optional[List[str]] = None

test: Optional[TestConfig] = None

class Config(BaseModel):
"""Root configuration model for chipflow.toml."""
Expand Down
2 changes: 1 addition & 1 deletion chipflow_lib/platforms/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class IOModelOptions(TypedDict):
init_oe: NotRequired[int | bool]


@pydantic.with_config(ConfigDict(arbitrary_types_allowed=True)) # type: ignore[reportCallIssue]
@pydantic.config.with_config(ConfigDict(arbitrary_types_allowed=True)) # type: ignore[reportCallIssue]
class IOModel(IOModelOptions):
"""
Setting for IO Ports (see also base class `IOModelOptions`)
Expand Down
20 changes: 20 additions & 0 deletions chipflow_lib/steps/_json_compare.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import json
from pathlib import Path

from .. import ChipFlowError

def compare_events(gold_path: Path, gate_path: Path):
with open(gold_path, "r") as f:
gold = json.load(f)
with open(gate_path, "r") as f:
gate = json.load(f)
if len(gold["events"]) != len(gate["events"]):
raise ChipFlowError(f"Simulator check failed! Event mismatch: {len(gold['events'])} events in reference, {len(gate['events'])} in test output")
for ev_gold, ev_gate in zip(gold["events"], gate["events"]):
if ev_gold["peripheral"] != ev_gate["peripheral"] or \
ev_gold["event"] != ev_gate["event"] or \
ev_gold["payload"] != ev_gate["payload"]:
raise ChipFlowError(f"Simulator check failed! Reference event {ev_gold} mismatches test event {ev_gate}")

return True

72 changes: 59 additions & 13 deletions chipflow_lib/steps/sim.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import os
import inspect
import importlib.resources
import logging
import os
import subprocess

from contextlib import contextmanager
from pathlib import Path

from doit.cmd_base import TaskLoader2, loader
from doit.doit_cmd import DoitMain
Expand All @@ -12,8 +13,10 @@
from amaranth import Module

from . import StepBase, _wire_up_ports
from ._json_compare import compare_events
from .. import ChipFlowError, _ensure_chipflow_root
from ..platforms._utils import top_components
from ..cli import run
from ..platforms._utils import top_components, load_pinlock
from ..platforms.sim import VARIABLES, TASKS, DOIT_CONFIG, SimPlatform


Expand All @@ -28,14 +31,6 @@ def common():
with importlib.resources.as_file(common) as f:
yield f

@contextmanager
def source():
root = _ensure_chipflow_root()
sourcedir = Path(root) / 'design' / 'sim'
#sim_src = sourcedir.joinpath('design','sim')
#with importlib.resources.as_file(sim_src) as f:
yield sourcedir

@contextmanager
def runtime():
yowasp = importlib.resources.files("yowasp_yosys")
Expand Down Expand Up @@ -75,7 +70,34 @@ def __init__(self, config):
self._platform = SimPlatform(config)
self._config = config

def build_cli_parser(self, parser):
action_argument = parser.add_subparsers(dest="action")
action_argument.add_parser(
"build", help=inspect.getdoc(self.build).splitlines()[0]) # type: ignore
action_argument.add_parser(
"run", help=inspect.getdoc(self.run).splitlines()[0]) # type: ignore
action_argument.add_parser(
"check", help=inspect.getdoc(self.check).splitlines()[0]) # type: ignore

def run_cli(self, args):
load_pinlock() # check pinlock first so we error cleanly

match (args.action):
case "build":
self.build(args)
case "run":
self.run(args)
case "check":
self.check(args)

@property
def sim_dir(self):
return _ensure_chipflow_root() / 'build' / 'sim'

def build(self, *args):
"""
Builds the simulation model for the design
"""
print("Building simulation...")
m = Module()
self._platform.instantiate_ports(m)
Expand All @@ -94,10 +116,9 @@ def build(self, *args):

#FIXME: common source for build dir
self._platform.build(m, top)
with common() as common_dir, source() as source_dir, runtime() as runtime_dir:
with common() as common_dir, runtime() as runtime_dir:
context = {
"COMMON_DIR": common_dir,
"SOURCE_DIR": source_dir,
"RUNTIME_DIR": runtime_dir,
"PROJECT_ROOT": _ensure_chipflow_root(),
"BUILD_DIR": _ensure_chipflow_root() / 'build',
Expand All @@ -107,3 +128,28 @@ def build(self, *args):
context[k] = v.format(**context)
if DoitMain(ContextTaskLoader(DOIT_CONFIG, TASKS, context)).run(["build_sim"]) !=0:
raise ChipFlowError("Failed building simulator")

def run(self, *args):
"""
Run the simulation. Will ensure that the simulation and the software are both built.
"""
run(["software"])
self.build(args)
result = subprocess.run([self.sim_dir / "sim_soc"], cwd=self.sim_dir)

if result.returncode != 0:
raise ChipFlowError("Simulation failed")

def check(self, *args):
"""
Run the simulation and check events against reference (tests/events_reference.json). Will ensure that the simulation and the software are both built.
"""
if not self._config.chipflow.test:
raise ChipFlowError("No [chipflow.test] section found in configuration")
if not self._config.chipflow.test.event_reference:
raise ChipFlowError("No event_reference configuration found in [chipflow.test]")

self.run(args)
compare_events(self._config.chipflow.test.event_reference, self.sim_dir / "events.json")
print("Integration test passed sucessfully")

Loading