fabulous: Add a viaduct uarch

Signed-off-by: gatecat <gatecat@ds0.me>
This commit is contained in:
gatecat 2022-08-04 09:04:02 +02:00
parent b9b16eaa53
commit f423055390
12 changed files with 1637 additions and 1 deletions

View File

@ -1,4 +1,4 @@
set(VIADUCT_UARCHES "example" "okami")
set(VIADUCT_UARCHES "example" "okami" "fabulous")
foreach(uarch ${VIADUCT_UARCHES})
aux_source_directory(${family}/viaduct/${uarch} UARCH_FILES)
foreach(target ${family_targets})

View File

@ -0,0 +1,88 @@
X(FABULOUS_LC)
X(FABULOUS_COMB)
X(FABULOUS_FF)
X(SET_NORESET)
X(ASYNC_SR)
X(NEG_CLK)
X(FF)
X(LATCH_NOFF)
X(IO_1_bidirectional_frame_config_pass)
X(InPass4_frame_config)
X(OutPass4_frame_config)
X(RegFile_32x4)
X(MULADD)
X(MUX8LUT_frame_config)
X(CLK)
X(I)
X(T)
X(O)
X(Q)
X(Ci)
X(Co)
X(X0Y0)
X(REG_CLK)
X(LUT_CLK)
X(global_clock)
X(Global_Clock)
X(O2Q)
X(WRITE_DATA)
X(WRITE_ADDRESS)
X(READ_DATA)
X(READ_ADDRESS)
X(DSP_DATA_OUT)
X(DSP_DATA_IN)
X(DSP_CLR)
X(carry_in)
X(carry_out)
X(LUTFF)
X(LUTFF_E)
X(LUTFF_SR)
X(LUTFF_SS)
X(LUTFF_ESR)
X(LUTFF_ESS)
X(LUTFF_R)
X(LUTFF_S)
X(LUTFF_ER)
X(LUTFF_ES)
X(LUTFF_N)
X(LUTFF_NE)
X(LUTFF_NSR)
X(LUTFF_NSS)
X(LUTFF_NESR)
X(LUTFF_NESS)
X(LUTFF_NR)
X(LUTFF_NS)
X(LUTFF_NER)
X(LUTFF_NES)
X(clr)
X(__disconnected)
X(INIT)
X(E)
X(C)
X(D)
X(S)
X(R)
X(SR)
X(EN)
X(BEL)
X(PAD)
X(LUT1)
X(BelBegin)
X(BelEnd)
X(GlobalClk)
X(CFG)

View File

@ -0,0 +1,102 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2021-22 gatecat <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef FAB_CFG_H
#define FAB_CFG_H
#include "fab_defs.h"
#include "hashlib.h"
#include "idstring.h"
#include "nextpnr_assertions.h"
#include "nextpnr_namespaces.h"
#include <string>
#include <vector>
NEXTPNR_NAMESPACE_BEGIN
/*
This set of structures is designed to enumerate the different configurable options for a fabulous architecture,
affecting the packer etc...
*/
struct ControlSetConfig
{
/*
CLB signal routing masks for fast validity checking
for each unique CLK/CE/SR input to a CLB, add an entry to this vector, and set the bits to 1 for each ff that
signal can drive for a CLB with 8 FFs and 2 clocks split at halfway, the first entry would be 0x0F and the second
0xF0
*/
std::vector<route_mask_t> routing = {0b11111111}; // default 1 shared between all
bool have_signal = true;
bool can_invert = false;
};
struct LogicConfig
{
// ** Core CLB config
unsigned lc_per_clb = 8; // number of logic cells per clb
bool split_lc = false; // whether to represent SLICE as a single bel or separate lut+ff (latter important if ff and
// lut can be used separately)
// ** LUT config
unsigned lut_k = 4; // base number of inputs for lookup table
enum LutType
{
SINGLE_LUT,
// ...
} lut_type = LutType::SINGLE_LUT; // different types of fracturable LUT structure
enum LutCascade
{
NO_CASCADE,
// ...
} lut_casc = LutCascade::NO_CASCADE; // different types of cascading between LUTs
// TODO: other features we might want to represent...
// TODO: fracLUT/FF/mux/carry output sharing matrices
// ** Carry config
enum CarryType
{
NO_CARRY, // no carry chain
HA_PRE_LUT, // half addder before LUT (classic fabulous LC)
PG_POST_LUT, // prop/gen logic after a fractured LUT
FA_POST_LUT, // full adder after a fractured LUT
} carry_type = CarryType::HA_PRE_LUT;
int carry_lut_frac = -1; // how the LUT is fractured for PG_POST_LUT/FA_POST_LUT, if the LUT fracturing is different
// (or only supported) for carry modes and not in general
// ** FF config
unsigned ff_per_lc = 1; // number of flipflops per logic cell
uint32_t dedi_ff_input = 0; // mask of flipflops in a LC that have dedicated inputs
uint32_t dedi_ff_output = 0; // mask of flipflops in a LC that have dedicated outputs
ControlSetConfig clk, sr, en; // flipflop control set routing
};
struct FabricConfig
{
LogicConfig clb;
// DSP cascading, BRAM, IP rules, IO, clocking ...
};
NEXTPNR_NAMESPACE_END
#endif

View File

@ -0,0 +1,39 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2022 gatecat <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef FAB_DEFS_H
#define FAB_DEFS_H
#include <cstdint>
#include "nextpnr_namespaces.h"
NEXTPNR_NAMESPACE_BEGIN
/*
This defines some compile-time maximums for the fabulous arch
*/
static constexpr unsigned MAX_LUTK = 6; // max number of LUT inputs
typedef uint64_t route_mask_t; // the width of this type defines the max number of FFs in a CLB (for defining control
// set route patterns)
NEXTPNR_NAMESPACE_END
#endif

View File

