Skip to content
Open
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
14 changes: 13 additions & 1 deletion chipflow/config/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,25 @@ class VoltageRange(SelectiveSerializationModel):
typical: Annotated[Optional[Voltage], OmitIfNone()] = None


class BlockConfig(BaseModel):
"""Per-project block dimensions when ``[chipflow.silicon] package = "block"``.

A block is a hard-macro target rather than a packaged chip — the user
declares how many pin slots they want on each axis, and the backend
sizes the macro to fit them at the process's preferred pin pitch.
"""
width: int
height: int


class SiliconConfig(BaseModel):
"""Configuration for silicon in chipflow.toml."""
process: 'Process'
package: str
power: Dict[str, Voltage] = {}
debug: Optional[Dict[str, bool]] = None
# This is still kept around to allow forcing pad locations.
# Required only when package = "block".
block: Optional[BlockConfig] = None

class SimulationConfig(BaseModel):
"""Configuration for simulation settings."""
Expand Down
2 changes: 2 additions & 0 deletions chipflow/packaging/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
# Concrete package types
from .standard import (
BareDiePackageDef,
BlockPackageDef,
QuadPackageDef,
)

Expand Down Expand Up @@ -116,6 +117,7 @@
'LinearAllocPackageDef',
# Package types
'BareDiePackageDef',
'BlockPackageDef',
'QuadPackageDef',
'GAPin',
'GALayout',
Expand Down
10 changes: 8 additions & 2 deletions chipflow/packaging/lockfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,21 @@
if TYPE_CHECKING:
# Forward references to package definitions
from .grid_array import GAPackageDef
from .standard import QuadPackageDef, BareDiePackageDef
from .standard import QuadPackageDef, BareDiePackageDef, BlockPackageDef
from .openframe import OpenframePackageDef

# Import Process directly for pydantic to work properly
from ..config import Process


# Union of all package definition types
PackageDef = Union['GAPackageDef', 'QuadPackageDef', 'BareDiePackageDef', 'OpenframePackageDef']
PackageDef = Union[
'GAPackageDef',
'QuadPackageDef',
'BareDiePackageDef',
'BlockPackageDef',
'OpenframePackageDef',
]


class Package(pydantic.BaseModel):
Expand Down
82 changes: 81 additions & 1 deletion chipflow/packaging/standard.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@

import itertools
from enum import IntEnum
from typing import List, Literal, Tuple
from typing import TYPE_CHECKING, List, Literal, Tuple

from .base import LinearAllocPackageDef
from .pins import PowerPins, JTAGPins, BringupPins
from .lockfile import LockFile
from .allocation import _linear_allocate_components

if TYPE_CHECKING:
from ..config import Config, Process


class _Side(IntEnum):
Expand Down Expand Up @@ -84,6 +89,81 @@ def bringup_pins(self) -> BringupPins:
)


class BlockPackageDef(LinearAllocPackageDef):
"""
Definition of a hard-macro target with pins on four sides.

Structurally a sibling of :class:`BareDiePackageDef` — pins are
addressed by ``(_Side, index)`` tuples — but used when the build is
producing a block (LEF / Liberty / GDS for embedding into another
design) rather than a packaged chip. Differences:

- No I/O pad ring, no JTAG, no fixed clock/reset/power locations:
blocks take power via straps from the parent and route their
clocks/resets through regular pins. Bringup-pin allocation is
skipped.
- ``width`` and ``height`` are pin-slot counts, same units as
:class:`QuadPackageDef.width` / ``.height`` — not microns.
Translation to physical dimensions happens at the backend using
the process's pin pitch.

Attributes:
width: Number of pin slots on top and bottom edges.
height: Number of pin slots on left and right edges.
"""

package_type: Literal["BlockPackageDef"] = "BlockPackageDef"

width: int
height: int

def model_post_init(self, __context):
"""Initialize pin ordering. No bringup pins to subtract."""
pins = set(itertools.product((_Side.N, _Side.S), range(self.width)))
pins |= set(itertools.product((_Side.W, _Side.E), range(self.height)))
self._ordered_pins: List[BareDiePin] = sorted(pins)
return super().model_post_init(__context)

@property
def bringup_pins(self) -> BringupPins:
"""Blocks have no chip-style bringup pins.

The base ``bringup_pins`` property is abstract and must return a
:class:`BringupPins` instance, but :meth:`allocate_pins` below
is overridden to skip the bringup step entirely so this value is
never read. We raise here to make any accidental future caller
fail loudly rather than silently allocating wrong locations.
"""
raise NotImplementedError(
"BlockPackageDef has no bringup pins — clocks, resets and "
"power are wired through regular pins or via parent abutment."
)

