nextpnr/himbaechel/himbaechel_dbgen/chip.py
YRabbit df13104384 gowin: Himbaechel. Add extra chip data
To implement unusual IOs that have a dynamically changing configuration
 it is convenient to store the switching method in the additional chip
 data.

Signed-off-by: YRabbit <rabbit@yrabbit.cyou>
2023-08-31 08:28:09 +02:00

596 lines
21 KiB
Python

from dataclasses import dataclass, field
from .bba import BBAWriter
from enum import Enum
from typing import Optional
import abc
import struct, hashlib
"""
This provides a semi-flattened routing graph that is built into a deduplicated one.
There are two key elements:
- Tile Types:
these represent a unique kind of grid location in terms of its contents:
- bels (logic functionality like LUTs, FFs, IOs, IP, etc)
- internal wires (excluding connectivity to other tiles)
- pips that switch internal wires
- Nodes
these merge tile-internal wires across wires to create inter-tile connectivity
so, for example, a length-4 wire might connect (x, y, "E4AI") and (x+3, y, "E4AO")
"""
class BBAStruct(abc.ABC):
def serialise_lists(self, context: str, bba: BBAWriter):
pass
def serialise(self, context: str, bba: BBAWriter):
pass
@dataclass(eq=True, frozen=True)
class IdString:
index: int = 0
class StringPool:
def __init__(self):
self.strs = {"": 0}
self.known_id_count = 1
def read_constids(self, file: str):
idx = 1
with open(file, "r") as f:
for line in f:
l = line.strip()
if not l.startswith("X("):
continue
l = l[2:]
assert l.endswith(")"), l
l = l[:-1].strip()
i = self.id(l)
assert i.index == idx, (i, idx, l)
idx += 1
self.known_id_count = idx
def id(self, val: str):
if val in self.strs:
return IdString(self.strs[val])
else:
idx = len(self.strs)
self.strs[val] = idx
return IdString(idx)
def serialise_lists(self, context: str, bba: BBAWriter):
bba.label(f"{context}_strs")
for s, idx in sorted(self.strs.items(), key=lambda x: x[1]): # sort by index
if idx < self.known_id_count:
continue
bba.str(s)
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.known_id_count)
bba.slice(f"{context}_strs", len(self.strs) - self.known_id_count)
@dataclass
class PinType(Enum):
INPUT = 0
OUTPUT = 1
INOUT = 2
@dataclass
class BelPin(BBAStruct):
name: IdString
wire: int
dir: PinType
def serialise_lists(self, context: str, bba: BBAWriter):
pass
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.name.index)
bba.u32(self.wire)
bba.u32(self.dir.value)
BEL_FLAG_GLOBAL = 0x01
BEL_FLAG_HIDDEN = 0x02
@dataclass
class BelData(BBAStruct):
index: int
name: IdString
bel_type: IdString
z: int
flags: int = 0
site: int = 0
checker_idx: int = 0
pins: list[BelPin] = field(default_factory=list)
extra_data: object = None
def serialise_lists(self, context: str, bba: BBAWriter):
# sort pins for fast binary search lookups
self.pins.sort(key=lambda p: p.name.index)
# write pins array
bba.label(f"{context}_pins")
for i, pin in enumerate(self.pins):
pin.serialise(f"{context}_pin{i}", bba)
# extra data (optional)
if self.extra_data is not None:
bba.label(f"{context}_extra_data")
self.extra_data.serialise(f"{context}_extra_data", bba)
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.name.index)
bba.u32(self.bel_type.index)
bba.u16(self.z)
bba.u16(0)
bba.u32(self.flags)
bba.u32(self.site)
bba.u32(self.checker_idx)
bba.slice(f"{context}_pins", len(self.pins))
if self.extra_data is not None:
bba.ref(f"{context}_extra_data")
else:
bba.u32(0)
@dataclass
class BelPinRef(BBAStruct):
bel: int
pin: IdString
def serialise_lists(self, context: str, bba: BBAWriter):
pass
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.bel)
bba.u32(self.pin.index)
@dataclass
class TileWireData:
index: int
name: IdString
wire_type: IdString
flags: int = 0
# not serialised, but used to build the global constant networks
const_val: int = -1
# these crossreferences will be updated by finalise(), no need to manually update
pips_uphill: list[int] = field(default_factory=list)
pips_downhill: list[int] = field(default_factory=list)
bel_pins: list[BelPinRef] = field(default_factory=list)
def serialise_lists(self, context: str, bba: BBAWriter):
bba.label(f"{context}_pips_uh")
for pip_idx in self.pips_uphill:
bba.u32(pip_idx)
bba.label(f"{context}_pips_dh")
for pip_idx in self.pips_downhill:
bba.u32(pip_idx)
bba.label(f"{context}_bel_pins")
for i, bel_pin in enumerate(self.bel_pins):
bel_pin.serialise(f"{context}_bp{i}", bba)
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.name.index)
bba.u32(self.wire_type.index)
bba.u32(self.flags)
bba.slice(f"{context}_pips_uh", len(self.pips_uphill))
bba.slice(f"{context}_pips_dh", len(self.pips_downhill))
bba.slice(f"{context}_bel_pins", len(self.bel_pins))
@dataclass
class PipData(BBAStruct):
index: int
src_wire: int
dst_wire: int
pip_type: IdString = field(default_factory=IdString)
flags: int = 0
timing_idx: int = -1
def serialise_lists(self, context: str, bba: BBAWriter):
pass
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.src_wire)
bba.u32(self.dst_wire)
bba.u32(self.pip_type.index)
bba.u32(self.flags)
bba.u32(self.timing_idx)
@dataclass
class TileType(BBAStruct):
strs: StringPool
type_name: IdString
bels: list[BelData] = field(default_factory=list)
pips: list[PipData] = field(default_factory=list)
wires: list[TileWireData] = field(default_factory=list)
_wire2idx: dict[IdString, int] = field(default_factory=dict)
extra_data: object = None
def create_bel(self, name: str, type: str, z: int):
# Create a new bel of a given name, type and z (index within the tile) in the tile type
bel = BelData(index=len(self.bels),
name=self.strs.id(name),
bel_type=self.strs.id(type),
z=z)
self.bels.append(bel)
return bel
def add_bel_pin(self, bel: BelData, pin: str, wire: str, dir: PinType):
# Add a pin with associated wire to a bel. The wire should exist already.
pin_id = self.strs.id(pin)
wire_idx = self._wire2idx[self.strs.id(wire)]
bel.pins.append(BelPin(pin_id, wire_idx, dir))
self.wires[wire_idx].bel_pins.append(BelPinRef(bel.index, pin_id))
def create_wire(self, name: str, type: str=""):
# Create a new tile wire of a given name and type (optional) in the tile type
wire = TileWireData(index=len(self.wires),
name=self.strs.id(name),
wire_type=self.strs.id(type))
self._wire2idx[wire.name] = wire.index
self.wires.append(wire)
return wire
def create_pip(self, src: str, dst: str):
# Create a pip between two tile wires in the tile type. Both wires should exist already.
src_idx = self._wire2idx[self.strs.id(src)]
dst_idx = self._wire2idx[self.strs.id(dst)]
pip = PipData(index=len(self.pips), src_wire=src_idx, dst_wire=dst_idx)
self.wires[src_idx].pips_downhill.append(pip.index)
self.wires[dst_idx].pips_uphill.append(pip.index)
self.pips.append(pip)
return pip
def has_wire(self, wire: str):
# Check if a wire has already been created
return self.strs.id(wire) in self._wire2idx
def set_wire_type(self, wire: str, type: str):
# wire type change
self.wires[self._wire2idx[self.strs.id(wire)]].wire_type = self.strs.id(type)
def serialise_lists(self, context: str, bba: BBAWriter):
# list children of members
for i, bel in enumerate(self.bels):
bel.serialise_lists(f"{context}_bel{i}", bba)
for i, wire in enumerate(self.wires):
wire.serialise_lists(f"{context}_wire{i}", bba)
for i, pip in enumerate(self.pips):
pip.serialise_lists(f"{context}_pip{i}", bba)
# lists of members
bba.label(f"{context}_bels")
for i, bel in enumerate(self.bels):
bel.serialise(f"{context}_bel{i}", bba)
bba.label(f"{context}_wires")
for i, wire in enumerate(self.wires):
wire.serialise(f"{context}_wire{i}", bba)
bba.label(f"{context}_pips")
for i, pip in enumerate(self.pips):
pip.serialise(f"{context}_pip{i}", bba)
# extra data (optional)
if self.extra_data is not None:
bba.label(f"{context}_extra_data")
self.extra_data.serialise(f"{context}_extra_data", bba)
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.type_name.index)
bba.slice(f"{context}_bels", len(self.bels))
bba.slice(f"{context}_wires", len(self.wires))
bba.slice(f"{context}_pips", len(self.pips))
if self.extra_data is not None:
bba.ref(f"{context}_extra_data")
else:
bba.u32(0)
# Pre deduplication (nodes flattened, absolute coords)
@dataclass
class NodeWire:
x: int
y: int
wire: str
# Post deduplication (node shapes merged, relative coords)
@dataclass
class TileWireRef(BBAStruct):
dx: int
dy: int
wire: int
def serialise_lists(self, context: str, bba: BBAWriter):
pass
def serialise(self, context: str, bba: BBAWriter):
bba.u16(self.dx)
bba.u16(self.dy)
bba.u16(self.wire)
@dataclass
class NodeShape(BBAStruct):
wires: list[TileWireRef] = field(default_factory=list)
def key(self):
m = hashlib.sha1()
for wire in self.wires:
m.update(wire.dx.to_bytes(2, 'little', signed=True))
m.update(wire.dy.to_bytes(2, 'little', signed=True))
m.update(wire.wire.to_bytes(2, 'little'))
return m.digest()
def serialise_lists(self, context: str, bba: BBAWriter):
bba.label(f"{context}_wires")
for i, w in enumerate(self.wires):
w.serialise(f"{context}_w{i}", bba)
if len(self.wires) % 2 != 0:
bba.u16(0) # alignment
def serialise(self, context: str, bba: BBAWriter):
bba.slice(f"{context}_wires", len(self.wires))
bba.u32(-1) # timing index (not yet used)
MODE_TILE_WIRE = 0x7000
MODE_IS_ROOT = 0x7001
MODE_ROW_CONST = 0x7002
MODE_GLB_CONST = 0x7003
@dataclass
class RelNodeRef(BBAStruct):
dx_mode: int = MODE_TILE_WIRE
dy: int = 0
wire: int = 0
def serialise_lists(self, context: str, bba: BBAWriter):
pass
def serialise(self, context: str, bba: BBAWriter):
bba.u16(self.dx_mode)
bba.u16(self.dy)
bba.u16(self.wire)
@dataclass
class TileRoutingShape(BBAStruct):
wire_to_node: list[RelNodeRef] = field(default_factory=list)
def key(self):
m = hashlib.sha1()
for wire in self.wire_to_node:
m.update(wire.dx_mode.to_bytes(2, 'little', signed=True))
m.update(wire.dy.to_bytes(2, 'little', signed=(wire.dy < 0)))
m.update(wire.wire.to_bytes(2, 'little', signed=True))
return m.digest()
def serialise_lists(self, context: str, bba: BBAWriter):
bba.label(f"{context}_w2n")
for i, w in enumerate(self.wire_to_node):
w.serialise(f"{context}_w{i}", bba)
if len(self.wire_to_node) % 2 != 0:
bba.u16(0) # alignment
def serialise(self, context: str, bba: BBAWriter):
bba.slice(f"{context}_w2n", len(self.wire_to_node))
bba.u32(-1) # timing index
@dataclass
class TileInst(BBAStruct):
x: int
y: int
type_idx: Optional[int] = None
name_prefix: IdString = field(default_factory=IdString)
loc_type: int = 0
shape: TileRoutingShape = field(default_factory=TileRoutingShape)
shape_idx: int = -1
extra_data: object = None
def serialise_lists(self, context: str, bba: BBAWriter):
if self.extra_data is not None:
self.extra_data.serialise_lists(f"{context}_extra_data", bba)
bba.label(f"{context}_extra_data")
self.extra_data.serialise(f"{context}_extra_data", bba)
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.name_prefix.index)
bba.u32(self.type_idx)
bba.u32(self.shape_idx)
if self.extra_data is not None:
bba.ref(f"{context}_extra_data")
else:
bba.u32(0)
@dataclass
class PadInfo(BBAStruct):
# package pin name
package_pin: IdString
# reference to corresponding bel
tile: IdString
bel: IdString
# function name
pad_function: IdString
# index of pin bank
pad_bank: int
# extra pad flags
flags: int
extra_data: object = None
def serialise_lists(self, context: str, bba: BBAWriter):
if self.extra_data is not None:
self.extra_data.serialise_lists(f"{context}_extra_data", bba)
bba.label(f"{context}_extra_data")
self.extra_data.serialise(f"{context}_extra_data", bba)
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.package_pin.index)
bba.u32(self.tile.index)
bba.u32(self.bel.index)
bba.u32(self.pad_function.index)
bba.u32(self.pad_bank)
bba.u32(self.flags)
if self.extra_data is not None:
bba.ref(f"{context}_extra_data")
else:
bba.u32(0)
@dataclass
class PackageInfo(BBAStruct):
strs: StringPool
name: IdString
pads: list[int] = field(default_factory=list)
def create_pad(self, package_pin: str, tile: str, bel: str, pad_function: str, pad_bank: int, flags: int = 0):
pad = PadInfo(package_pin = self.strs.id(package_pin), tile = self.strs.id(tile), bel = self.strs.id(bel),
pad_function = self.strs.id(pad_function), pad_bank = pad_bank, flags = flags)
self.pads.append(pad)
return pad
def serialise_lists(self, context: str, bba: BBAWriter):
for i, pad in enumerate(self.pads):
pad.serialise_lists(f"{context}_pad{i}", bba)
bba.label(f"{context}_pads")
for i, pad in enumerate(self.pads):
pad.serialise(f"{context}_pad{i}", bba)
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.name.index)
bba.slice(f"{context}_pads", len(self.pads))
class Chip:
def __init__(self, uarch: str, name: str, width: int, height: int):
self.strs = StringPool()
self.uarch = uarch
self.name = name
self.width = width
self.height = height
self.tile_types = []
self.tiles = [[TileInst(x, y) for x in range(width)] for y in range(height)]
self.tile_type_idx = dict()
self.node_shapes = []
self.node_shape_idx = dict()
self.tile_shapes = []
self.tile_shapes_idx = dict()
self.packages = []
self.extra_data = None
def create_tile_type(self, name: str):
tt = TileType(self.strs, self.strs.id(name))
self.tile_type_idx[name] = len(self.tile_types)
self.tile_types.append(tt)
return tt
def set_tile_type(self, x: int, y: int, type: str):
self.tiles[y][x].type_idx = self.tile_type_idx[type]
def tile_type_at(self, x: int, y: int):
assert self.tiles[y][x].type_idx is not None, f"tile type at ({x}, {y}) must be set"
return self.tile_types[self.tiles[y][x].type_idx]
def add_node(self, wires: list[NodeWire]):
# add a node - joining between multiple tile wires into a single connection (from nextpnr's point of view)
# all the tile wires must exist, and the tile types must be set, first
x0 = wires[0].x
y0 = wires[0].y
# compute node shape
shape = NodeShape()
for w in wires:
wire_id = w.wire if w.wire is IdString else self.strs.id(w.wire)
shape.wires.append(TileWireRef(
dx=w.x-x0, dy=w.y-y0,
wire=self.tile_type_at(w.x, w.y)._wire2idx[wire_id]
))
# deduplicate node shapes
key = shape.key()
if key in self.node_shape_idx:
shape_idx = self.node_shape_idx[key]
else:
shape_idx = len(self.node_shapes)
self.node_shape_idx[key] = shape_idx
self.node_shapes.append(shape)
# update tile wire to node ref
for i, w in enumerate(wires):
inst = self.tiles[w.y][w.x]
wire_idx = shape.wires[i].wire
# make sure there's actually enough space; first
if wire_idx >= len(inst.shape.wire_to_node):
inst.shape.wire_to_node += [RelNodeRef() for k in range(len(inst.shape.wire_to_node), wire_idx+1)]
if i == 0:
# root of the node. we don't need to back-reference anything because the node is based here
# so we re-use the structure to store the index of the node shape, instead
assert inst.shape.wire_to_node[wire_idx].dx_mode == MODE_TILE_WIRE, "attempting to add wire to multiple nodes!"
inst.shape.wire_to_node[wire_idx] = RelNodeRef(MODE_IS_ROOT, (shape_idx & 0xFFFF), ((shape_idx >> 16) & 0xFFFF))
else:
# back-reference to the root of the node
dx = x0 - w.x
dy = y0 - w.y
assert dx < MODE_TILE_WIRE, "dx range causes overlap with magic values!"
assert inst.shape.wire_to_node[wire_idx].dx_mode == MODE_TILE_WIRE, "attempting to add wire to multiple nodes!"
inst.shape.wire_to_node[wire_idx] = RelNodeRef(dx, dy, shape.wires[0].wire)
def flatten_tile_shapes(self):
for row in self.tiles:
for tile in row:
key = tile.shape.key()
if key in self.tile_shapes_idx:
tile.shape_idx = self.tile_shapes_idx[key]
else:
tile.shape_idx = len(self.tile_shapes)
self.tile_shapes.append(tile.shape)
self.tile_shapes_idx[key] = tile.shape_idx
print(f"{len(self.tile_shapes)} unique tile routing shapes")
def create_package(self, name: str):
pkg = PackageInfo(self.strs, self.strs.id(name))
self.packages.append(pkg)
return pkg
def serialise(self, bba: BBAWriter):
self.flatten_tile_shapes()
# TODO: preface, etc
# Lists that make up the database
for i, tt in enumerate(self.tile_types):
tt.serialise_lists(f"tt{i}", bba)
for i, shp in enumerate(self.node_shapes):
shp.serialise_lists(f"nshp{i}", bba)
for i, tsh in enumerate(self.tile_shapes):
tsh.serialise_lists(f"tshp{i}", bba)
for i, pkg in enumerate(self.packages):
pkg.serialise_lists(f"pkg{i}", bba)
for y, row in enumerate(self.tiles):
for x, tinst in enumerate(row):
tinst.serialise_lists(f"tinst_{x}_{y}", bba)
self.strs.serialise_lists(f"constids", bba)
if self.extra_data is not None:
self.extra_data.serialise_lists("extra_data", bba)
bba.label("extra_data")
self.extra_data.serialise("extra_data", bba)
bba.label(f"tile_types")
for i, tt in enumerate(self.tile_types):
tt.serialise(f"tt{i}", bba)
bba.label(f"node_shapes")
for i, shp in enumerate(self.node_shapes):
shp.serialise(f"nshp{i}", bba)
bba.label(f"tile_shapes")
for i, tsh in enumerate(self.tile_shapes):
tsh.serialise(f"tshp{i}", bba)
bba.label(f"packages")
for i, pkg in enumerate(self.packages):
pkg.serialise(f"pkg{i}", bba)
bba.label(f"tile_insts")
for y, row in enumerate(self.tiles):
for x, tinst in enumerate(row):
tinst.serialise(f"tinst_{x}_{y}", bba)
bba.label(f"constids")
self.strs.serialise(f"constids", bba)
bba.label("chip_info")
bba.u32(0x00ca7ca7) # magic
bba.u32(1) # version (TODO)
bba.u32(self.width)
bba.u32(self.height)
bba.str(self.uarch)
bba.str(self.name)
bba.str("python_dbgen") # generator
bba.slice("tile_types", len(self.tile_types))
bba.slice("tile_insts", self.width*self.height)
bba.slice("node_shapes", len(self.node_shapes))
bba.slice("tile_shapes", len(self.tile_shapes))
# packages
bba.slice("packages", len(self.packages))
# speed grades: not yet used
bba.u32(0)
bba.u32(0)
# db-defined constids
bba.ref("constids")
# extra data
if self.extra_data is not None:
bba.ref("extra_data")
else:
bba.u32(0)
def write_bba(self, filename):
with open(filename, "w") as f:
bba = BBAWriter(f)
bba.pre('#include \"nextpnr.h\"')
bba.pre('NEXTPNR_NAMESPACE_BEGIN')
bba.post('NEXTPNR_NAMESPACE_END')
bba.push('chipdb_blob')
bba.ref('chip_info')
self.serialise(bba)
bba.pop()