Skip to content

Commit

Permalink
himbaechel: Initial timing support
Browse files Browse the repository at this point in the history
Signed-off-by: gatecat <[email protected]>
  • Loading branch information
gatecat committed Sep 8, 2023
1 parent 890d7f7 commit 3e1e783
Show file tree
Hide file tree
Showing 9 changed files with 586 additions and 25 deletions.
2 changes: 1 addition & 1 deletion generic/viaduct/example/synth_viaduct_example.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# tcl synth_viaduct_example.tcl {out.json}

yosys read_verilog -lib [file dirname [file normalize $argv0]]/example_prims.v
yosys hierarchy -check
yosys hierarchy -check -top top
yosys proc
yosys flatten
yosys tribuf -logic
Expand Down
178 changes: 178 additions & 0 deletions himbaechel/arch.cc
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,35 @@ Arch::Arch(ArchArgs args)
IdString::initialize_add(this, chip_info->extra_constids->bba_ids[i].get(),
i + chip_info->extra_constids->known_id_count);
}
// Select speed grade
if (args.speed.empty()) {
if (chip_info->speed_grades.ssize() == 0) {
// no timing information and no speed grade specified
speed_grade = nullptr;
} else if (chip_info->speed_grades.ssize() == 1) {
// speed grade not specified but only one available; use it
speed_grade = &(chip_info->speed_grades[0]);
} else {
std::string available_speeds = "";
for (const auto &speed_data : chip_info->speed_grades) {
if (!available_speeds.empty())
available_speeds += ", ";
available_speeds += IdString(speed_data.name).c_str(this);
}
log_error("Speed grade must be specified using --speed (available options: %s).\n",
available_speeds.c_str());
}
} else {
for (const auto &speed_data : chip_info->speed_grades) {
if (IdString(speed_data.name) == id(args.speed)) {
speed_grade = &speed_data;
break;
}
}
if (!speed_grade) {
log_error("Speed grade '%s' not found in database.\n", args.speed.c_str());
}
}
init_tiles();
}

Expand Down Expand Up @@ -167,6 +196,7 @@ bool Arch::place()

bool Arch::route()
{
set_fast_pip_delays(true);
uarch->preRoute();
std::string router = str_or_default(settings, id("router"), defaultRouter);
bool result;
Expand All @@ -181,6 +211,7 @@ bool Arch::route()
uarch->postRoute();
getCtx()->settings[getCtx()->id("route")] = 1;
archInfoToAttributes();
set_fast_pip_delays(false);
return result;
}

