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
141 changes: 141 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

chipflow-lib is a Python library for working with the ChipFlow platform, enabling users to build ASIC (Application Specific Integrated Circuit) designs using the Amaranth HDL framework. The library provides a CLI tool (`chipflow`) that handles design elaboration, simulation, and submission to the ChipFlow cloud builder.

## Build and Test Commands

### Installation
- Install dependencies: `pdm install`
- Python 3.11+ required
- Uses PDM for dependency management

### Testing
- Run all tests: `pdm test`
- Run with coverage: `pdm test-cov`
- Run with HTML coverage report: `pdm test-cov-html`
- Run single test: `pdm run pytest tests/test_file.py::test_function_name`
- Run test for specific module with coverage: `pdm run python -m pytest --cov=chipflow_lib.MODULE tests/test_file.py -v`

### Linting
- Run all linting checks: `pdm lint`
- Includes: license header check, ruff linting, and pyright type checking
- Run ruff only: `pdm run ruff check`
- Run pyright only: `pdm run pyright chipflow_lib`

### Documentation
- Build docs: `pdm docs`
- Test documentation: `pdm test-docs`

### Running the CLI
- Run chipflow CLI: `pdm chipflow <command>`

## High-Level Architecture

### Core Components

1. **CLI System** (`cli.py`):
- Entry point for the `chipflow` command
- Dynamically loads "steps" (silicon, sim, software) from configuration
- Steps can be extended via `chipflow.toml` `[chipflow.steps]` section
- Parses `chipflow.toml` configuration using Pydantic models

2. **Configuration System**:
- `chipflow.toml`: User project configuration file (must exist in `CHIPFLOW_ROOT`)
- `config_models.py`: Pydantic models defining configuration schema
- `config.py`: Configuration file parsing logic
- Key configuration sections: `[chipflow]`, `[chipflow.silicon]`, `[chipflow.simulation]`, `[chipflow.software]`, `[chipflow.test]`

3. **Platform Abstraction** (`platforms/`):
- `SiliconPlatform`: Targets ASIC fabrication (supports SKY130, GF180, GF130BCD, IHP_SG13G2, HELVELLYN2)
- `SimPlatform`: Targets simulation (builds C++ CXXRTL simulator)
- `SoftwarePlatform`: RISC-V software build support
- Each platform has process-specific port types (e.g., `Sky130Port` with drive mode configuration)

4. **Steps System** (`steps/`):
- Extensible command architecture
- `silicon.py`: Handles ASIC preparation and cloud submission
- `prepare`: Elaborates Amaranth design to RTLIL
- `submit`: Submits design to ChipFlow cloud builder (requires `CHIPFLOW_API_KEY`)
- `sim.py`: Simulation workflow
- `build`: Builds CXXRTL simulator
- `run`: Runs simulation with software
- `check`: Validates simulation against reference events
- `software.py`: RISC-V software compilation

5. **Pin Locking System** (`_pin_lock.py`):
- `chipflow pin lock`: Allocates physical pins for design components
- Generates `pins.lock` file with persistent pin assignments
- Attempts to reuse previous allocations when possible
- Package definitions in `_packages.py` define available pins per package

6. **IO Annotations** (`platforms/_utils.py`, `platforms/_signatures.py`):
- IO signatures define standard interfaces (JTAG, SPI, I2C, UART, GPIO, QSPI)
- `IOModel` configures electrical characteristics (drive mode, trip point, inversion)
- Annotations attach metadata to Amaranth components for automatic pin allocation

### Key Design Patterns

1. **Component Discovery via Configuration**:
- User defines top-level components in `[chipflow.top]` section as `name = "module:ClassName"`
- `_get_cls_by_reference()` dynamically imports and instantiates classes
- `top_components()` returns dict of instantiated components

2. **Port Wiring**:
- `_wire_up_ports()` in `steps/__init__.py` automatically connects platform ports to component interfaces
- Uses pin lock data to map logical interface names to physical ports
- Handles signal inversion, direction, and enable signals

3. **Build Process**:
- Amaranth elaboration → RTLIL format → Yosys integration → Platform-specific output
- For silicon: RTLIL sent to cloud builder with pin configuration
- For simulation: RTLIL → CXXRTL C++ → compiled simulator executable

4. **Error Handling**:
- Custom `ChipFlowError` exception for user-facing errors
- Causes are preserved and printed with `traceback.print_exception(e.__cause__)`
- CLI wraps unexpected exceptions in `UnexpectedError` with debug context

## Code Style