@ -0,0 +1,175 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2022 gatecat <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef FABULOUS_PARSING_H
#define FABULOUS_PARSING_H
#include <iostream>
#include <limits>
#include <string>
#include "idstring.h"
#include "nextpnr_assertions.h"
#include "nextpnr_namespaces.h"
NEXTPNR_NAMESPACE_BEGIN
struct BaseCtx;
// Lightweight NIH string_view
struct parser_view
{
char *m_ptr;
size_t m_length;
parser_view() : m_ptr(nullptr), m_length(0){};
explicit parser_view(std::string &str) : m_ptr(&(str[0])), m_length(str.size()){};
parser_view(char *ptr, size_t length) : m_ptr(ptr), m_length(length){};
static constexpr size_t npos = std::numeric_limits<size_t>::max();
char operator[](size_t idx)
{
NPNR_ASSERT(idx < m_length);
return m_ptr[idx];
}
size_t size() const { return m_length; }
bool empty() const { return m_length == 0; }
parser_view substr(size_t start, size_t length = npos)
{
NPNR_ASSERT(start <= m_length);
if (length == npos)
length = m_length - start;
NPNR_ASSERT(length <= m_length);
return parser_view(m_ptr + start, length);
}
size_t find(char tok) const
{
for (size_t i = 0; i < m_length; i++)
if (m_ptr[i] == tok)
return i;
return npos;
}
IdString to_id(const BaseCtx *ctx)
{
// This isn't really ideal, let's hope one day we can move to C++20 and have proper string_views instead :3
char tmp = m_ptr[m_length];
m_ptr[m_length] = '\0';
IdString id = IdString(ctx, m_ptr);
m_ptr[m_length] = tmp;
return id;
}
long to_int()
{
// This isn't really ideal, let's hope one day we can move to C++20 and have proper string_views instead :3
char tmp = m_ptr[m_length];
m_ptr[m_length] = '\0';
long l = strtol(m_ptr, nullptr, 0);
m_ptr[m_length] = tmp;
return l;
}
parser_view strip(const std::string &ws = " \r\n\t")
{
char *ptr = m_ptr;
size_t length = m_length;
while (length > 0) { // strip front
if (ws.find(*ptr) == std::string::npos) // not whitespace
break;
ptr++;
length--;
}
while (length > 0) { // strip back
if (ws.find(ptr[length - 1]) == std::string::npos) // not whitespace
break;
length--;
}
return parser_view(ptr, length);
}
char back()
{
NPNR_ASSERT(m_length > 0);
return m_ptr[m_length - 1];
}
parser_view back(size_t count)
{
NPNR_ASSERT(count <= m_length);
return parser_view(m_ptr + (m_length - count), count);
}
bool starts_with(const std::string &st)
{
if (m_length < st.size())
return false;
for (size_t i = 0; i < st.length(); i++)
if (m_ptr[i] != st[i])
return false;
return true;
}
std::pair<parser_view, parser_view> split(char delim) const
{
size_t pos = find(delim);
NPNR_ASSERT(pos != npos);
return std::make_pair(parser_view(m_ptr, pos), parser_view(m_ptr + pos + 1, m_length - (pos + 1)));
}
};
struct CsvParser
{
explicit CsvParser(std::istream &in) : in(in){};
std::istream &in;
std::string buf;
parser_view view;
bool fetch_next_line()
{
while (!in.eof()) {
std::getline(in, buf);
view = parser_view(buf).strip();
size_t end_pos = view.find('#');
if (end_pos != parser_view::npos)
view = view.substr(0, end_pos);
view = view.strip();
if (!view.empty())
return true;
}
return false;
}
parser_view next_field()
{
size_t next_delim = view.find(',');
if (next_delim == parser_view::npos) {
parser_view result = view.substr(0, next_delim);
view = parser_view();
return result;
} else {
parser_view result = view.substr(0, next_delim);
view = view.substr(next_delim + 1);
return result;
}
}
};
NEXTPNR_NAMESPACE_END
#endif

View File

