Skip to content

Commit 38c5fcf

Browse files
machshevclaude
andcommitted
feat: add pydantic model and parser for lint flow config
Add Pydantic-based config parser separate from existing flow config infrastructure to enable gradual migration to standalone components. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: James McCorrie <james.mccorrie@lowrisc.org>
1 parent bcd72b0 commit 38c5fcf

5 files changed

Lines changed: 454 additions & 0 deletions

File tree

src/dvsim/linting/config.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Copyright lowRISC contributors (OpenTitan project).
2+
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
"""Pydantic models for lint flow configuration."""
6+
7+
8+
from pydantic import BaseModel, ConfigDict, Field
9+
10+
11+
class MessageBucket(BaseModel):
12+
"""Message bucket configuration for categorizing lint messages."""
13+
14+
model_config = ConfigDict(frozen=True)
15+
16+
category: str = Field(..., description="Message category (e.g., 'flow', 'lint')")
17+
severity: str = Field(..., description="Message severity (e.g., 'info', 'warning', 'error')")
18+
label: str = Field(default="", description="Optional label for the bucket")
19+
20+
21+
class LintFlowConfig(BaseModel):
22+
"""Configuration for the lint flow.
23+
24+
This model represents the core lint flow configuration that can be parsed
25+
from hjson files. It focuses on lint-specific fields and does not include
26+
all the base flow configuration fields that are handled by the existing
27+
FlowCfg class hierarchy.
28+
"""
29+
30+
# Flow identification
31+
flow: str = Field(default="lint", description="Flow type identifier")
32+
name: str = Field(..., description="Name of the lint configuration")
33+
34+
# Build configuration
35+
build_dir: str = Field(default="", description="Build directory path")
36+
build_log: str = Field(default="", description="Build log file path")
37+
build_cmd: str = Field(default="", description="Build command")
38+
build_opts: list[str] = Field(default_factory=list, description="Build options")
39+
40+
# FuseSoC configuration
41+
fusesoc_core: str = Field(default="", description="FuseSoC core to use")
42+
additional_fusesoc_argument: str = Field(
43+
default="", description="Additional FuseSoC argument (e.g., mapping)"
44+
)
45+
46+
# Lint-specific configuration
47+
is_style_lint: bool = Field(default=False, description="Whether this is a style lint run")
48+
report_severities: list[str] = Field(
49+
default_factory=lambda: ["info", "warning", "error"],
50+
description="Message severities to include in reports",
51+
)
52+
fail_severities: list[str] = Field(
53+
default_factory=lambda: ["warning", "error"],
54+
description="Message severities that cause the flow to fail",
55+
)
56+
message_buckets: list[MessageBucket] = Field(
57+
default_factory=list, description="Message bucket configuration"
58+
)
59+
60+
# Tool configuration
61+
tool: str | None = Field(default=None, description="Lint tool to use")
62+
dut: str = Field(default="", description="Design under test name")
63+
64+
# Directory paths
65+
scratch_path: str = Field(default="", description="Scratch directory path")
66+
rel_path: str = Field(default="", description="Relative path for results")
67+
68+
# Import and use configurations
69+
import_cfgs: list[str] = Field(
70+
default_factory=list, description="Configuration files to import"
71+
)
72+
use_cfgs: list[dict | str] = Field(
73+
default_factory=list, description="Sub-configurations to use (for primary configs)"
74+
)
75+
76+
model_config = ConfigDict(
77+
frozen=True, # Make the model immutable
78+
extra="allow", # Allow extra fields for forwards compatibility
79+
)