- Follow PEP-8 style
- Use `snake_case` for Python
- Type hints required (checked by pyright in standard mode)
- Ruff linting enforces: E4, E7, E9, F, W291, W293 (ignores F403, F405 for wildcard imports)
- All files must have SPDX license header: `# SPDX-License-Identifier: BSD-2-Clause`
- No trailing whitespace
- No whitespace on blank lines

## Testing Notes

- Tests located in `tests/` directory
- Fixtures in `tests/fixtures/`
- Use public APIs when testing unless specifically instructed otherwise
- CLI commands count as public API
- Test coverage enforced via pytest-cov

## Common Workflows

### Submitting a Design to ChipFlow Cloud
1. Create `chipflow.toml` with `[chipflow.silicon]` section defining process and package
2. Run `chipflow pin lock` to allocate pins
3. Run `chipflow silicon prepare` to elaborate design
4. Set `CHIPFLOW_API_KEY` environment variable
5. Run `chipflow silicon submit --wait` to submit and monitor build

### Running Simulation
1. Run `chipflow sim build` to build simulator
2. Run `chipflow sim run` to run simulation (builds software automatically)
3. Run `chipflow sim check` to validate against reference events (requires `[chipflow.test]` configuration)

## Environment Variables

- `CHIPFLOW_ROOT`: Project root directory (auto-detected if not set)
- `CHIPFLOW_API_KEY`: API key for cloud builder authentication
- `CHIPFLOW_API_KEY_SECRET`: Deprecated, use `CHIPFLOW_API_KEY` instead
- `CHIPFLOW_API_ORIGIN`: Cloud builder URL (default: https://build.chipflow.org)
- `CHIPFLOW_BACKEND_VERSION`: Developer override for backend version
- `CHIPFLOW_SUBMISSION_NAME`: Override submission name (default: git commit hash)
81 changes: 26 additions & 55 deletions chipflow_lib/__init__.py
Original file line number Diff line number Diff line change
@@ -1,71 +1,42 @@
# SPDX-License-Identifier: BSD-2-Clause
"""
Chipflow library

This is the main entry point for the ChipFlow library, providing tools for
building ASIC designs using the Amaranth HDL framework.
"""

import importlib.metadata
import logging
import os
import sys
import tomli
from pathlib import Path
from typing import TYPE_CHECKING

# Import core utilities
from .utils import (
ChipFlowError,
ensure_chipflow_root,
get_cls_by_reference,
get_src_loc,
)

if TYPE_CHECKING:
from .config_models import Config
from .config import Config

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


logger = logging.getLogger(__name__)

class ChipFlowError(Exception):
pass


def _get_cls_by_reference(reference, context):
module_ref, _, class_ref = reference.partition(":")
try:
module_obj = importlib.import_module(module_ref)
except ModuleNotFoundError as e:
raise ChipFlowError(f"Module `{module_ref}` referenced by {context} is not found") from e
try:
return getattr(module_obj, class_ref)
except AttributeError as e:
raise ChipFlowError(f"Module `{module_ref}` referenced by {context} does not define "
f"`{class_ref}`") from e


def _ensure_chipflow_root():
root = getattr(_ensure_chipflow_root, 'root', None)
if root:
return root

if "CHIPFLOW_ROOT" not in os.environ:
logger.debug(f"CHIPFLOW_ROOT not found in environment. Setting CHIPFLOW_ROOT to {os.getcwd()} for any child scripts")
os.environ["CHIPFLOW_ROOT"] = os.getcwd()
else:
logger.debug(f"CHIPFLOW_ROOT={os.environ['CHIPFLOW_ROOT']} found in environment")

if os.environ["CHIPFLOW_ROOT"] not in sys.path:
sys.path.append(os.environ["CHIPFLOW_ROOT"])
_ensure_chipflow_root.root = Path(os.environ["CHIPFLOW_ROOT"]).absolute() #type: ignore
return _ensure_chipflow_root.root #type: ignore


def _get_src_loc(src_loc_at=0):
frame = sys._getframe(1 + src_loc_at)
return (frame.f_code.co_filename, frame.f_lineno)

# Maintain backward compatibility with underscore-prefixed names
_get_cls_by_reference = get_cls_by_reference
_ensure_chipflow_root = ensure_chipflow_root
_get_src_loc = get_src_loc


def _parse_config() -> 'Config':
"""Parse the chipflow.toml configuration file."""
from .config import _parse_config_file
chipflow_root = _ensure_chipflow_root()
config_file = Path(chipflow_root) / "chipflow.toml"
try:
return _parse_config_file(config_file)
except FileNotFoundError:
raise ChipFlowError(f"Config file not found. I expected to find it at {config_file}")
except tomli.TOMLDecodeError as e:
raise ChipFlowError(f"{config_file} has a formatting error: {e.msg} at line {e.lineno}, column {e.colno}")
from .config.parser import _parse_config as config_parse
return config_parse()


__all__ = [
'__version__',
'ChipFlowError',
'ensure_chipflow_root',
]
75 changes: 8 additions & 67 deletions chipflow_lib/_pin_lock.py
Original file line number Diff line number Diff line change
@@ -1,71 +1,12 @@
# SPDX-License-Identifier: BSD-2-Clause
import inspect
import logging
"""
Backward compatibility shim for pin lock functionality.

from pathlib import Path
from pprint import pformat
This module re-exports pin lock functionality from the packaging module.
New code should import directly from chipflow_lib.packaging instead.
"""

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

# logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
logger = logging.getLogger(__name__)


def lock_pins() -> None:
config = _parse_config()

# Parse with Pydantic for type checking and strong typing

chipflow_root = _ensure_chipflow_root()
lockfile = Path(chipflow_root, 'pins.lock')
oldlock = None

if lockfile.exists():
print("Reusing current pin allocation from `pins.lock`")
oldlock = LockFile.model_validate_json(lockfile.read_text())
logger.debug(f"Old Lock =\n{pformat(oldlock)}")
logger.debug(f"Locking pins: {'using pins.lock' if lockfile.exists() else ''}")

if not config.chipflow.silicon:
raise ChipFlowError("no [chipflow.silicon] section found in chipflow.toml")

# Get package definition from dict instead of Pydantic model
package_name = config.chipflow.silicon.package
package_def = PACKAGE_DEFINITIONS[package_name]
process = config.chipflow.silicon.process

top = top_components(config)

# Use the PackageDef to allocate the pins:
for name, component in top.items():
package_def.register_component(name, component)

newlock = package_def.allocate_pins(config, process, oldlock)

with open(lockfile, 'w') as f:
f.write(newlock.model_dump_json(indent=2, serialize_as_any=True))


class PinCommand:
def __init__(self, config):
self.config = config

def build_cli_parser(self, parser):
assert inspect.getdoc(self.lock) is not None
action_argument = parser.add_subparsers(dest="action")
action_argument.add_parser(
"lock", help=inspect.getdoc(self.lock).splitlines()[0]) # type: ignore

def run_cli(self, args):
logger.debug(f"command {args}")
if args.action == "lock":
self.lock()

def lock(self):
"""Lock the pin map for the design.

Will attempt to reuse previous pin positions.
"""
lock_pins()
__all__ = ['lock_pins', 'PinCommand']
2 changes: 1 addition & 1 deletion chipflow_lib/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
_get_cls_by_reference,
_parse_config,
)
from ._pin_lock import PinCommand
from .packaging import PinCommand