@ -0,0 +1,396 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2021-22 gatecat <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "fabric_parsing.h"
#include "log.h"
#include "nextpnr.h"
#include "util.h"
#include "viaduct_api.h"
#include "viaduct_helpers.h"
#include <fstream>
#define GEN_INIT_CONSTIDS
#define VIADUCT_CONSTIDS "viaduct/fabulous/constids.inc"
#include "viaduct_constids.h"
#include "fab_cfg.h"
#include "fab_defs.h"
#include "fasm.h"
#include "pack.h"
#include "validity_check.h"
#include <boost/filesystem.hpp>
NEXTPNR_NAMESPACE_BEGIN
namespace {
struct FabulousImpl : ViaductAPI
{
FabulousImpl(const dict<std::string, std::string> &args)
{
for (auto a : args) {
if (a.first == "fasm")
fasm_file = a.second;
else
log_error("unrecognised fabulous option '%s'\n", a.first.c_str());
}
}
~FabulousImpl(){};
void init(Context *ctx) override
{
init_uarch_constids(ctx);
ViaductAPI::init(ctx);
h.init(ctx);
fab_root = get_env_var("FAB_ROOT", ", set it to the fabulous build output or project path");
if (boost::filesystem::exists(fab_root + "/.FABulous"))
is_new_fab = true;
else
is_new_fab = false;
log_info("Detected FABulous %s format project.\n", is_new_fab ? "2.0" : "1.0");
// To consider: a faster serialised form of the device data (like bba that other arches use) so we don't have to
// go through the whole csv parsing malarkey each time
blk_trk = std::make_unique<BlockTracker>(ctx, cfg);
is_new_fab ? init_bels_v2() : init_bels_v1();
init_pips();
}
void pack() override { fabulous_pack(ctx, cfg); }
void postRoute() override
{
if (!fasm_file.empty())
fabulous_write_fasm(ctx, cfg, fasm_file);
}
void prePlace() override { assign_cell_info(); }
bool isBelLocationValid(BelId bel) const override { return blk_trk->check_validity(bel, cfg, cell_tags); }
private:
FabricConfig cfg; // TODO: non-default config
ViaductHelpers h;
WireId global_clk_wire;
std::string fasm_file;
std::unique_ptr<BlockTracker> blk_trk;
std::string get_env_var(const std::string &name, const std::string &prompt = "")
{
const char *var = getenv(name.c_str());
if (var == nullptr)
log_error("environment variable '%s' is not set%s\n", name.c_str(), prompt.c_str());
return std::string(var);
}
std::ifstream open_data_rel(const std::string &postfix)
{
const std::string filename(fab_root + postfix);
std::ifstream in(filename);
if (!in)
log_error("failed to open data file '%s' (is FAB_ROOT set correctly?)\n", filename.c_str());
return in;
}
std::string fab_root;
bool is_new_fab;
pool<IdString> warned_beltypes;
void add_pseudo_pip(WireId src, WireId dst, IdString pip_type)
{
const auto &src_data = ctx->wire_info(src);
IdStringList pip_name = IdStringList::concat(ctx->getWireName(src), ctx->getWireName(dst));
ctx->addPip(pip_name, pip_type, src, dst, ctx->getDelayFromNS(0.05), Loc(src_data.x, src_data.y, 0));
}
void handle_bel_ports(BelId bel, IdString tile, IdString bel_type, const std::vector<parser_view> &ports)
{
// TODO: improve the scalability here as we support more bel types
IdString idx = ctx->getBelName(bel)[1];
Loc loc = ctx->getBelLocation(bel);
if (bel_type == id_IO_1_bidirectional_frame_config_pass) {
for (parser_view p : ports) {
IdString port_id = p.to_id(ctx);
WireId port_wire = get_wire(tile, port_id, ctx->idf("W_IO_%s", port_id.c_str(ctx)));
IdString pin = p.back(1).to_id(ctx);
ctx->addBelPin(bel, pin, port_wire, pin.in(id_I, id_T) ? PORT_IN : PORT_OUT);
}
} else if (bel_type.in(id_InPass4_frame_config, id_OutPass4_frame_config)) {
for (parser_view p : ports) {
IdString port_id = p.to_id(ctx);
WireId port_wire = get_wire(tile, port_id, port_id);
IdString pin = p.back(2).to_id(ctx);
ctx->addBelPin(bel, pin, port_wire, bel_type == id_OutPass4_frame_config ? PORT_IN : PORT_OUT);
}
} else if (bel_type == id_RegFile_32x4) {
WireId clk_wire = get_wire(tile, id_CLK, id_REG_CLK);
ctx->addBelInput(bel, id_CLK, clk_wire);
add_pseudo_pip(global_clk_wire, clk_wire, id_global_clock);
for (parser_view p : ports) {
IdString port_id = p.to_id(ctx);
// TODO: nicer way of determining port type?
if (p[0] == 'D') {
ctx->addBelInput(bel, port_id, get_wire(tile, port_id, id_WRITE_DATA));
} else if (p[0] == 'W') {
ctx->addBelInput(bel, port_id, get_wire(tile, port_id, id_WRITE_ADDRESS));
} else if (p[1] == 'D') {
ctx->addBelOutput(bel, port_id, get_wire(tile, port_id, id_READ_DATA));
} else {
ctx->addBelInput(bel, port_id, get_wire(tile, port_id, id_READ_ADDRESS));
}
}
} else if (bel_type == id_MULADD) {
// TODO: do DSPs need a clock too like regfiles?
for (parser_view p : ports) {
IdString port_id = p.to_id(ctx);
if (p[0] == 'Q') {
ctx->addBelOutput(bel, port_id, get_wire(tile, port_id, id_DSP_DATA_OUT));
} else if (port_id == id_clr) {
ctx->addBelInput(bel, port_id, get_wire(tile, port_id, id_DSP_CLR));
} else {
ctx->addBelInput(bel, port_id, get_wire(tile, port_id, id_DSP_DATA_IN));
}
}
} else if (bel_type == id_MUX8LUT_frame_config) {
for (parser_view p : ports) {
IdString port_id = p.to_id(ctx);
ctx->addBelPin(bel, port_id, get_wire(tile, port_id, ctx->idf("LUTMUX_%s", port_id.c_str(ctx))),
p[0] == 'M' ? PORT_OUT : PORT_IN);
}
} else if (bel_type == id_FABULOUS_LC) {
// TODO: split LC mode, LUT permutation pseudo-switchbox, LUT thru pseudo-pips
WireId clk_wire = get_wire(tile, ctx->idf("L%s_CLK", idx.c_str(ctx)), id_LUT_CLK);
ctx->addBelInput(bel, id_CLK, clk_wire);
add_pseudo_pip(global_clk_wire, clk_wire, id_global_clock);
blk_trk->set_bel_type(bel, BelFlags::BLOCK_CLB, BelFlags::FUNC_LC_COMB, loc.z);
for (parser_view p : ports) {
IdString port_id = p.to_id(ctx);
WireId port_wire = get_wire(tile, port_id, ctx->idf("LUT_%s", port_id.c_str(ctx)));
// TODO: more robust port name handling
if (p[3] == 'S' || p[3] == 'E' || p[3] == 'I') { // set/reset, enable, LUT input
ctx->addBelInput(bel, p.substr(3).to_id(ctx), port_wire);
} else if (p[3] == 'O') { // LUT otuput
ctx->addBelOutput(bel, p.substr(3, 1).to_id(ctx), port_wire);
} else if (p[3] == 'C') { // carry chain
if (p[4] == 'i') {
ctx->addBelInput(bel, id_Ci, port_wire);
} else {
NPNR_ASSERT(p[4] == 'o');
ctx->addBelOutput(bel, id_Co, port_wire);
}
} else {
log_error("don't know what to do with LC port '%s'\n", port_id.c_str(ctx));
}
}
} else {
// ...
if (!warned_beltypes.count(bel_type) && !ports.empty()) {
log_warning("don't know how to handle ports for bel type '%s'\n", bel_type.c_str(ctx));
warned_beltypes.insert(bel_type);
}
}
}
void init_global_clock()
{
// TODO: how do we extend this to more complex clocking topologies?
BelId global_clk_bel =
ctx->addBel(IdStringList::concat(ctx->id("X0Y0"), id_CLK), id_Global_Clock, Loc(0, 0, 0), true, false);
global_clk_wire = ctx->addWire(IdStringList::concat(ctx->id("X0Y0"), id_CLK), id_CLK, 0, 0);
ctx->addBelOutput(global_clk_bel, id_CLK, global_clk_wire);
}
// TODO: this is for legacy fabulous only, the new code path can be a lot simpler
void init_bels_v1()
{
std::ifstream in = open_data_rel("/npnroutput/bel.txt");
CsvParser csv(in);
init_global_clock();
while (csv.fetch_next_line()) {
IdString tile = csv.next_field().to_id(ctx);
int bel_x = csv.next_field().substr(1).to_int();
int bel_y = csv.next_field().substr(1).to_int();
auto bel_idx = csv.next_field();
IdString bel_type = csv.next_field().to_id(ctx);
NPNR_ASSERT(bel_idx.size() == 1);
int bel_z = bel_idx[0] - 'A';
NPNR_ASSERT(bel_z >= 0 && bel_z < 26);
/*
In the future we will need to handle optionally splitting SLICEs into separate LUT/COMB and FF bels
This is the preferred approach in nextpnr for arches where the LUT and FF can be used separately of
each other (e.g. there is a way of routing the LUT and FF outputs individually, and some extra
optional FF input).
While this isn't yet the standard fabulous SLICE, it should be considered as a future option in fabulous.
*/
Loc loc(bel_x, bel_y, bel_z);
BelId bel = ctx->addBel(IdStringList::concat(tile, bel_idx.to_id(ctx)), bel_type, loc, false, false);
std::vector<parser_view> ports;
parser_view port;
while (!(port = csv.next_field()).empty()) {
ports.push_back(port);
}
handle_bel_ports(bel, tile, bel_type, ports);
}
postprocess_bels();
}
void init_bels_v2()
{
std::ifstream in = open_data_rel("/.FABulous/bel.v2.txt");
CsvParser csv(in);
init_global_clock();
BelId curr_bel;
while (csv.fetch_next_line()) {
IdString cmd = csv.next_field().to_id(ctx);
if (cmd == id_BelBegin) {
IdString tile = csv.next_field().to_id(ctx);
auto bel_idx = csv.next_field();
IdString bel_type = csv.next_field().to_id(ctx);
NPNR_ASSERT(bel_idx.size() == 1);
int bel_z = bel_idx[0] - 'A';
NPNR_ASSERT(bel_z >= 0 && bel_z < 26);
Loc loc = tile_loc(tile);
curr_bel = ctx->addBel(IdStringList::concat(tile, bel_idx.to_id(ctx)), bel_type,
Loc(loc.x, loc.y, bel_z), false, false);
} else if (cmd.in(id_I, id_O)) {
IdString port = csv.next_field().to_id(ctx);
auto wire_name = csv.next_field().split('.');
WireId wire =
get_wire(wire_name.first.to_id(ctx), wire_name.second.to_id(ctx), wire_name.second.to_id(ctx));
ctx->addBelPin(curr_bel, port, wire, cmd == id_O ? PORT_OUT : PORT_IN);
} else if (cmd == id_GlobalClk) {
IdStringList bel_name = ctx->getBelName(curr_bel);
WireId clk_wire = get_wire(bel_name[0], ctx->idf("%s_CLK", bel_name[1].c_str(ctx)), id_REG_CLK);
ctx->addBelInput(curr_bel, id_CLK, clk_wire);
add_pseudo_pip(global_clk_wire, clk_wire, id_global_clock);
} else if (cmd == id_CFG) {
// TODO...
} else if (cmd == id_BelEnd) {
curr_bel = BelId();
} else if (cmd != IdString()) {
log_error("unsupported command %s in definition of bel %s\n", cmd.c_str(ctx),
curr_bel == BelId() ? "<none>" : ctx->nameOfBel(curr_bel));
}
}
postprocess_bels();
}
void postprocess_bels()
{
// This does some post-processing on bels to make them useful for nextpnr place-and-route regardless of the code
// path that creates them. In the future, splitting muxes and creating split LCs would be done here, too
for (auto bel : ctx->getBels()) {
auto &data = ctx->bel_info(bel);
if (data.type == id_FABULOUS_LC) {
if (!data.pins.count(id_Q)) {
// Add a Q pseudo-pin and pseudo-pip from Q to O
WireId o_wire = ctx->getBelPinWire(bel, id_O);
IdString q_name = ctx->idf("%s_Q", data.name[1].c_str(ctx));
WireId q_wire = get_wire(data.name[0], q_name, q_name);
ctx->addBelOutput(bel, id_Q, q_wire);
// Pseudo-pip for FF mode
add_pseudo_pip(q_wire, o_wire, id_O2Q);
}
} else if (data.type == id_IO_1_bidirectional_frame_config_pass) {
if (!data.pins.count(id_PAD)) {
// Add a PAD pseudo-pin for the top level
ctx->addBelInout(bel, id_PAD,
get_wire(data.name[0], ctx->idf("PAD_%s", data.name[1].c_str(ctx)), id_PAD));
}
}
}
}
void init_pips()
{
std::ifstream in = open_data_rel(is_new_fab ? "/.FABulous/pips.txt" : "/npnroutput/pips.txt");
CsvParser csv(in);
while (csv.fetch_next_line()) {
IdString src_tile = csv.next_field().to_id(ctx);
IdString src_port = csv.next_field().to_id(ctx);
IdString dst_tile = csv.next_field().to_id(ctx);
IdString dst_port = csv.next_field().to_id(ctx);
int delay = csv.next_field().to_int();
IdString pip_name = csv.next_field().to_id(ctx);
WireId src_wire = get_wire(src_tile, src_port, src_port);
WireId dst_wire = get_wire(dst_tile, dst_port, dst_port);
ctx->addPip(IdStringList::concat(src_tile, pip_name), pip_name, src_wire, dst_wire,
ctx->getDelayFromNS(0.01 * delay), tile_loc(src_tile));
}
}
// Fast lookup of tile names to XY pairs
dict<IdString, Loc> tile2loc;
Loc tile_loc(IdString tile)
{
if (!tile2loc.count(tile)) {
std::string tile_name = tile.str(ctx);
parser_view view(tile_name);
NPNR_ASSERT(view[0] == 'X');
size_t ypos = view.find('Y');
NPNR_ASSERT(ypos != parser_view::npos);
int x = view.substr(1, ypos - 1).to_int();
int y = view.substr(ypos + 1).to_int();
tile2loc[tile] = Loc(x, y, 0);
}
return tile2loc.at(tile);
}
// Create a wire if it doesn't exist, otherwise just return it
WireId get_wire(IdString tile, IdString wire, IdString type)
{
// Create a wire name by using the built-in IdStringList mechanism to store a (tile, wire) pair
// this way we don't store a full string in memory of every concatenated wire name, reducing the memory
// footprint and start time significantly beyond the ~1k LUT scale
auto wire_name = IdStringList::concat(tile, wire);
auto found = ctx->wire_by_name.find(wire_name);
if (found != ctx->wire_by_name.end())
return found->second;
// doesn't exist
Loc loc = tile_loc(tile);
return ctx->addWire(wire_name, type, loc.x, loc.y);
}
CellTagger cell_tags;
void assign_cell_info()
{
for (auto &cell : ctx->cells) {
cell_tags.assign_for(ctx, cfg, cell.second.get());
}
}
void notifyBelChange(BelId bel, CellInfo *cell)
{
CellInfo *old = ctx->getBoundBelCell(bel);
blk_trk->update_bel(bel, old, cell);
}
};
struct FabulousArch : ViaductArch
{
FabulousArch() : ViaductArch("fabulous"){};
std::unique_ptr<ViaductAPI> create(const dict<std::string, std::string> &args)
{
return std::make_unique<FabulousImpl>(args);
}
} fabulousArch;
} // namespace
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,191 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2021-22 gatecat <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "fasm.h"
#include "log.h"
#include "nextpnr.h"
#include "util.h"
#include <algorithm>
#include <boost/range/adaptor/reversed.hpp>
#include <fstream>
#define VIADUCT_CONSTIDS "viaduct/fabulous/constids.inc"
#include "viaduct_constids.h"
NEXTPNR_NAMESPACE_BEGIN
namespace {
struct FabFasmWriter
{
FabFasmWriter(const Context *ctx, const FabricConfig &cfg, const std::string &filename)
: ctx(ctx), cfg(cfg), out(filename)
{
if (!out)
log_error("failed to open fasm file '%s' for writing\n", filename.c_str());
}
std::string format_name(IdStringList name)
{
std::string result;
for (IdString entry : name) {
if (!result.empty())
result += ".";
result += entry.str(ctx);
}
return result;
}
void write_pip(PipId pip)
{
auto &data = ctx->pip_info(pip);
if (data.type.in(id_global_clock, id_O2Q))
return; // pseudo-pips with no underlying bitstream bits
// write pip name but with '.' instead of '/' for separator
out << format_name(data.name) << std::endl;
}
void write_routing(const NetInfo *net)
{
std::vector<PipId> sorted_pips;
for (auto &w : net->wires)
if (w.second.pip != PipId())
sorted_pips.push_back(w.second.pip);
std::sort(sorted_pips.begin(), sorted_pips.end());
out << stringf("# routing for net '%s'\n", ctx->nameOf(net)) << std::endl;
for (auto pip : sorted_pips)
write_pip(pip);
out << std::endl;
}
std::string prefix;
// Write a FASM bitvector; optionally inverting the values in the process
void write_vector(const std::string &name, const std::vector<bool> &value, bool invert = false)
{
out << prefix << name << " = " << int(value.size()) << "'b";
for (auto bit : boost::adaptors::reverse(value))
out << ((bit ^ invert) ? '1' : '0');
out << std::endl;
}
// Write a FASM bitvector given an integer value
void write_int_vector(const std::string &name, uint64_t value, int width, bool invert = false)
{
std::vector<bool> bits(width, false);
for (int i = 0; i < width; i++)
bits[i] = (value & (1ULL << i)) != 0;
write_vector(name, bits, invert);
}
// Write an int vector param
void write_int_vector_param(const CellInfo *cell, const std::string &name, uint64_t defval, int width,
bool invert = false)
{
uint64_t value = int_or_default(cell->params, ctx->id(name), defval);
std::vector<bool> bits(width, false);
for (int i = 0; i < width; i++)
bits[i] = (value & (1ULL << i)) != 0;
write_vector(stringf("%s[%d:0]", name.c_str(), width - 1), bits, invert);
}
void write_bool(const CellInfo *cell, const std::string &name)
{
if (bool_or_default(cell->params, ctx->id(name))) {
out << prefix << name << std::endl;
}
}
void write_logic(const CellInfo *lc)
{
prefix = format_name(ctx->getBelName(lc->bel)) + ".";
if (lc->type.in(id_FABULOUS_LC, id_FABULOUS_COMB)) {
write_int_vector_param(lc, "INIT", 0U, 1U << cfg.clb.lut_k); // todo lut depermute and thru
}
if (lc->type == id_FABULOUS_LC) {
write_bool(lc, "FF");
}
if (lc->type.in(id_FABULOUS_LC, id_FABULOUS_FF)) {
write_bool(lc, "SET_NORESET");
write_bool(lc, "NEG_CLK");
write_bool(lc, "NEG_EN");
write_bool(lc, "NEG_SR");
write_bool(lc, "ASYNC_SR");
}
}
void write_io(const CellInfo *io)
{
write_bool(io, "INPUT_USED");
write_bool(io, "OUTPUT_USED");
write_bool(io, "ENABLE_USED");
}
void write_generic_cell(const CellInfo *ci)
{
prefix = format_name(ctx->getBelName(ci->bel)) + ".";
for (auto &param : ci->params) {
// TODO: better parameter type auto-detection
if (param.second.is_string) {
// enum type parameter
out << prefix << param.first.c_str(ctx) << "." << param.second.str << std::endl;
} else if (param.second.str.size() == 1) {
// boolean type parameter
if (param.second.intval != 0)
out << prefix << param.first.c_str(ctx) << std::endl;
} else {
// vector type parameter
int msb = int(param.second.str.size()) - 1;
out << prefix << param.first.c_str(ctx) << "[" << msb << ":0] = ";
for (auto bit : boost::adaptors::reverse(param.second.str))
out << bit;
out << std::endl;
}
}
}
void write_cell(const CellInfo *ci)
{
out << stringf("# config for cell '%s'\n", ctx->nameOf(ci)) << std::endl;
if (ci->type.in(id_FABULOUS_COMB, id_FABULOUS_FF, id_FABULOUS_LC))
write_logic(ci);
else if (ci->type == id_IO_1_bidirectional_frame_config_pass)
write_io(ci);
else
write_generic_cell(ci);
// TODO: other cell types
out << std::endl;
}
void write_fasm()
{
for (const auto &net : ctx->nets)
write_routing(net.second.get());
for (const auto &cell : ctx->cells)
write_cell(cell.second.get());
}
const Context *ctx;
const FabricConfig &cfg;
std::ofstream out;
};
} // namespace
void fabulous_write_fasm(const Context *ctx, const FabricConfig &cfg, const std::string &filename)
{
FabFasmWriter wr(ctx, cfg, filename);
wr.write_fasm();
}
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,32 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2021-22 gatecat <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef FABULOUS_FASM_H
#define FABULOUS_FASM_H
#include "fab_cfg.h"
#include "nextpnr.h"
NEXTPNR_NAMESPACE_BEGIN
void fabulous_write_fasm(const Context *ctx, const FabricConfig &cfg, const std::string &filename);
NEXTPNR_NAMESPACE_END
#endif

