Compare commits

...

3 Commits

Author SHA1 Message Date
Lofty
83ad4a9f17 mistral: fix horrific wire hashing performance 2023-09-02 11:30:35 +01:00
Lofty
1f8e8948ea mistral: update mistral to ef6263f 2023-09-02 11:01:56 +01:00
Lofty
5b99b99859 mistral: update to mistral dc82215 2023-09-02 10:12:54 +01:00
8 changed files with 76 additions and 76 deletions

View File

@ -43,10 +43,10 @@ void IdString::initialize_arch(const BaseCtx *ctx)
#undef X
}
CycloneV::rnode_t Arch::find_rnode(CycloneV::block_type_t bt, int x, int y, CycloneV::port_type_t port, int bi,
CycloneV::rnode_coords Arch::find_rnode(CycloneV::block_type_t bt, int x, int y, CycloneV::port_type_t port, int bi,
int pi) const
{
auto pn1 = CycloneV::pnode(bt, x, y, port, bi, pi);
auto pn1 = CycloneV::pnode_coords{bt, x, y, port, bi, pi};
auto rn1 = cyclonev->pnode_to_rnode(pn1);
if (rn1)
return rn1;
@ -63,7 +63,7 @@ CycloneV::rnode_t Arch::find_rnode(CycloneV::block_type_t bt, int x, int y, Cycl
return rn2;
}
return 0;
return CycloneV::rnode_coords{};
}
WireId Arch::get_port(CycloneV::block_type_t bt, int x, int y, int bi, CycloneV::port_type_t port, int pi) const
@ -72,7 +72,7 @@ WireId Arch::get_port(CycloneV::block_type_t bt, int x, int y, int bi, CycloneV:
if (rn)
return WireId(rn);
log_error("Trying to connect unknown node %s\n", CycloneV::pn2s(CycloneV::pnode(bt, x, y, port, bi, pi)).c_str());
log_error("Trying to connect unknown node %s\n", CycloneV::pnode_coords{bt, x, y, port, bi, pi}.to_string().c_str());
}
bool Arch::has_port(CycloneV::block_type_t bt, int x, int y, int bi, CycloneV::port_type_t port, int pi) const
@ -103,33 +103,33 @@ Arch::Arch(ArchArgs args)
bels_by_tile.resize(cyclonev->get_tile_sx() * cyclonev->get_tile_sy());
for (auto lab_pos : cyclonev->lab_get_pos())
create_lab(CycloneV::pos2x(lab_pos), CycloneV::pos2y(lab_pos), /*is_mlab=*/false);
create_lab(lab_pos.x(), lab_pos.y(), /*is_mlab=*/false);
for (auto mlab_pos : cyclonev->mlab_get_pos())
create_lab(CycloneV::pos2x(mlab_pos), CycloneV::pos2y(mlab_pos), /*is_mlab=*/true);
create_lab(mlab_pos.x(), mlab_pos.y(), /*is_mlab=*/true);
for (auto gpio_pos : cyclonev->gpio_get_pos())
create_gpio(CycloneV::pos2x(gpio_pos), CycloneV::pos2y(gpio_pos));
create_gpio(gpio_pos.x(), gpio_pos.y());
for (auto cmuxh_pos : cyclonev->cmuxh_get_pos())
create_clkbuf(CycloneV::pos2x(cmuxh_pos), CycloneV::pos2y(cmuxh_pos));
create_clkbuf(cmuxh_pos.x(), cmuxh_pos.y());
create_control(CycloneV::pos2x(cyclonev->ctrl_get_pos()[0]), CycloneV::pos2y(cyclonev->ctrl_get_pos()[0]));
create_control(cyclonev->ctrl_get_pos()[0].x(), cyclonev->ctrl_get_pos()[0].y());
auto hps_pos = cyclonev->hps_get_pos();
if (!hps_pos.empty()) {
create_hps_mpu_general_purpose(CycloneV::pos2x(hps_pos[CycloneV::I_HPS_MPU_GENERAL_PURPOSE]),
CycloneV::pos2y(hps_pos[CycloneV::I_HPS_MPU_GENERAL_PURPOSE]));
create_hps_mpu_general_purpose(hps_pos[CycloneV::I_HPS_MPU_GENERAL_PURPOSE].x(),
hps_pos[CycloneV::I_HPS_MPU_GENERAL_PURPOSE].y());
}
for (auto m10k_pos : cyclonev->m10k_get_pos())
create_m10k(CycloneV::pos2x(m10k_pos), CycloneV::pos2y(m10k_pos));
create_m10k(m10k_pos.x(), m10k_pos.y());
// This import takes about 5s, perhaps long term we can speed it up, e.g. defer to Mistral more...
log_info("Initialising routing graph...\n");
int pip_count = 0;
for (const auto &rnode : cyclonev->rnodes()) {
WireId dst_wire(rnode.id());
WireId dst_wire(rnode.rc());
for (const auto &src : rnode.sources()) {
WireId src_wire(src);
wires[dst_wire].wires_uphill.push_back(src_wire);
@ -158,7 +158,7 @@ BelId Arch::getBelByName(IdStringList name) const
int y = id2int.at(name[2]);
int z = id2int.at(name[3]);
bel.pos = CycloneV::xy2pos(x, y);
bel.pos = CycloneV::xycoords{x, y};
bel.z = z;
NPNR_ASSERT(name[0] == getBelType(bel));
@ -168,8 +168,8 @@ BelId Arch::getBelByName(IdStringList name) const
IdStringList Arch::getBelName(BelId bel) const
{
int x = CycloneV::pos2x(bel.pos);
int y = CycloneV::pos2y(bel.pos);
int x = bel.pos.x();
int y = bel.pos.y();
int z = bel.z & 0xFF;
std::array<IdString, 4> ids{
@ -215,7 +215,7 @@ WireId Arch::getWireByName(IdStringList name) const
int x = id2int.at(name[1]);
int y = id2int.at(name[2]);
int z = id2int.at(name[3]);
return WireId(CycloneV::rnode(ty, x, y, z));
return WireId(CycloneV::rnode_coords{ty, x, y, z});
}
IdStringList Arch::getWireName(WireId wire) const
@ -224,17 +224,17 @@ IdStringList Arch::getWireName(WireId wire) const
// non-mistral wires
std::array<IdString, 4> ids{
id_WIRE,
int2id.at(CycloneV::rn2x(wire.node)),
int2id.at(CycloneV::rn2y(wire.node)),
int2id.at(wire.node.x()),
int2id.at(wire.node.y()),
wires.at(wire).name_override,
};
return IdStringList(ids);
} else {
std::array<IdString, 4> ids{
rn_t2id.at(CycloneV::rn2t(wire.node)),
int2id.at(CycloneV::rn2x(wire.node)),
int2id.at(CycloneV::rn2y(wire.node)),
int2id.at(CycloneV::rn2z(wire.node)),
rn_t2id.at(wire.node.t()),
int2id.at(wire.node.x()),
int2id.at(wire.node.y()),
int2id.at(wire.node.z()),
};
return IdStringList(ids);
}
@ -260,7 +260,7 @@ std::vector<BelId> Arch::getBelsByTile(int x, int y) const
std::vector<BelId> bels;
if (x >= 0 && x < cyclonev->get_tile_sx() && y >= 0 && y < cyclonev->get_tile_sy()) {
for (size_t i = 0; i < bels_by_tile.at(pos2idx(x, y)).size(); i++)
bels.push_back(BelId(CycloneV::xy2pos(x, y), i));
bels.push_back(BelId(CycloneV::xycoords{x, y}, i));
}
return bels;
@ -320,7 +320,7 @@ BelId Arch::bel_by_block_idx(int x, int y, IdString type, int block_index) const
for (size_t i = 0; i < bels.size(); i++) {
auto &bel_data = bels.at(i);
if (bel_data.type == type && bel_data.block_index == block_index)
return BelId(CycloneV::xy2pos(x, y), i);
return BelId(CycloneV::xycoords{x, y}, i);
}
return BelId();
}
@ -328,7 +328,7 @@ BelId Arch::bel_by_block_idx(int x, int y, IdString type, int block_index) const
BelId Arch::add_bel(int x, int y, IdString name, IdString type)
{
auto &bels = bels_by_tile.at(pos2idx(x, y));
BelId id = BelId(CycloneV::xy2pos(x, y), bels.size());
BelId id = BelId(CycloneV::xycoords{x, y}, bels.size());
all_bels.push_back(id);
bels.emplace_back();
auto &bel = bels.back();
@ -356,7 +356,7 @@ WireId Arch::add_wire(int x, int y, IdString name, uint64_t flags)
// Determine a unique ID for the wire
int z = 0;
WireId id;
while (wires.count(id = WireId(CycloneV::rnode(CycloneV::rnode_type_t((z >> 10) + 128), x, y, (z & 0x3FF)))))
while (wires.count(id = WireId(CycloneV::rnode_coords{CycloneV::rnode_type_t((z >> 10) + 128), x, y, (z & 0x3FF)})))
z++;
wires[id].name_override = name;
wires[id].flags = flags;
@ -438,10 +438,10 @@ void Arch::assignArchInfo()
BoundingBox Arch::getRouteBoundingBox(WireId src, WireId dst) const
{
BoundingBox bounds;
int src_x = CycloneV::rn2x(src.node);
int src_y = CycloneV::rn2y(src.node);
int dst_x = CycloneV::rn2x(dst.node);
int dst_y = CycloneV::rn2y(dst.node);
int src_x = src.node.x();
int src_y = src.node.y();
int dst_x = dst.node.x();
int dst_y = dst.node.y();
bounds.x0 = std::min(src_x, dst_x);
bounds.y0 = std::min(src_y, dst_y);
bounds.x1 = std::max(src_x, dst_x);

View File

@ -311,7 +311,7 @@ struct Arch : BaseArch<ArchRanges>
std::vector<BelId> getBelsByTile(int x, int y) const override;
Loc getBelLocation(BelId bel) const override
{
return Loc(CycloneV::pos2x(bel.pos), CycloneV::pos2y(bel.pos), bel.z);
return Loc(bel.pos.x(), bel.pos.y(), bel.z);
}
BelId getBelByLocation(Loc loc) const override
{
@ -322,7 +322,7 @@ struct Arch : BaseArch<ArchRanges>
auto &bels = bels_by_tile.at(pos2idx(loc.x, loc.y));
if (loc.z < 0 || loc.z >= int(bels.size()))
return BelId();
return BelId(CycloneV::xy2pos(loc.x, loc.y), loc.z);
return BelId(CycloneV::xycoords(loc.x, loc.y), loc.z);
}
IdString getBelType(BelId bel) const override; // arch.cc
WireId getBelPinWire(BelId bel, IdString pin) const override
@ -380,7 +380,7 @@ struct Arch : BaseArch<ArchRanges>
PipId getPipByName(IdStringList name) const override;
AllPipRange getPips() const override { return AllPipRange(wires); }
Loc getPipLocation(PipId pip) const override { return Loc(CycloneV::rn2x(pip.dst), CycloneV::rn2y(pip.dst), 0); }
Loc getPipLocation(PipId pip) const override { return Loc(pip.dst.x(), pip.dst.y(), 0); }
IdStringList getPipName(PipId pip) const override;
WireId getPipSrcWire(PipId pip) const override { return WireId(pip.src); };
WireId getPipDstWire(PipId pip) const override { return WireId(pip.dst); };
@ -466,7 +466,7 @@ struct Arch : BaseArch<ArchRanges>
void add_bel_pin(BelId bel, IdString pin, PortType dir, WireId wire);
CycloneV::rnode_t find_rnode(CycloneV::block_type_t bt, int x, int y, CycloneV::port_type_t port, int bi = -1,
CycloneV::rnode_coords find_rnode(CycloneV::block_type_t bt, int x, int y, CycloneV::port_type_t port, int bi = -1,
int pi = -1) const;
WireId get_port(CycloneV::block_type_t bt, int x, int y, int bi, CycloneV::port_type_t port, int pi = -1) const;
bool has_port(CycloneV::block_type_t bt, int x, int y, int bi, CycloneV::port_type_t port, int pi = -1) const;
@ -545,7 +545,7 @@ struct Arch : BaseArch<ArchRanges>
return y * cyclonev->get_tile_sx() + x;
}
size_t pos2idx(CycloneV::pos_t pos) const { return pos2idx(CycloneV::pos2x(pos), CycloneV::pos2y(pos)); }
size_t pos2idx(CycloneV::xycoords pos) const { return pos2idx(pos.x(), pos.y()); }
BelInfo &bel_data(BelId bel) { return bels_by_tile.at(pos2idx(bel.pos)).at(bel.z); }
const BelInfo &bel_data(BelId bel) const { return bels_by_tile.at(pos2idx(bel.pos)).at(bel.z); }

View File

@ -76,44 +76,44 @@ struct DelayInfo
struct BelId
{
BelId() = default;
BelId(CycloneV::pos_t _pos, uint16_t _z) : pos{_pos}, z{_z} {}
BelId(CycloneV::xycoords _pos, uint16_t _z) : pos{_pos}, z{_z} {}
// pos_t is used for X/Y, nextpnr-cyclonev uses its own Z coordinate system.
CycloneV::pos_t pos = 0;
// xycoords is used for X/Y, nextpnr-cyclonev uses its own Z coordinate system.
CycloneV::xycoords pos{};
uint16_t z = 0;
bool operator==(const BelId &other) const { return pos == other.pos && z == other.z; }
bool operator!=(const BelId &other) const { return pos != other.pos || z != other.z; }
bool operator<(const BelId &other) const { return pos < other.pos || (pos == other.pos && z < other.z); }
unsigned int hash() const { return mkhash(pos, z); }
unsigned int hash() const { return mkhash(pos.v, z); }
};
static constexpr auto invalid_rnode = std::numeric_limits<CycloneV::rnode_t>::max();
static constexpr auto invalid_rnode = CycloneV::rnode_coords{};
struct WireId
{
WireId() = default;
explicit WireId(CycloneV::rnode_t node) : node(node){};
CycloneV::rnode_t node = invalid_rnode;
explicit WireId(CycloneV::rnode_coords node) : node(node){};
CycloneV::rnode_coords node = invalid_rnode;
// Wires created by nextpnr have rnode type >= 128
bool is_nextpnr_created() const
{
NPNR_ASSERT(node != invalid_rnode);
return unsigned(CycloneV::rn2t(node)) >= 128;
return unsigned(node.t()) >= 128;
}
bool operator==(const WireId &other) const { return node == other.node; }
bool operator!=(const WireId &other) const { return node != other.node; }
bool operator<(const WireId &other) const { return node < other.node; }
unsigned int hash() const { return unsigned(node); }
unsigned int hash() const { return node.v; }
};
struct PipId
{
PipId() = default;
PipId(CycloneV::rnode_t src, CycloneV::rnode_t dst) : src(src), dst(dst){};
CycloneV::rnode_t src = invalid_rnode, dst = invalid_rnode;
PipId(CycloneV::rnode_coords src, CycloneV::rnode_coords dst) : src(src), dst(dst){};
CycloneV::rnode_coords src = invalid_rnode, dst = invalid_rnode;
bool operator==(const PipId &other) const { return src == other.src && dst == other.dst; }
bool operator!=(const PipId &other) const { return src != other.src || dst != other.dst; }

View File

@ -30,15 +30,15 @@ struct MistralBitgen
Context *ctx;
CycloneV *cv;
using rnode_t = CycloneV::rnode_t;
using pnode_t = CycloneV::pnode_t;
using pos_t = CycloneV::pos_t;
using rnode_coords = CycloneV::rnode_coords;
using pnode_coords = CycloneV::pnode_coords;
using xycoords = CycloneV::xycoords;
using block_type_t = CycloneV::block_type_t;
using port_type_t = CycloneV::port_type_t;
rnode_t find_rnode(block_type_t bt, pos_t pos, port_type_t port, int bi = -1, int pi = -1) const
rnode_coords find_rnode(block_type_t bt, xycoords pos, port_type_t port, int bi = -1, int pi = -1) const
{
auto pn1 = CycloneV::pnode(bt, pos, port, bi, pi);
auto pn1 = CycloneV::pnode_coords{bt, pos, port, bi, pi};
auto rn1 = cv->pnode_to_rnode(pn1);
if (rn1)
return rn1;
@ -55,7 +55,7 @@ struct MistralBitgen
return rn2;
}
return 0;
return CycloneV::rnode_coords{};
}
void options()
@ -88,7 +88,7 @@ struct MistralBitgen
void write_io_cell(CellInfo *ci, int x, int y, int bi)
{
bool is_output = (ci->type == id_MISTRAL_OB || (ci->type == id_MISTRAL_IO && ci->getPort(id_OE) != nullptr));
auto pos = CycloneV::xy2pos(x, y);
auto pos = CycloneV::xycoords{x, y};
// TODO: configurable pull, IO standard, etc
cv->bmux_b_set(CycloneV::GPIO, pos, CycloneV::USE_WEAK_PULLUP, bi, false);
if (is_output) {
@ -96,12 +96,12 @@ struct MistralBitgen
cv->bmux_m_set(CycloneV::GPIO, pos, CycloneV::IOCSR_STD, bi, CycloneV::DIS);
// Output gpios must also bypass things in the associated dqs
auto dqs = cv->p2p_to(CycloneV::pnode(CycloneV::GPIO, pos, CycloneV::PNONE, bi, -1));
auto dqs = cv->p2p_to(CycloneV::pnode_coords{CycloneV::GPIO, pos, CycloneV::PNONE, bi, -1});
if (dqs) {
cv->bmux_m_set(CycloneV::DQS16, CycloneV::pn2p(dqs), CycloneV::INPUT_REG4_SEL, CycloneV::pn2bi(dqs),
cv->bmux_m_set(CycloneV::DQS16, dqs.p(), CycloneV::INPUT_REG4_SEL, dqs.bi(),
CycloneV::SEL_LOCKED_DPA);
cv->bmux_r_set(CycloneV::DQS16, CycloneV::pn2p(dqs), CycloneV::RB_T9_SEL_EREG_CFF_DELAY,
CycloneV::pn2bi(dqs), 0x1f);
cv->bmux_r_set(CycloneV::DQS16, dqs.p(), CycloneV::RB_T9_SEL_EREG_CFF_DELAY,
dqs.bi(), 0x1f);
}
}
// There seem to be two mirrored OEIN inversion bits for constant OE for inputs/outputs. This might be to
@ -114,14 +114,14 @@ struct MistralBitgen
void write_clkbuf_cell(CellInfo *ci, int x, int y, int bi)
{
(void)ci; // currently unused
auto pos = CycloneV::xy2pos(x, y);
auto pos = CycloneV::xycoords{x, y};
cv->bmux_r_set(CycloneV::CMUXHG, pos, CycloneV::INPUT_SEL, bi, 0x1b); // hardcode to general routing
cv->bmux_m_set(CycloneV::CMUXHG, pos, CycloneV::TESTSYN_ENOUT_SELECT, bi, CycloneV::PRE_SYNENB);
}
void write_m10k_cell(CellInfo *ci, int x, int y, int bi)
{
auto pos = CycloneV::xy2pos(x, y);
auto pos = CycloneV::xycoords{x, y};
// Notes:
// DATA_FLOW_THRU is probably transparent reads.
@ -240,8 +240,8 @@ struct MistralBitgen
const std::array<CycloneV::port_type_t, 6> mux_port{CycloneV::FFT0, CycloneV::FFT1, CycloneV::FFT1L,
CycloneV::FFB0, CycloneV::FFB1, CycloneV::FFB1L};
for (int i = 0; i < 6; i++) {
if (ctx->wires_connected(alm_data.comb_out[i / 3], ctx->get_port(block_type, CycloneV::pos2x(pos),
CycloneV::pos2y(pos), alm, mux_port[i])))
if (ctx->wires_connected(alm_data.comb_out[i / 3], ctx->get_port(block_type, pos.x(),
pos.y(), alm, mux_port[i])))
cv->bmux_m_set(block_type, pos, mux_settings[i], alm, CycloneV::NLUT);
}
@ -358,7 +358,7 @@ struct MistralBitgen
for (int i = 0; i < 3; i++) {
// Check for fabric->clock routing
if (ctx->wires_connected(
ctx->get_port(block_type, CycloneV::pos2x(pos), CycloneV::pos2y(pos), -1, CycloneV::DATAIN, 0),
ctx->get_port(block_type, pos.x(), pos.y(), -1, CycloneV::DATAIN, 0),
lab_data.clk_wires[i]))
cv->bmux_m_set(block_type, pos, CycloneV::CLKA_SEL, 0, CycloneV::DIN0);
}

View File

@ -251,7 +251,7 @@ DelayQuad Arch::getPipDelay(PipId pip) const
return DelayQuad{20};
// This is guesswork based on average of (interconnect delay / number of pips)
auto src_type = CycloneV::rn2t(src.node);
auto src_type = src.node.t();
switch (src_type) {
case CycloneV::rnode_type_t::SCLK:
@ -303,7 +303,7 @@ bool Arch::getArcDelayOverride(const NetInfo *net_info, const PortRef &sink, Del
mistral::AnalogSim::wave input_wave[2], output_wave[2];
mistral::AnalogSim::time_interval output_delays[2];
mistral::AnalogSim::time_interval output_delay_sum[2];
std::vector<std::pair<mistral::CycloneV::rnode_t, int>> outputs;
std::vector<std::pair<mistral::CycloneV::rnode_coords, int>> outputs;
auto temp = mistral::CycloneV::T_100;
auto est = mistral::CycloneV::EST_SLOW;
@ -342,7 +342,7 @@ bool Arch::getArcDelayOverride(const NetInfo *net_info, const PortRef &sink, Del
continue;
if (dst.is_nextpnr_created())
dst.node = 0;
dst.node = CycloneV::rnode_coords{};
auto mode = cyclonev->rnode_timing_get_mode(src.node);
NPNR_ASSERT(mode != mistral::CycloneV::RTM_UNSUPPORTED);
@ -378,13 +378,13 @@ bool Arch::getArcDelayOverride(const NetInfo *net_info, const PortRef &sink, Del
: mistral::CycloneV::RF_RISE;
mistral::AnalogSim sim;
int input = -1;
std::vector<std::pair<mistral::CycloneV::rnode_t, int>> outputs;
std::vector<std::pair<mistral::CycloneV::rnode_coords, int>> outputs;
cyclonev->rnode_timing_build_circuit(src.node, temp, CycloneV::DELAY_MAX, actual_edge, sim, input, outputs);
sim.set_input_wave(input, input_wave[edge]);
auto o = std::find_if(
outputs.begin(), outputs.end(),
[&](std::pair<mistral::CycloneV::rnode_t, int> output) { return output.first == dst.node; });
[&](std::pair<mistral::CycloneV::rnode_coords, int> output) { return output.first == dst.node; });
NPNR_ASSERT(o != outputs.end());
output_wave[edge].clear();
@ -419,10 +419,10 @@ delay_t Arch::predictDelay(BelId src_bel, IdString src_pin, BelId dst_bel, IdStr
delay_t Arch::estimateDelay(WireId src, WireId dst) const
{
int x0 = CycloneV::rn2x(src.node);
int y0 = CycloneV::rn2y(src.node);
int x1 = CycloneV::rn2x(dst.node);
int y1 = CycloneV::rn2y(dst.node);
int x0 = src.node.x();
int y0 = src.node.y();
int x1 = dst.node.x();
int y1 = dst.node.y();
int x_diff = std::abs(x1 - x0);
int y_diff = std::abs(y1 - y0);
return 75 * x_diff + 200 * y_diff;

View File

@ -71,7 +71,7 @@ struct MistralGlobalRouter
// When routing globals; we allow global->local for some tricky cases but never local->local
bool global_pip_filter(PipId pip) const
{
auto src_type = CycloneV::rn2t(pip.src);
auto src_type = pip.src.t();
return src_type != CycloneV::H14 && src_type != CycloneV::H6 && src_type != CycloneV::H3 &&
src_type != CycloneV::V12 && src_type != CycloneV::V2 && src_type != CycloneV::V4 &&
src_type != CycloneV::WM;

View File

@ -56,8 +56,8 @@ bool Arch::is_io_cell(IdString cell_type) const
BelId Arch::get_io_pin_bel(const CycloneV::pin_info_t *pin) const
{
auto pad = pin->pad;
CycloneV::pos_t pos = (pad & 0x3FFF);
return bel_by_block_idx(CycloneV::pos2x(pos), CycloneV::pos2y(pos), id_MISTRAL_IO, (pad >> 14));
CycloneV::xycoords pos = CycloneV::xycoords{pad & 0x3FFF};
return bel_by_block_idx(pos.x(), pos.y(), id_MISTRAL_IO, (pad >> 14));
}
NEXTPNR_NAMESPACE_END

View File

@ -1081,7 +1081,7 @@ uint64_t Arch::compute_lut_mask(uint32_t lab, uint8_t alm)
#if 1
if (getCtx()->debug) {
auto pos = alm_data.lut_bels[0].pos;
log("ALM %03d.%03d.%d\n", CycloneV::pos2x(pos), CycloneV::pos2y(pos), alm);
log("ALM %03d.%03d.%d\n", pos.x(), pos.y(), alm);
for (int i = 0; i < 2; i++) {
log(" LUT%d: ", i);
if (luts[i]) {