class UnexpectedError(ChipFlowError):
pass
Expand Down
57 changes: 19 additions & 38 deletions chipflow_lib/config.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,20 @@
# SPDX-License-Identifier: BSD-2-Clause
import os


import tomli
from pydantic import ValidationError

from . import ChipFlowError
from .config_models import Config

def get_dir_models():
return os.path.dirname(__file__) + "/models"


def get_dir_software():
return os.path.dirname(__file__) + "/software"


def _parse_config_file(config_file) -> 'Config':
"""Parse a specific chipflow.toml configuration file."""

with open(config_file, "rb") as f:
config_dict = tomli.load(f)

try:
# Validate with Pydantic
return Config.model_validate(config_dict) # Just validate the config_dict
except ValidationError as e:
# Format Pydantic validation errors in a user-friendly way
error_messages = []
for error in e.errors():
location = ".".join(str(loc) for loc in error["loc"])
message = error["msg"]
error_messages.append(f"Error at '{location}': {message}")

error_str = "\n".join(error_messages)
raise ChipFlowError(f"Validation error in chipflow.toml:\n{error_str}")


"""
Backward compatibility shim for config parsing.

This module re-exports config parsing utilities from the config module.
New code should import directly from chipflow_lib.config instead.
"""

# Re-export from config.parser module for backward compatibility
from .config.parser import ( # noqa: F401
get_dir_models,
get_dir_software,
_parse_config_file,
)

__all__ = [
'get_dir_models',
'get_dir_software',
'_parse_config_file',
]
Loading
Loading