View File

@ -0,0 +1,253 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2021-22 gatecat <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "pack.h"
#include "log.h"
#include "util.h"
#define VIADUCT_CONSTIDS "viaduct/fabulous/constids.inc"
#include "viaduct_constids.h"
#include "viaduct_helpers.h"
NEXTPNR_NAMESPACE_BEGIN
namespace {
struct FabulousPacker
{
Context *ctx;
const FabricConfig &cfg;
ViaductHelpers h;
dict<IdString, unsigned> lut_types;
std::vector<IdString> lut_inputs;
FabulousPacker(Context *ctx, const FabricConfig &cfg) : ctx(ctx), cfg(cfg)
{
// Set up some structures for faster lookups
for (unsigned i = 0; i < cfg.clb.lut_k; i++) {
lut_types[ctx->idf("LUT%d", i + 1)] = i + 1;
lut_inputs.push_back(ctx->idf("I%d", i));
}
h.init(ctx);
}
void pack_luts()
{
// Pack LUTs into FABULOUS_COMB (split-LUTFF mode) or FABULOUS_LC (packed-LUTFF mode)
// TODO: fracturable LUT handling
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
auto fnd_lut = lut_types.find(ci->type);
if (fnd_lut == lut_types.end())
continue;
unsigned lut_n = fnd_lut->second;
// convert to the necessary type
ci->type = cfg.clb.split_lc ? id_FABULOUS_COMB : id_FABULOUS_LC;
// add disconnected unused inputs
for (unsigned i = 0; i < cfg.clb.lut_k; i++)
if (!ci->ports.count(lut_inputs.at(i)))
ci->addInput(lut_inputs.at(i));
// replicate the INIT value so the unused MSBs become don't-cares
auto inst_init = get_or_default(ci->params, id_INIT, Property(0));
unsigned orig_init_len = 1U << lut_n, prim_len = 1U << cfg.clb.lut_k;
Property new_init(0, prim_len);
for (unsigned i = 0; i < prim_len; i += orig_init_len) {
auto chunk = inst_init.extract(0, orig_init_len);
for (unsigned j = 0; j < orig_init_len; j++)
new_init.str.at(i + j) = chunk.str.at(j);
}
new_init.update_intval();
ci->params[id_INIT] = new_init;
}
}
// Two-stage flipflop packing. First convert all the random primitives into a much easier-to-handle FABULOUS_FF
// Then for split-LC mode, cluster it to connected LUTs; separate-LC mode, pack it into a connected or new LC
void prepare_ffs()
{
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
const std::string &type_str = ci->type.str(ctx);
if (type_str.size() < 5 || type_str.substr(0, 5) != "LUTFF")
continue;
ci->type = id_FABULOUS_FF;
// parse config string and unify
size_t idx = 5;
if (idx < type_str.size() && type_str.at(idx) == '_')
++idx;
// clock inversion
if (idx < type_str.size() && type_str.at(idx) == 'N') {
ci->params[id_NEG_CLK] = 1;
++idx;
} else {
ci->params[id_NEG_CLK] = 0;
}
// clock enable
if (idx < type_str.size() && type_str.at(idx) == 'E')
++idx;
if (ci->ports.count(id_E))
ci->renamePort(id_E, id_EN);
else
ci->addInput(id_EN); // autocreate emtpy enable port if enable missing or unused
// sr presence and type
std::string srt = type_str.substr(idx);
if (srt == "S") {
ci->params[id_SET_NORESET] = 1;
ci->params[id_ASYNC_SR] = 1;
} else if (srt == "R") {
ci->params[id_SET_NORESET] = 0;
ci->params[id_ASYNC_SR] = 1;
} else if (srt == "SS") {
ci->params[id_SET_NORESET] = 1;
ci->params[id_ASYNC_SR] = 0;
} else if (srt == "SR" || srt == "") {
ci->params[id_SET_NORESET] = 0;
ci->params[id_ASYNC_SR] = 0;
} else {
NPNR_ASSERT_FALSE("unhandled FF type");
}
if (ci->ports.count(id_S))
ci->renamePort(id_S, id_SR);
else if (ci->ports.count(id_R))
ci->renamePort(id_R, id_SR);
if (!ci->ports.count(id_SR))
ci->addInput(id_SR); // autocreate emtpy enable port if enable missing or unused
}
}
void pack_ffs()
{
pool<IdString> to_delete;
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (ci->type != id_FABULOUS_FF)
continue;
NetInfo *d = ci->getPort(id_D);
if (!d || !d->driver.cell)
continue;
CellInfo *drv = d->driver.cell;
if (drv->type != (cfg.clb.split_lc ? id_FABULOUS_COMB : id_FABULOUS_LC) || d->driver.port != id_O)
continue;
if (!cfg.clb.split_lc && d->users.entries() > 1)
continue; // TODO: could also resolve by duplicating LUT
if (drv->cluster != ClusterId()) {
// TODO: actually we can pack these often, we just have to be more careful to check control sets
continue;
}
// we can pack them together
if (cfg.clb.split_lc) {
// create/modify cluster and add constraints. copy from an arch where we do this already...
NPNR_ASSERT_FALSE("unimplemented");
} else {
to_delete.insert(ci->name);
// this connection is packed inside the LC
ci->disconnectPort(id_D);
drv->disconnectPort(id_O);
// move other ports/params
ci->movePortTo(id_CLK, drv, id_CLK);
ci->movePortTo(id_SR, drv, id_SR);
ci->movePortTo(id_EN, drv, id_EN);
ci->movePortTo(id_O, drv, id_Q);
drv->params[id_NEG_CLK] = ci->params[id_NEG_CLK];
drv->params[id_ASYNC_SR] = ci->params[id_ASYNC_SR];
drv->params[id_SET_NORESET] = ci->params[id_SET_NORESET];
drv->params[id_FF] = 1;
for (auto &attr : ci->attrs)
drv->attrs[attr.first] = attr.second;
}
}
for (auto del : to_delete)
ctx->cells.erase(del);
if (!cfg.clb.split_lc) {
// convert remaining ffs to their own lc
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (ci->type != id_FABULOUS_FF)
continue;
ci->type = id_FABULOUS_LC;
ci->renamePort(id_D, lut_inputs.at(0));
ci->renamePort(id_O, id_Q);
// configure LUT as a thru
Property init(1U << cfg.clb.lut_k);
for (unsigned i = 0; i < (1U << cfg.clb.lut_k); i += 2) {
init.str[i] = Property::S0;
init.str[i + 1] = Property::S1;
}
init.update_intval();
ci->params[id_INIT] = init;
ci->params[id_FF] = 1;
}
}
}
void update_bel_attrs()
{
// This new arch uses the new IdStringList system with a / separator
// old fabulous arches used a dot separator in bel names
// update old attributes for maximum cross-compat
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
if (!ci->attrs.count(id_BEL))
continue;
std::string &bel = ci->attrs.at(id_BEL).str;
if (bel.find('/') != std::string::npos) // new style
continue;
size_t dot_pos = bel.find('.');
if (dot_pos != std::string::npos)
bel[dot_pos] = '/';
}
}
void handle_constants()
{
const dict<IdString, Property> vcc_params = {{id_INIT, Property(0x3, 2)}};
const dict<IdString, Property> gnd_params = {{id_INIT, Property(0x0, 2)}};
h.replace_constants(CellTypePort(id_LUT1, id_O), CellTypePort(id_LUT1, id_O), vcc_params, gnd_params);
}
void handle_io()
{
// As per the preferred approach for new nextpnr flows, we require IO to be inserted by Yosys
// pre-place-and-route, or just manually instantiated
const pool<CellTypePort> top_ports{
CellTypePort(id_IO_1_bidirectional_frame_config_pass, id_PAD),
};
h.remove_nextpnr_iobs(top_ports);
}
void run()
{
update_bel_attrs();
handle_constants();
handle_io();
pack_luts();
prepare_ffs();
pack_ffs();
}
};
} // namespace
void fabulous_pack(Context *ctx, const FabricConfig &cfg)
{
FabulousPacker packer(ctx, cfg);
packer.run();
}
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,32 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2021-22 gatecat <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef FABULOUS_PACK_H
#define FABULOUS_PACK_H
#include "fab_cfg.h"
#include "nextpnr.h"
NEXTPNR_NAMESPACE_BEGIN
void fabulous_pack(Context *ctx, const FabricConfig &cfg);
NEXTPNR_NAMESPACE_END
#endif