src/dvsim/linting/parser.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Copyright lowRISC contributors (OpenTitan project).
2+
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
"""Parser for lint flow hjson configuration files."""
6+
7+
from pathlib import Path
8+
9+
import hjson
10+
from pydantic import ValidationError
11+
12+
from dvsim.linting.config import LintFlowConfig, MessageBucket
13+
14+
15+
def parse_lint_flow_config(hjson_path: str | Path) -> LintFlowConfig:
16+
"""Parse a lint flow hjson configuration file.
17+
18+
This function loads an hjson file and validates it against the LintFlowConfig
19+
pydantic model. This is a new parser specifically for lint flows and does not
20+
modify the existing flow config parsing infrastructure.
21+
22+
Args:
23+
hjson_path: Path to the hjson configuration file
24+
25+
Returns:
26+
LintFlowConfig: Validated lint flow configuration
27+
28+
Raises:
29+
FileNotFoundError: If the hjson file doesn't exist
30+
ValidationError: If the hjson data doesn't match the expected schema
31+
RuntimeError: If there are other errors parsing the hjson
32+
33+
Example:
34+
>>> config = parse_lint_flow_config("path/to/lint_cfg.hjson")
35+
>>> print(config.name)
36+
>>> print(config.report_severities)
37+
38+
"""
39+
hjson_path = Path(hjson_path)
40+
41+
if not hjson_path.exists():
42+
msg = f"Configuration file not found: {hjson_path}"
43+
raise FileNotFoundError(msg)
44+
45+
# Parse the hjson file using hjson library directly
46+
try:
47+
with hjson_path.open() as f:
48+
hjson_data = hjson.load(f, use_decimal=True)
49+
except hjson.HjsonDecodeError as e:
50+
msg = f"Failed to parse hjson file {hjson_path}: {e}"
51+
raise RuntimeError(msg) from e
52+
except OSError as e:
53+
msg = f"Failed to read hjson file {hjson_path}: {e}"
54+
raise RuntimeError(msg) from e
55+
56+
# Validate against the pydantic model
57+
try:
58+
# Convert message_buckets from list of dicts to list of MessageBucket objects
59+
if "message_buckets" in hjson_data:
60+
hjson_data["message_buckets"] = [
61+
MessageBucket(**bucket) if isinstance(bucket, dict) else bucket
62+
for bucket in hjson_data["message_buckets"]
63+
]
64+
65+
return LintFlowConfig(**hjson_data)
66+
except ValidationError as e:
67+
msg = f"Configuration validation failed for {hjson_path}:\n{e}"
68+
raise RuntimeError(msg) from e
69+
70+
71+
def load_lint_config_from_dict(config_dict: dict) -> LintFlowConfig:
72+
"""Load a lint flow configuration from a dictionary.
73+
74+
This is useful for loading inline configurations or for testing.
75+
76+
Args:
77+
config_dict: Dictionary containing lint flow configuration
78+
79+
Returns:
80+
LintFlowConfig: Validated lint flow configuration
81+
82+
Raises:
83+
ValidationError: If the dictionary doesn't match the expected schema
84+
85+
Example:
86+
>>> config_dict = {"name": "test_lint", "flow": "lint"}
87+
>>> config = load_lint_config_from_dict(config_dict)
88+
89+
"""
90+
# Convert message_buckets if present
91+
if "message_buckets" in config_dict:
92+
config_dict["message_buckets"] = [
93+
MessageBucket(**bucket) if isinstance(bucket, dict) else bucket
94+
for bucket in config_dict["message_buckets"]
95+
]
96+
97+
return LintFlowConfig(**config_dict)

tests/flow/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Copyright lowRISC contributors (OpenTitan project).
2+
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
"""Flow tests."""
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright lowRISC contributors (OpenTitan project).
2+
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
// Example lint flow configuration for integration testing
6+
{
7+
name: aes_dv_lint
8+
flow: lint
9+
10+
// Design under test
11+
dut: aes
12+
13+
// FuseSoC configuration
14+
fusesoc_core: lowrisc:dv:aes_sim
15+
additional_fusesoc_argument: --mapping=lowrisc:systems:top_earlgrey:0.1
16+
17+
// Build configuration
18+
build_dir: "{scratch_path}/{build_mode}"
19+
build_log: "{build_dir}/{tool}.log"
20+
build_cmd: fusesoc
21+
build_opts: [
22+
"--cores-root {proj_root}/hw"
23+
"run"
24+
"--target={flow}"
25+
"--tool={tool}"
26+
"--work-root={build_dir}/fusesoc-work"
27+
]
28+
29+
// Lint configuration
30+
is_style_lint: false
31+
report_severities: ["info", "warning", "error"]
32+
fail_severities: ["warning", "error"]
33+
34+
// Message bucket configuration
35+
message_buckets: [
36+
{category: "flow", severity: "info", label: "Flow Info"}
37+
{category: "flow", severity: "warning", label: "Flow Warnings"}
38+
{category: "flow", severity: "error", label: "Flow Errors"}
39+
{category: "lint", severity: "info", label: "Lint Info"}
40+
{category: "lint", severity: "warning", label: "Lint Warnings"}
41+
{category: "lint", severity: "error", label: "Lint Errors"}
42+
]
43+
44+
// Tool configuration
45+
tool: ascentlint
46+
47+
// Paths
48+
scratch_path: /tmp/dvsim/scratch
49+
rel_path: hw/ip/aes/dv/lint/{tool}
50+
}

0 commit comments

Comments
 (0)