def allocate_pins(
self, config: 'Config', process: 'Process', lockfile: LockFile | None
) -> LockFile:
"""Allocate pins without the chip-package bringup step.

Blocks don't have an I/O ring, so the parent class's
``_allocate_bringup`` (which reserves clock/reset/power/JTAG
slots at fixed positions) doesn't apply. Just allocate registered
components linearly from the perimeter slots.
"""
portmap = _linear_allocate_components(
self._interfaces,
lockfile,
self._allocate,
set(self._ordered_pins),
)
package = self._get_package()
return LockFile(
package=package,
process=process,
metadata=self._interfaces,
port_map=portmap,
)


class QuadPackageDef(LinearAllocPackageDef):
"""
Definition of a quad flat package.
Expand Down
25 changes: 23 additions & 2 deletions chipflow/packaging/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,30 @@ def lock_pins(config: Optional['Config'] = None) -> None:
if not config.chipflow.silicon:
raise ChipFlowError("no [chipflow.silicon] section found in chipflow.toml")

# Get package definition from dict
# Resolve the package definition. Most packages are fixed entries in
# PACKAGE_DEFINITIONS (PGA144, BGA144, …). The special name "block"
# is parameterized per project from [chipflow.silicon.block].
package_name = config.chipflow.silicon.package
package_def = PACKAGE_DEFINITIONS[package_name]
if package_name == "block":
from .standard import BlockPackageDef
block_cfg = config.chipflow.silicon.block
if block_cfg is None:
raise ChipFlowError(
'package = "block" requires a [chipflow.silicon.block] '
'section with `width` and `height` (pin slot counts).'
)
package_def = BlockPackageDef(
name="block",
width=block_cfg.width,
height=block_cfg.height,
)
else:
if package_name not in PACKAGE_DEFINITIONS:
raise ChipFlowError(
f'Unknown package {package_name!r}. Known: '
f'{sorted(PACKAGE_DEFINITIONS.keys()) + ["block"]}'
)
package_def = PACKAGE_DEFINITIONS[package_name]
process = config.chipflow.silicon.process

top = top_components(config)
Expand Down
47 changes: 47 additions & 0 deletions tests/test_block_package.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# SPDX-License-Identifier: BSD-2-Clause
"""Tests for BlockPackageDef — the parameterized per-project package used
when ``[chipflow.silicon] package = "block"``."""

import unittest

from chipflow.packaging.standard import BlockPackageDef, _Side


class BlockPackageDefTestCase(unittest.TestCase):
def test_pin_slots_match_perimeter(self):
"""A 5×3 block has 5 N + 5 S + 3 W + 3 E = 16 slots."""
pkg = BlockPackageDef(name="block", width=5, height=3)
slots = pkg._ordered_pins
self.assertEqual(len(slots), 5 + 5 + 3 + 3)
sides = {s for s, _ in slots}
self.assertEqual(sides, {_Side.N, _Side.S, _Side.W, _Side.E})

def test_does_not_reserve_bringup_slots(self):
"""Unlike chip packages, BlockPackageDef must not subtract any
bringup pins from the available set — blocks have no I/O ring."""
pkg = BlockPackageDef(name="block", width=4, height=4)
# All 16 perimeter slots remain available.
self.assertEqual(len(pkg._ordered_pins), 16)

def test_bringup_pins_property_raises(self):
"""The abstract bringup_pins property must not be silently usable
on a block — calling it should fail loudly."""
pkg = BlockPackageDef(name="block", width=4, height=4)
with self.assertRaises(NotImplementedError):
pkg.bringup_pins

def test_serialization_round_trip(self):
"""Block defs survive pydantic serialize/deserialize so they fit
into LockFile / Package / bundle.zip."""
pkg = BlockPackageDef(name="block", width=10, height=20)
dumped = pkg.model_dump()
self.assertEqual(dumped["package_type"], "BlockPackageDef")
self.assertEqual(dumped["width"], 10)
self.assertEqual(dumped["height"], 20)
round = BlockPackageDef.model_validate(dumped)
self.assertEqual(round.width, 10)
self.assertEqual(round.height, 20)


if __name__ == "__main__":
unittest.main()
Loading