View File

@ -0,0 +1,201 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2021-22 gatecat <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "validity_check.h"
#include "log.h"
#include "util.h"
#define VIADUCT_CONSTIDS "viaduct/fabulous/constids.inc"
#include "viaduct_constids.h"
NEXTPNR_NAMESPACE_BEGIN
CLBState::CLBState(const LogicConfig &cfg)
{
// TODO: more than one per LC if in split-SLICE mode with fracturable LUTs
lc_comb = std::make_unique<CellInfo *[]>(cfg.lc_per_clb);
if (cfg.split_lc) {
ff = std::make_unique<CellInfo *[]>(cfg.lc_per_clb * cfg.ff_per_lc);
}
// TODO: mux
}
void CellTagger::assign_for(const Context *ctx, const FabricConfig &cfg, const CellInfo *ci)
{
if (int(data.size()) <= ci->flat_index)
data.resize(ci->flat_index + 1);
auto &t = data.at(ci->flat_index);
// Use the same logic to handle both packed and split LC modes
if (ci->type.in(id_FABULOUS_COMB, id_FABULOUS_LC)) {
unsigned lut_input_count = 0;
for (unsigned i = 0; i < cfg.clb.lut_k; i++)
if (ci->getPort(ctx->idf("I%d", i)))
lut_input_count = i + 1;
t.comb.lut_inputs = SSOArray<IdString, MAX_LUTK>(lut_input_count, IdString());
for (unsigned i = 0; i < lut_input_count; i++) {
const NetInfo *sig = ci->getPort(ctx->idf("I%d", i));
t.comb.lut_inputs[i] = sig ? sig->name : IdString();
}
t.comb.carry_used = false; // TODO
t.comb.lut_out = ci->getPort(id_O);
}
if (ci->type.in(id_FABULOUS_FF, id_FABULOUS_LC)) {
if (ci->type == id_FABULOUS_FF || bool_or_default(ci->params, id_FF)) {
t.ff.ff_used = true;
auto get_ctrlsig = [&](IdString name) {
const NetInfo *sig = ci->getPort(name);
bool invert = sig && bool_or_default(ci->params, ctx->idf("NEG_%s", name.c_str(ctx)));
return ControlSig(sig ? sig->name : id___disconnected, invert);
};
t.ff.clk = get_ctrlsig(id_CLK);
t.ff.sr = get_ctrlsig(id_SR);
t.ff.en = get_ctrlsig(id_EN);
t.ff.async = bool_or_default(ci->params, id_ASYNC_SR);
t.ff.latch = bool_or_default(ci->params, id_LATCH_NOFF);
t.ff.d = ci->getPort(id_D);
t.ff.q = ci->getPort(id_Q);
} else {
t.ff.ff_used = false;
}
}
}
void BlockTracker::set_bel_type(BelId bel, BelFlags::BlockType block, BelFlags::FuncType func, uint8_t index)
{
Loc loc = ctx->getBelLocation(bel);
if (int(tiles.size()) <= loc.y)
tiles.resize(loc.y + 1);
auto &row = tiles.at(loc.y);
if (int(row.size()) <= loc.x)
row.resize(loc.x + 1);
auto &tile = row.at(loc.x);
if (block == BelFlags::BLOCK_CLB) {
if (!tile.clb)
tile.clb = std::make_unique<CLBState>(cfg.clb);
}
if (int(bel_data.size()) <= bel.index)
bel_data.resize(bel.index + 1);
auto &flags = bel_data.at(bel.index);
flags.block = block;
flags.func = func;
flags.index = index;
}
void BlockTracker::update_bel(BelId bel, CellInfo *old_cell, CellInfo *new_cell)
{
if (bel.index >= int(bel_data.size()))
return; // some kind of bel not being tracked
auto flags = bel_data.at(bel.index);
if (flags.block == BelFlags::BLOCK_OTHER)
return; // no structures to update
Loc loc = ctx->getBelLocation(bel);
if (loc.y >= int(tiles.size()))
return; // some kind of bel not being tracked
const auto &row = tiles.at(loc.y);
if (loc.x >= int(row.size()))
return; // some kind of bel not being tracked
const auto &entry = row.at(loc.x);
if (flags.block == BelFlags::BLOCK_CLB) {
NPNR_ASSERT(entry.clb);
// TODO: incremental validity check updates might care about this in the future, hence keeping it in the
// interface for now
NPNR_UNUSED(old_cell);
if (flags.func == BelFlags::FUNC_LC_COMB)
entry.clb->lc_comb[flags.index] = new_cell;
else if (flags.func == BelFlags::FUNC_FF)
entry.clb->ff[flags.index] = new_cell;
else if (flags.func == BelFlags::FUNC_MUX)
entry.clb->mux[flags.index] = new_cell;
}
}
bool CLBState::check_validity(const LogicConfig &cfg, const CellTagger &cell_data)
{
SSOArray<ControlSig, 2> used_clk(cfg.clk.routing.size()), used_sr(cfg.sr.routing.size()),
used_en(cfg.en.routing.size());
auto check_ctrlsig = [&](unsigned idx, ControlSig actual, const ControlSetConfig &ctrl,
SSOArray<ControlSig, 2> &used) {
// see if we have an already-matching signal available
for (unsigned i = 0; i < ctrl.routing.size(); i++) {
// doesn't route to this pin
if (((ctrl.routing.at(i) >> unsigned(idx)) & 0x1U) == 0)
continue;
if (used[i] == actual)
return true;
}
// see if we have a free slot available
for (unsigned i = 0; i < ctrl.routing.size(); i++) {
// doesn't route to this pin
if (((ctrl.routing.at(i) >> unsigned(idx)) & 0x1U) == 0)
continue;
if (used[i] != ControlSig())
continue;
used[i] = actual;
return true;
}
// no option available
return false;
};
for (unsigned z = 0; z < cfg.lc_per_clb; z++) {
// flipflop control set checking
if (cfg.split_lc) {
NPNR_ASSERT_FALSE("unimplemented!"); // TODO
} else {
NPNR_ASSERT(cfg.ff_per_lc == 1); // split-slice mode must be used for more
const CellInfo *lc = lc_comb[z];
if (!lc)
continue;
auto &lct = cell_data.get(lc);
if (lct.ff.ff_used) {
// check shared control signals
if (!check_ctrlsig(z, lct.ff.clk, cfg.clk, used_clk))
return false;
if (cfg.en.have_signal && !check_ctrlsig(z, lct.ff.en, cfg.en, used_en))
return false;
if (cfg.sr.have_signal && !check_ctrlsig(z, lct.ff.sr, cfg.sr, used_sr))
return false;
}
}
}
// TODO: other checks...
return true;
}
bool BlockTracker::check_validity(BelId bel, const FabricConfig &cfg, const CellTagger &cell_data)
{
if (bel.index >= int(bel_data.size()))
return true; // some kind of bel not being tracked
auto flags = bel_data.at(bel.index);
if (flags.block == BelFlags::BLOCK_OTHER)
return true; // no structures to update
Loc loc = ctx->getBelLocation(bel);
if (loc.y >= int(tiles.size()))
return true; // some kind of bel not being tracked
const auto &row = tiles.at(loc.y);
if (loc.x >= int(row.size()))
return true; // some kind of bel not being tracked
const auto &entry = row.at(loc.x);
if (flags.block == BelFlags::BLOCK_CLB) {
return entry.clb->check_validity(cfg.clb, cell_data);
} else {
return true;
}
}
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,127 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2021-22 gatecat <gatecat@ds0.me>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef VALIDITY_CHECKING_H
#define VALIDITY_CHECKING_H
#include "fab_cfg.h"
#include "fab_defs.h"
#include "sso_array.h"
#include "nextpnr.h"
NEXTPNR_NAMESPACE_BEGIN
// The validity checking engine for the fabulous configurable CLB
// data that we tag onto cells for fast lookup, so we aren't doing slow hash map accesses in the inner-loop-critical
// validity checking code
struct ControlSig
{
ControlSig() : net(), invert(false){};
ControlSig(IdString net, bool invert) : net(net), invert(invert){};
IdString net;
bool invert;
bool operator==(const ControlSig &other) const { return net == other.net && invert == other.invert; }
bool operator!=(const ControlSig &other) const { return net != other.net || invert != other.invert; }
};
struct CellTags
{
struct
{
SSOArray<IdString, MAX_LUTK> lut_inputs; // for checking fracturable LUTs
bool carry_used = false;
const NetInfo *lut_out = nullptr;
// ...
} comb; // data for LUTs, or the LUT part of combined LUT+FF cells
struct
{
ControlSig clk, sr, en;
bool ff_used = false;
bool async = false;
bool latch = false;
const NetInfo *d = nullptr, *q = nullptr;
} ff; // data for FFs, or the FF part of combined LUT+FF cells
};
// map between cell and tags, using the flat_index that viaduct defines for this purpose
struct CellTagger
{
std::vector<CellTags> data;
const CellTags &get(const CellInfo *ci) const { return data.at(ci->flat_index); }
void assign_for(const Context *ctx, const FabricConfig &cfg, const CellInfo *ci);
};
// we need to add some extra data to CLB bels to track what they do, so we can update CLBState accordingly
struct BelFlags
{
enum BlockType : uint8_t
{
BLOCK_OTHER,
BLOCK_CLB,
// ...
} block = BlockType::BLOCK_OTHER;
enum FuncType : uint8_t
{
FUNC_LC_COMB,
FUNC_FF,
FUNC_MUX,
FUNC_OTHER,
} func;
uint8_t index;
// ...
};
// state of a CLB, for fast bel->cell lookup
// TODO: add valid/dirty tracking for incremental validity re-checking, important once we have bigger/more complex CLBs
// (cf. xilinx/intel arches in nextpnr)
struct CLBState
{
explicit CLBState(const LogicConfig &cfg);
// In combined-LC mode (LC bel contains LUT and FF), this indexes the entire LC bel to cell. In separate mode, this
// indexes the combinational part (LUT or LUT+carry only).
std::unique_ptr<CellInfo *[]> lc_comb;
// In split-LC mode only, this maps FF bel (in CLB) index to cell
std::unique_ptr<CellInfo *[]> ff;
// If there is (a) separate mux bel(s), map them to cells
std::unique_ptr<CellInfo *[]> mux;
bool check_validity(const LogicConfig &cfg, const CellTagger &cell_data);
};
struct BlockTracker
{
Context *ctx;
const FabricConfig &cfg;
std::vector<BelFlags> bel_data;
BlockTracker(Context *ctx, const FabricConfig &cfg) : ctx(ctx), cfg(cfg){};
void set_bel_type(BelId bel, BelFlags::BlockType block, BelFlags::FuncType func, uint8_t index);
void update_bel(BelId bel, CellInfo *old_cell, CellInfo *new_cell);
struct TileData
{
std::unique_ptr<CLBState> clb;
// ...
};
std::vector<std::vector<TileData>> tiles;
bool check_validity(BelId bel, const FabricConfig &cfg, const CellTagger &cell_data);
};
NEXTPNR_NAMESPACE_END
#endif