Skip to content

Commit 93a13cb

Browse files
robtaylorclaude
andcommitted
Phase 5: Extract config module
Extract config functionality into config/ module: - Created config/models.py with Config, Process, and related models - Created config/parser.py with parsing utilities - Created config/__init__.py with public API exports - Converted config_models.py to backward compatibility shim - Converted config.py to backward compatibility shim - Updated __init__.py to delegate to config module - Updated tests to use new module paths All tests passing (38 passed, 11 skipped). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 04c3abc commit 93a13cb

File tree

11 files changed

+381
-203
lines changed

11 files changed

+381
-203
lines changed

chipflow_lib/__init__.py

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
"""
88

99
import importlib.metadata
10-
import tomli
11-
from pathlib import Path
1210
from typing import TYPE_CHECKING
1311

1412
# Import core utilities
@@ -20,7 +18,7 @@
2018
)
2119

2220
if TYPE_CHECKING:
23-
from .config_models import Config
21+
from .config import Config
2422

2523
__version__ = importlib.metadata.version("chipflow_lib")
2624

@@ -33,17 +31,8 @@
3331

3432
def _parse_config() -> 'Config':
3533
"""Parse the chipflow.toml configuration file."""
36-
from .config import _parse_config_file
37-
chipflow_root = ensure_chipflow_root()
38-
config_file = Path(chipflow_root) / "chipflow.toml"
39-
try:
40-
return _parse_config_file(config_file)
41-
except FileNotFoundError:
42-
raise ChipFlowError(f"Config file not found. I expected to find it at {config_file}")
43-
except tomli.TOMLDecodeError as e:
44-
raise ChipFlowError(
45-
f"{config_file} has a formatting error: {e.msg} at line {e.lineno}, column {e.colno}"
46-
)
34+
from .config import _parse_config as config_parse
35+
return config_parse()
4736

4837

4938
__all__ = [

chipflow_lib/_pin_lock.py

Lines changed: 8 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,12 @@
11
# SPDX-License-Identifier: BSD-2-Clause
2-
import inspect
3-
import logging
2+
"""
3+
Backward compatibility shim for pin lock functionality.
44
5-
from pathlib import Path
6-
from pprint import pformat
5+
This module re-exports pin lock functionality from the packaging module.
6+
New code should import directly from chipflow_lib.packaging instead.
7+
"""
78

8-
from . import _parse_config, _ensure_chipflow_root, ChipFlowError
9-
from .platforms._utils import top_components, LockFile
10-
from .platforms._packages import PACKAGE_DEFINITIONS
9+
# Re-export from packaging module for backward compatibility
10+
from .packaging import lock_pins, PinCommand # noqa: F401
1111

12-
# logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
13-
logger = logging.getLogger(__name__)
14-
15-
16-
def lock_pins() -> None:
17-
config = _parse_config()
18-
19-
# Parse with Pydantic for type checking and strong typing
20-
21-
chipflow_root = _ensure_chipflow_root()
22-
lockfile = Path(chipflow_root, 'pins.lock')
23-
oldlock = None
24-
25-
if lockfile.exists():
26-
print("Reusing current pin allocation from `pins.lock`")
27-
oldlock = LockFile.model_validate_json(lockfile.read_text())
28-
logger.debug(f"Old Lock =\n{pformat(oldlock)}")
29-
logger.debug(f"Locking pins: {'using pins.lock' if lockfile.exists() else ''}")
30-
31-
if not config.chipflow.silicon:
32-
raise ChipFlowError("no [chipflow.silicon] section found in chipflow.toml")
33-
34-
# Get package definition from dict instead of Pydantic model
35-
package_name = config.chipflow.silicon.package
36-
package_def = PACKAGE_DEFINITIONS[package_name]
37-
process = config.chipflow.silicon.process
38-
39-
top = top_components(config)
40-
41-
# Use the PackageDef to allocate the pins:
42-
for name, component in top.items():
43-
package_def.register_component(name, component)
44-
45-
newlock = package_def.allocate_pins(config, process, oldlock)
46-
47-
with open(lockfile, 'w') as f:
48-
f.write(newlock.model_dump_json(indent=2, serialize_as_any=True))
49-
50-
51-
class PinCommand:
52-
def __init__(self, config):
53-
self.config = config
54-
55-
def build_cli_parser(self, parser):
56-
assert inspect.getdoc(self.lock) is not None
57-
action_argument = parser.add_subparsers(dest="action")
58-
action_argument.add_parser(
59-
"lock", help=inspect.getdoc(self.lock).splitlines()[0]) # type: ignore
60-
61-
def run_cli(self, args):
62-
logger.debug(f"command {args}")
63-
if args.action == "lock":
64-
self.lock()
65-
66-
def lock(self):
67-
"""Lock the pin map for the design.
68-
69-
Will attempt to reuse previous pin positions.
70-
"""
71-
lock_pins()
12+
__all__ = ['lock_pins', 'PinCommand']

chipflow_lib/config.py

Lines changed: 19 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,20 @@
11
# SPDX-License-Identifier: BSD-2-Clause
2-
import os
3-
4-
5-
import tomli
6-
from pydantic import ValidationError
7-
8-
from . import ChipFlowError
9-
from .config_models import Config
10-
11-
def get_dir_models():
12-
return os.path.dirname(__file__) + "/models"
13-
14-
15-
def get_dir_software():
16-
return os.path.dirname(__file__) + "/software"
17-
18-
19-
def _parse_config_file(config_file) -> 'Config':
20-
"""Parse a specific chipflow.toml configuration file."""
21-
22-
with open(config_file, "rb") as f:
23-
config_dict = tomli.load(f)
24-
25-
try:
26-
# Validate with Pydantic
27-
return Config.model_validate(config_dict) # Just validate the config_dict
28-
except ValidationError as e:
29-
# Format Pydantic validation errors in a user-friendly way
30-
error_messages = []
31-
for error in e.errors():
32-
location = ".".join(str(loc) for loc in error["loc"])
33-
message = error["msg"]
34-
error_messages.append(f"Error at '{location}': {message}")
35-
36-
error_str = "\n".join(error_messages)
37-
raise ChipFlowError(f"Validation error in chipflow.toml:\n{error_str}")
38-
39-
2+
"""
3+
Backward compatibility shim for config parsing.
4+
5+
This module re-exports config parsing utilities from the config module.
6+
New code should import directly from chipflow_lib.config instead.
7+
"""
8+
9+
# Re-export from config module for backward compatibility
10+
from .config import ( # noqa: F401
11+
get_dir_models,
12+
get_dir_software,
13+
_parse_config_file,
14+
)
15+
16+
__all__ = [
17+
'get_dir_models',
18+
'get_dir_software',
19+
'_parse_config_file',
20+
]

chipflow_lib/config/__init__.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# SPDX-License-Identifier: BSD-2-Clause
2+
"""
3+
Configuration management for ChipFlow.
4+
5+
This module provides configuration models and parsing functionality
6+
for chipflow.toml configuration files.
7+
"""
8+
9+
# Configuration models
10+
from .models import (
11+
Process,
12+
Voltage,
13+
VoltageRange,
14+
SiliconConfig,
15+
SimulationConfig,
16+
CompilerConfig,
17+
SoftwareConfig,
18+
TestConfig,
19+
ChipFlowConfig,
20+
Config,
21+
)
22+
23+
# Parsing utilities
24+
from .parser import (
25+
get_dir_models,
26+
get_dir_software,
27+
_parse_config_file,
28+
_parse_config,
29+
)
30+
31+
__all__ = [
32+
# Models
33+
'Process',
34+
'Voltage',
35+
'VoltageRange',
36+
'SiliconConfig',
37+
'SimulationConfig',
38+
'CompilerConfig',
39+
'SoftwareConfig',
40+
'TestConfig',
41+
'ChipFlowConfig',
42+
'Config',
43+
# Parser utilities
44+
'get_dir_models',
45+
'get_dir_software',
46+
'_parse_config_file',
47+
'_parse_config',
48+
]

chipflow_lib/config/models.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# SPDX-License-Identifier: BSD-2-Clause
2+
from enum import Enum
3+
from pathlib import Path
4+
from typing import Dict, Optional, Any, List, Annotated
5+
6+
from pydantic import (
7+
BaseModel, PlainSerializer, WrapValidator
8+
)
9+
10+
from .._appresponse import AppResponseModel, OmitIfNone
11+
12+
class Process(Enum):
13+
"""
14+
IC manufacturing process
15+
"""
16+
#: Skywater foundry open-source 130nm process
17+
SKY130 = "sky130"
18+
#: GlobalFoundries open-source 130nm process
19+
GF180 = "gf180"
20+
#: Pragmatic Semiconductor FlexIC process (old)
21+
HELVELLYN2 = "helvellyn2"
22+
#: GlobalFoundries 130nm BCD process
23+
GF130BCD = "gf130bcd"
24+
#: IHP open source 130nm SiGe Bi-CMOS process
25+
IHP_SG13G2 = "ihp_sg13g2"
26+
27+
def __str__(self):
28+
return f'{self.value}'
29+
30+
31+
32+
Voltage = Annotated[
33+
float,
34+
PlainSerializer(lambda x: f'{x:.1e}V', return_type=str),
35+
WrapValidator(lambda v, h: h(v.strip('Vv ') if isinstance(v, str) else h(v)))
36+
]
37+
38+
39+
class VoltageRange(AppResponseModel):
40+
"""
41+
Models a voltage range for a power domain or IO
42+
"""
43+
min: Annotated[Optional[Voltage], OmitIfNone()] = None
44+
max: Annotated[Optional[Voltage], OmitIfNone()] = None
45+
typical: Annotated[Optional[Voltage], OmitIfNone()] = None
46+
47+
48+
class SiliconConfig(BaseModel):
49+
"""Configuration for silicon in chipflow.toml."""
50+
process: 'Process'
51+
package: str
52+
power: Dict[str, Voltage] = {}
53+
debug: Optional[Dict[str, bool]] = None
54+
# This is still kept around to allow forcing pad locations.
55+
56+
class SimulationConfig(BaseModel):
57+
num_steps: int = 3000000
58+
59+
class CompilerConfig(BaseModel):
60+
cpu: str
61+
abi: str
62+
63+
class SoftwareConfig(BaseModel):
64+
riscv: CompilerConfig = CompilerConfig(cpu="baseline_rv32-a-c-d", abi="ilp32")
65+
66+
class TestConfig(BaseModel):
67+
event_reference: Path
68+
69+
class ChipFlowConfig(BaseModel):
70+
"""Root configuration for chipflow.toml."""
71+
project_name: str
72+
top: Dict[str, Any] = {}
73+
steps: Optional[Dict[str, str]] = None
74+
silicon: Optional[SiliconConfig] = None
75+
simulation: SimulationConfig = SimulationConfig()
76+
software: SoftwareConfig = SoftwareConfig()
77+
clock_domains: Optional[List[str]] = None
78+
test: Optional[TestConfig] = None
79+
80+
class Config(BaseModel):
81+
"""Root configuration model for chipflow.toml."""
82+
chipflow: ChipFlowConfig

chipflow_lib/config/parser.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# SPDX-License-Identifier: BSD-2-Clause
2+
"""
3+
Configuration file parsing and utilities.
4+
"""
5+
6+
import os
7+
import tomli
8+
9+
from pathlib import Path
10+
from pydantic import ValidationError
11+
12+
from ..utils import ChipFlowError, ensure_chipflow_root
13+
from .models import Config
14+
15+
def get_dir_models():
16+
return os.path.dirname(__file__) + "/models"
17+
18+
19+
def get_dir_software():
20+
return os.path.dirname(__file__) + "/software"
21+
22+
23+
def _parse_config_file(config_file) -> 'Config':
24+
"""Parse a specific chipflow.toml configuration file."""
25+
26+
with open(config_file, "rb") as f:
27+
config_dict = tomli.load(f)
28+
29+
try:
30+
# Validate with Pydantic
31+
return Config.model_validate(config_dict) # Just validate the config_dict
32+
except ValidationError as e:
33+
# Format Pydantic validation errors in a user-friendly way
34+
error_messages = []
35+
for error in e.errors():
36+
location = ".".join(str(loc) for loc in error["loc"])
37+
message = error["msg"]
38+
error_messages.append(f"Error at '{location}': {message}")
39+
40+
error_str = "\n".join(error_messages)
41+
raise ChipFlowError(f"Validation error in chipflow.toml:\n{error_str}")
42+
43+
44+
def _parse_config() -> 'Config':
45+
"""Parse the chipflow.toml configuration file."""
46+
chipflow_root = ensure_chipflow_root()
47+
config_file = Path(chipflow_root) / "chipflow.toml"
48+
try:
49+
return _parse_config_file(config_file)
50+
except FileNotFoundError:
51+
raise ChipFlowError(f"Config file not found. I expected to find it at {config_file}")
52+
except tomli.TOMLDecodeError as e:
53+
raise ChipFlowError(
54+
f"{config_file} has a formatting error: {e.msg} at line {e.lineno}, column {e.colno}"
55+
)

0 commit comments

Comments
 (0)