Expand All @@ -190,6 +221,8 @@ void Arch::assignArchInfo()
for (auto &cell : cells) {
CellInfo *ci = cell.second.get();
ci->flat_index = cell_idx++;
if (speed_grade && ci->timing_index == -1)
ci->timing_index = get_cell_timing_idx(ci->type);
for (auto &port : ci->ports) {
// Default 1:1 cell:bel mapping
if (!ci->cell_bel_pins.count(port.first))
Expand Down Expand Up @@ -259,4 +292,149 @@ const std::vector<std::string> Arch::availablePlacers = {"sa", "heap"};
const std::string Arch::defaultRouter = "router1";
const std::vector<std::string> Arch::availableRouters = {"router1", "router2"};

void Arch::set_fast_pip_delays(bool fast_mode)
{
if (fast_mode && !fast_pip_delays) {
// Have to rebuild these structures
drive_res.clear();
load_cap.clear();
for (auto &net : nets) {
for (auto &wire_pair : net.second->wires) {
PipId pip = wire_pair.second.pip;
if (pip == PipId())
continue;
auto &pip_data = chip_pip_info(chip_info, pip);
auto pip_tmg = get_pip_timing(pip_data);
if (pip_tmg != nullptr) {
WireId src = getPipSrcWire(pip), dst = getPipDstWire(pip);
load_cap[src] += pip_tmg->in_cap.slow_max;
drive_res[dst] = (((pip_tmg->flags & 1) || !drive_res.count(src)) ? 0 : drive_res.at(src)) +
pip_tmg->out_res.slow_max;
}
}
}
}
fast_pip_delays = fast_mode;
}

// Helper for cell timing lookups
namespace {
template <typename Tres, typename Tgetter, typename Tkey>
int db_binary_search(const RelSlice<Tres> &list, Tgetter key_getter, Tkey key)
{
if (list.ssize() < 7) {
for (int i = 0; i < list.ssize(); i++) {
if (key_getter(list[i]) == key) {
return i;
}
}
} else {
int b = 0, e = list.ssize() - 1;
while (b <= e) {
int i = (b + e) / 2;
if (key_getter(list[i]) == key) {
return i;
}
if (key_getter(list[i]) > key)
e = i - 1;
else
b = i + 1;
}
}
return -1;
}
} // namespace

int Arch::get_cell_timing_idx(IdString type_variant) const
{
return db_binary_search(
speed_grade->cell_types, [](const CellTimingPOD &ct) { return ct.type_variant; }, type_variant.index);
}

bool Arch::lookup_cell_delay(int type_idx, IdString from_port, IdString to_port, DelayQuad &delay) const
{
NPNR_ASSERT(type_idx != -1);
const auto &ct = speed_grade->cell_types[type_idx];
int to_pin_idx = db_binary_search(
ct.pins, [](const CellPinTimingPOD &pd) { return pd.pin; }, to_port.index);
if (to_pin_idx == -1)
return false;
const auto &tp = ct.pins[to_pin_idx];
int arc_idx = db_binary_search(
tp.comb_arcs, [](const CellPinCombArcPOD &arc) { return arc.input; }, from_port.index);
if (arc_idx == -1)
return false;
delay = DelayQuad(tp.comb_arcs[arc_idx].delay.fast_min, tp.comb_arcs[arc_idx].delay.slow_max);
return true;
}

const RelSlice<CellPinRegArcPOD> *Arch::lookup_cell_seq_timings(int type_idx, IdString port) const
{
NPNR_ASSERT(type_idx != -1);
const auto &ct = speed_grade->cell_types[type_idx];
int pin_idx = db_binary_search(
ct.pins, [](const CellPinTimingPOD &pd) { return pd.pin; }, port.index);
if (pin_idx == -1)
return nullptr;
return &ct.pins[pin_idx].reg_arcs;
}

TimingPortClass Arch::lookup_port_tmg_type(int type_idx, IdString port, PortType dir) const
{

NPNR_ASSERT(type_idx != -1);
const auto &ct = speed_grade->cell_types[type_idx];
int pin_idx = db_binary_search(
ct.pins, [](const CellPinTimingPOD &pd) { return pd.pin; }, port.index);
if (pin_idx == -1)
return (dir == PORT_OUT) ? TMG_IGNORE : TMG_COMB_INPUT;
auto &pin = ct.pins[pin_idx];

if (dir == PORT_IN) {
if (pin.flags & CellPinTimingPOD::FLAG_CLK)
return TMG_CLOCK_INPUT;
return pin.reg_arcs.ssize() > 0 ? TMG_REGISTER_INPUT : TMG_COMB_INPUT;
} else {
// If a clock-to-out entry exists, then this is a register output
return pin.reg_arcs.ssize() > 0 ? TMG_REGISTER_OUTPUT : TMG_COMB_OUTPUT;
}
}

// TODO: adding uarch overrides for these?
bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayQuad &delay) const
{
if (cell->timing_index == -1)
return false;
return lookup_cell_delay(cell->timing_index, fromPort, toPort, delay);
}
TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const
{
if (cell->timing_index == -1)
return TMG_IGNORE;
auto type = lookup_port_tmg_type(cell->timing_index, port, cell->ports.at(port).type);
clockInfoCount = 0;
if (type == TMG_REGISTER_INPUT || type == TMG_REGISTER_OUTPUT) {
auto reg_arcs = lookup_cell_seq_timings(cell->timing_index, port);
if (reg_arcs)
clockInfoCount = reg_arcs->ssize();
}
return type;
}
TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port, int index) const
{
TimingClockingInfo result;
NPNR_ASSERT(cell->timing_index != -1);
auto reg_arcs = lookup_cell_seq_timings(cell->timing_index, port);
NPNR_ASSERT(reg_arcs);
const auto &arc = (*reg_arcs)[index];

result.clock_port = IdString(arc.clock);
result.edge = ClockEdge(arc.edge);
result.setup = DelayPair(arc.setup.fast_min, arc.setup.slow_max);
result.hold = DelayPair(arc.hold.fast_min, arc.hold.slow_max);
result.clockToQ = DelayQuad(arc.clk_q.fast_min, arc.clk_q.slow_max);

return result;
}

NEXTPNR_NAMESPACE_END
97 changes: 96 additions & 1 deletion himbaechel/arch.h
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ struct ArchArgs
std::string uarch;
std::string chipdb;
std::string device;
std::string speed;
dict<std::string, std::string> options;
};

Expand Down Expand Up @@ -426,6 +427,7 @@ struct Arch : BaseArch<ArchRanges>
boost::iostreams::mapped_file_source blob_file;
const ChipInfoPOD *chip_info;
const PackageInfoPOD *package_info = nullptr;
const SpeedGradePOD *speed_grade = nullptr;

