nextpnr/himbaechel/himbaechel_dbgen/chip.py
gatecat cf647463e4 himbaechel: Add support for new constants API
Signed-off-by: gatecat <gatecat@ds0.me>
2023-11-07 09:00:03 +01:00

849 lines
32 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, 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:
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.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
const_value: IdString = field(default_factory=list)
flags: int = 0
timing_idx: 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.const_value.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
extra_data: object = None
def serialise_lists(self, context: str, bba: BBAWriter):
# extra data (optional)
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.src_wire)
bba.u32(self.dst_wire)
bba.u32(self.pip_type.index)
bba.u32(self.flags)
bba.u32(self.timing_idx)
if self.extra_data is not None:
bba.ref(f"{context}_extra_data")
else:
bba.u32(0)
@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="", const_value: 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),
const_value=self.strs.id(const_value))
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:
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.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
@dataclass
class NodeShape(BBAStruct):
wires: list[int] = field(default_factory=list)
timing_index: int = -1
def key(self):
m = hashlib.md5()
m.update(struct.pack("h"*len(self.wires), *self.wires))
return m.digest()
def serialise_lists(self, context: str, bba: BBAWriter):
bba.label(f"{context}_wires")
for w in self.wires:
bba.u16(w)
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)//3)
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[int] = field(default_factory=list)
def key(self):
m = hashlib.md5()
m.update(struct.pack("h"*len(self.wire_to_node), *self.wire_to_node))
return m.digest()
def serialise_lists(self, context: str, bba: BBAWriter):
bba.label(f"{context}_w2n")
for x in self.wire_to_node:
bba.u16(x)
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)//3)
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]
return self.tiles[y][x]
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:
if isinstance(w.wire, int):
wire_index = w.wire
else:
wire_id = w.wire if w.wire is IdString else self.strs.id(w.wire)
wire_index = self.tile_type_at(w.x, w.y)._wire2idx[wire_id]
shape.wires += [w.x-x0, w.y-y0, wire_index]
# 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*3+2]
# make sure there's actually enough space; first
while 3*wire_idx >= len(inst.shape.wire_to_node):
inst.shape.wire_to_node += [MODE_TILE_WIRE, 0, 0]
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[3*wire_idx+0] == MODE_TILE_WIRE, "attempting to add wire to multiple nodes!"
inst.shape.wire_to_node[3*wire_idx+0] = MODE_IS_ROOT
inst.shape.wire_to_node[3*wire_idx+1] = (shape_idx & 0xFFFF)
inst.shape.wire_to_node[3*wire_idx+2] = ((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[3*wire_idx+0] == MODE_TILE_WIRE, "attempting to add wire to multiple nodes!"
inst.shape.wire_to_node[3*wire_idx+0] = dx
inst.shape.wire_to_node[3*wire_idx+1] = dy
inst.shape.wire_to_node[3*wire_idx+2] = shape.wires[0*3+2]
def flatten_tile_shapes(self):
print("Deduplicating tile shapes...")
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(3) # version
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()