nextpnr/himbaechel/himbaechel_dbgen/chip.py

850 lines
32 KiB
Python
Raw Normal View History

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, order=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
timing_idx: int = -1
# 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.u32(self.timing_idx)
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
tmg: "TimingPool"
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, timing_class: 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,
timing_idx=self.tmg.pip_class_idx(timing_class))
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)
timing_index: int = -1
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(self.timing_index) # 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 TimingValue(BBAStruct):
def __init__(self, fast_min=0, fast_max=None, slow_min=None, slow_max=None):
self.fast_min = fast_min
self.fast_max = fast_max or fast_min
self.slow_min = slow_min or self.fast_min
self.slow_max = slow_max or self.fast_max
def serialise_lists(self, context: str, bba: BBAWriter):
pass
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.fast_min)
bba.u32(self.fast_max)
bba.u32(self.slow_min)
bba.u32(self.slow_max)
@dataclass
class PipTiming(BBAStruct):
int_delay: TimingValue = field(default_factory=TimingValue) # internal fixed delay in ps
in_cap: TimingValue = field(default_factory=TimingValue) # internal capacitance in notional femtofarads
out_res: TimingValue = field(default_factory=TimingValue) # drive/output resistance in notional milliohms
flags: int = 0 # is_buffered etc
def serialise_lists(self, context: str, bba: BBAWriter):
pass
def serialise(self, context: str, bba: BBAWriter):
self.int_delay.serialise(context, bba)
self.in_cap.serialise(context, bba)
self.out_res.serialise(context, bba)
bba.u32(self.flags)
@dataclass
class NodeTiming(BBAStruct):
res: TimingValue = field(default_factory=TimingValue) # wire resistance in notional milliohms
cap: TimingValue = field(default_factory=TimingValue) # wire capacitance in notional femtofarads
delay: TimingValue = field(default_factory=TimingValue) # fixed wire delay in ps
def serialise_lists(self, context: str, bba: BBAWriter):
pass
def serialise(self, context: str, bba: BBAWriter):
self.res.serialise(context, bba)
self.cap.serialise(context, bba)
self.delay.serialise(context, bba)
@dataclass
class ClockEdge(Enum):
RISING = 0
FALLING = 1
@dataclass
class CellPinRegArc(BBAStruct):
clock: int
edge: ClockEdge
setup: TimingValue = field(default_factory=TimingValue) # setup time in ps
hold: TimingValue = field(default_factory=TimingValue) # hold time in ps
clk_q: TimingValue = field(default_factory=TimingValue) # clock to output time in ps
def serialise_lists(self, context: str, bba: BBAWriter):
pass
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.clock.index)
bba.u32(self.edge.value)
self.setup.serialise(context, bba)
self.hold.serialise(context, bba)
self.clk_q.serialise(context, bba)
@dataclass
class CellPinCombArc(BBAStruct):
from_pin: int
delay: TimingValue = field(default_factory=TimingValue)
def serialise_lists(self, context: str, bba: BBAWriter):
pass
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.from_pin.index)
self.delay.serialise(context, bba)
@dataclass
class CellPinTiming(BBAStruct):
pin: int
flags: int = 0
comb_arcs: list[CellPinCombArc] = field(default_factory=list) # sorted by from_pin ID index
reg_arcs: list[CellPinRegArc] = field(default_factory=list) # sorted by clock ID index
def set_clock(self):
self.flags |= 1
def finalise(self):
self.comb_arcs.sort(key=lambda a: a.from_pin)
self.reg_arcs.sort(key=lambda a: a.clock)
def serialise_lists(self, context: str, bba: BBAWriter):
bba.label(f"{context}_comb")
for i, a in enumerate(self.comb_arcs):
a.serialise(f"{context}_comb{i}", bba)
bba.label(f"{context}_reg")
for i, a in enumerate(self.reg_arcs):
a.serialise(f"{context}_reg{i}", bba)
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.pin.index) # pin idstring
bba.u32(self.flags)
bba.slice(f"{context}_comb", len(self.comb_arcs))
bba.slice(f"{context}_reg", len(self.reg_arcs))
class CellTiming(BBAStruct):
def __init__(self, strs: StringPool, type_variant: str):
self.strs = strs
self.type_variant = strs.id(type_variant)
self.pin_data = {}
# combinational timing through a cell (like a LUT delay)
def add_comb_arc(self, from_pin: str, to_pin: str, delay: TimingValue):
if to_pin not in self.pin_data:
self.pin_data[to_pin] = CellPinTiming(pin=self.strs.id(to_pin))
self.pin_data[to_pin].comb_arcs.append(CellPinCombArc(from_pin=self.strs.id(from_pin), delay=delay))
# register input style timing (like a DFF input)
def add_setup_hold(self, clock: str, input_pin: str, edge: ClockEdge, setup: TimingValue, hold: TimingValue):
if input_pin not in self.pin_data:
self.pin_data[input_pin] = CellPinTiming(pin=self.strs.id(input_pin))
if clock not in self.pin_data:
self.pin_data[clock] = CellPinTiming(pin=self.strs.id(clock))
self.pin_data[input_pin].reg_arcs.append(CellPinRegArc(clock=self.strs.id(clock), edge=edge, setup=setup, hold=hold))
self.pin_data[clock].set_clock()
# register output style timing (like a DFF output)
def add_clock_out(self, clock: str, output_pin: str, edge: ClockEdge, delay: TimingValue):
if output_pin not in self.pin_data:
self.pin_data[output_pin] = CellPinTiming(pin=self.strs.id(output_pin))
if clock not in self.pin_data:
self.pin_data[clock] = CellPinTiming(pin=self.strs.id(clock))
self.pin_data[output_pin].reg_arcs.append(CellPinRegArc(clock=self.strs.id(clock), edge=edge, clk_q=delay))
self.pin_data[clock].set_clock()
def finalise(self):
self.pins = list(self.pin_data.values())
self.pins.sort(key=lambda p: p.pin)
for pin in self.pins:
pin.finalise()
def serialise_lists(self, context: str, bba: BBAWriter):
for i, p in enumerate(self.pins):
p.serialise_lists(f"{context}_pin{i}", bba)
bba.label(f"{context}_pins")
for i, p in enumerate(self.pins):
p.serialise(f"{context}_pin{i}", bba)
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.type_variant.index) # type idstring
bba.slice(f"{context}_pins", len(self.pins))
@dataclass
class SpeedGrade(BBAStruct):
name: int
pip_classes: list[Optional[PipTiming]] = field(default_factory=list)
node_classes: list[Optional[NodeTiming]] = field(default_factory=list)
cell_types: list[CellTiming] = field(default_factory=list) # sorted by (cell_type, variant) ID tuple
def finalise(self):
self.cell_types.sort(key=lambda ty: ty.type_variant)
for ty in self.cell_types:
ty.finalise()
def serialise_lists(self, context: str, bba: BBAWriter):
for i, t in enumerate(self.cell_types):
t.serialise_lists(f"{context}_cellty{i}", bba)
bba.label(f"{context}_pip_classes")
for i, p in enumerate(self.pip_classes):
p.serialise(f"{context}_pipc{i}", bba)
bba.label(f"{context}_node_classes")
for i, n in enumerate(self.node_classes):
n.serialise(f"{context}_nodec{i}", bba)
bba.label(f"{context}_cell_types")
for i, t in enumerate(self.cell_types):
t.serialise(f"{context}_cellty{i}", bba)
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.name.index) # speed grade idstring
bba.slice(f"{context}_pip_classes", len(self.pip_classes))
bba.slice(f"{context}_node_classes", len(self.node_classes))
bba.slice(f"{context}_cell_types", len(self.cell_types))
class TimingPool(BBAStruct):
def __init__(self, strs: StringPool):
self.strs = strs
self.speed_grades = []
self.speed_grade_idx = {}
self.pip_classes = {}
self.node_classes = {}
def set_speed_grades(self, speed_grades: list):
assert len(self.speed_grades) == 0
self.speed_grades = [SpeedGrade(name=self.strs.id(g)) for g in speed_grades]
self.speed_grade_idx = {g: i for i, g in enumerate(speed_grades)}
def pip_class_idx(self, name: str):
if name == "":
return -1
elif name in self.pip_classes:
return self.pip_classes[name]
else:
idx = len(self.pip_classes)
self.pip_classes[name] = idx
return idx
def node_class_idx(self, name: str):
if name == "":
return -1
elif name in self.node_classes:
return self.node_classes[name]
else:
idx = len(self.node_classes)
self.node_classes[name] = idx
return idx
def set_pip_class(self, grade: str, name: str, delay: TimingValue,
in_cap: Optional[TimingValue]=None, out_res: Optional[TimingValue]=None,
is_buffered=True):
idx = self.pip_class_idx(name)
sg = self.speed_grades[self.speed_grade_idx[grade]]
if idx >= len(sg.pip_classes):
sg.pip_classes += [None for i in range(1 + idx - len(sg.pip_classes))]
assert sg.pip_classes[idx] is None, f"attempting to set pip class {name} in speed grade {grade} twice"
sg.pip_classes[idx] = PipTiming(int_delay=delay, in_cap=in_cap, out_res=out_res, flags=(1 if is_buffered else 0))
def set_bel_pin_class(self, grade: str, name: str, delay: TimingValue,
in_cap: Optional[TimingValue]=None, out_res: Optional[TimingValue]=None):
# bel pin classes are shared with pip classes, but this alias adds a bit of extra clarity
set_pip_class(self, grade, name, delay, in_cap, out_res, is_buffered=True)
def set_node_class(self, grade: str, name: str, delay: TimingValue,
res: Optional[TimingValue]=None, cap: Optional[TimingValue]=None):
idx = self.node_class_idx(name)
sg = self.speed_grades[self.speed_grade_idx[grade]]
if idx >= len(sg.node_classes):
sg.node_classes += [None for i in range(1 + idx - len(sg.node_classes))]
assert sg.node_classes[idx] is None, f"attempting to set node class {name} in speed grade {grade} twice"
sg.node_classes[idx] = NodeTiming(delay=delay, res=res, cap=cap)
def add_cell_variant(self, speed_grade: str, name: str):
cell = CellTiming(self.strs, name)
self.speed_grades[self.speed_grade_idx[speed_grade]].cell_types.append(cell)
return cell
def finalise(self):
for sg in self.speed_grades:
sg.finalise()
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
self.timing = TimingPool(self.strs)
def create_tile_type(self, name: str):
tt = TileType(self.strs, self.timing, 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 set_speed_grades(self, speed_grades: list):
self.timing.set_speed_grades(speed_grades)
return self.timing
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)
for i, sg in enumerate(self.timing.speed_grades):
sg.serialise_lists(f"sg{i}", 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"speed_grades")
for i, sg in enumerate(self.timing.speed_grades):
sg.serialise(f"sg{i}", 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.slice("speed_grades", len(self.timing.speed_grades))
# 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):
self.timing.finalise()
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()