// Unlike Viaduct, we are not -generic based and therefore uarch must be non-nullptr
std::unique_ptr<HimbaechelAPI> uarch;
Expand Down Expand Up @@ -522,7 +524,27 @@ struct Arch : BaseArch<ArchRanges>
{
return normalise_wire(pip.tile, chip_pip_info(chip_info, pip).dst_wire);
}
DelayQuad getPipDelay(PipId pip) const override { return DelayQuad(100); }
DelayQuad getPipDelay(PipId pip) const override
{
auto &pip_data = chip_pip_info(chip_info, pip);
auto pip_tmg = get_pip_timing(pip_data);
if (pip_tmg != nullptr) {
// TODO: multi corner analysis
WireId src = getPipSrcWire(pip);
uint64_t input_res = fast_pip_delays ? 0 : (drive_res.count(src) ? drive_res.at(src) : 0);
uint64_t input_cap = fast_pip_delays ? 0 : (load_cap.count(src) ? load_cap.at(src) : 0);
auto src_tmg = get_node_timing(src);
if (src_tmg != nullptr)
input_res += (src_tmg->res.slow_max / 2);
// Scale delay (fF * mOhm -> ps)
delay_t total_delay = (input_res * input_cap) / uint64_t(1e6);
total_delay += pip_tmg->int_delay.slow_max;
return DelayQuad(total_delay);
} else {
// Pip with no specified delay. Return a notional value so the router still has something to work with.
return DelayQuad(100);
}
}
DownhillPipRange getPipsDownhill(WireId wire) const override
{
return DownhillPipRange(chip_info, get_tile_wire_range(wire));
Expand All @@ -547,11 +569,29 @@ struct Arch : BaseArch<ArchRanges>
}
void bindPip(PipId pip, NetInfo *net, PlaceStrength strength) override
{
if (!fast_pip_delays) {
auto &pip_data = chip_pip_info(chip_info, pip);
auto pip_tmg = get_pip_timing(pip_data);
if (pip_tmg != nullptr) {
WireId src = getPipSrcWire(pip);
load_cap[src] += pip_tmg->in_cap.slow_max;
drive_res[getPipDstWire(pip)] =
(((pip_tmg->flags & 1) || !drive_res.count(src)) ? 0 : drive_res.at(src)) +
pip_tmg->out_res.slow_max;
}
}
uarch->notifyPipChange(pip, net);
BaseArch::bindPip(pip, net, strength);
}
void unbindPip(PipId pip) override
{
if (!fast_pip_delays) {
auto &pip_data = chip_pip_info(chip_info, pip);
auto pip_tmg = get_pip_timing(pip_data);
if (pip_tmg != nullptr) {
load_cap[getPipSrcWire(pip)] -= pip_tmg->in_cap.slow_max;
}
}
uarch->notifyPipChange(pip, nullptr);
BaseArch::unbindPip(pip);
}
Expand Down Expand Up @@ -667,10 +707,65 @@ struct Arch : BaseArch<ArchRanges>
}
}

// -------------------------------------------------

const PipTimingPOD *get_pip_timing(const PipDataPOD &pip_data) const
{
int32_t idx = pip_data.timing_idx;
if (speed_grade && idx >= 0 && idx < speed_grade->pip_classes.ssize())
return &(speed_grade->pip_classes[idx]);
else
return nullptr;
}

const NodeTimingPOD *get_node_timing(WireId wire) const
{
int idx = -1;
if (!speed_grade)
return nullptr;
if (is_nodal_wire(chip_info, wire.tile, wire.index)) {
auto &shape = chip_node_shape(chip_info, wire.tile, wire.index);
idx = shape.timing_idx;
} else {
auto &wire_data = chip_wire_info(chip_info, wire);
idx = wire_data.timing_idx;
}
if (idx >= 0 && idx < speed_grade->node_classes.ssize())
return &(speed_grade->node_classes[idx]);
else
return nullptr;
}

// -------------------------------------------------

// Given cell type and variant, get the index inside the speed grade timing data
int get_cell_timing_idx(IdString type_variant) const;
// Return true and set delay if a comb path exists in a given cell timing index
bool lookup_cell_delay(int type_idx, IdString from_port, IdString to_port, DelayQuad &delay) const;
// Get setup and hold time and associated clock for a given cell timing index and signal
const RelSlice<CellPinRegArcPOD> *lookup_cell_seq_timings(int type_idx, IdString port) const;
// Attempt to look up port type based on timing database
TimingPortClass lookup_port_tmg_type(int type_idx, IdString port, PortType dir) const;

// -------------------------------------------------

bool getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort, DelayQuad &delay) const override;
// Get the port class, also setting clockInfoCount to the number of TimingClockingInfos associated with a port
TimingPortClass getPortTimingClass(const CellInfo *cell, IdString port, int &clockInfoCount) const override;
// Get the TimingClockingInfo of a port
TimingClockingInfo getPortClockingInfo(const CellInfo *cell, IdString port, int index) const override;

// -------------------------------------------------
void init_tiles();
void set_fast_pip_delays(bool fast_mode);
std::vector<IdString> tile_name;
dict<IdString, int> tile_name2idx;

// Load capacitance and drive resistance for nodes
// TODO: does this `dict` hurt routing performance too much?
bool fast_pip_delays = false;
dict<WireId, uint64_t> drive_res;
dict<WireId, uint64_t> load_cap;
};

NEXTPNR_NAMESPACE_END
Expand Down
1 change: 1 addition & 0 deletions himbaechel/archdefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ struct ArchNetInfo
struct ArchCellInfo : BaseClusterInfo
{
int flat_index;
int timing_index = -1;
dict<IdString, std::vector<IdString>> cell_bel_pins;
};

Expand Down
Loading

0 comments on commit 3e1e783

Please sign in to comment.