mirror of https://github.com/YosysHQ/nextpnr.git
Gowin. Divide packer. (#1645)
Split the packer into several files. Signed-off-by: YRabbit <rabbit@yrabbit.cyou>
This commit is contained in:
parent
d065288979
commit
d43c09d070
|
|
@ -13,6 +13,11 @@ set(SOURCES
|
|||
gowin_utils.cc
|
||||
gowin_utils.h
|
||||
pack.cc
|
||||
pack_bsram.cc
|
||||
pack_dsp.cc
|
||||
pack_luts.cc
|
||||
pack_io.cc
|
||||
pack_iologic.cc
|
||||
pack.h
|
||||
)
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -7,6 +7,125 @@ NEXTPNR_NAMESPACE_BEGIN
|
|||
|
||||
void gowin_pack(Context *ctx);
|
||||
|
||||
struct GowinPacker
|
||||
{
|
||||
Context *ctx;
|
||||
HimbaechelHelpers h;
|
||||
GowinUtils gwu;
|
||||
|
||||
GowinPacker(Context *ctx) : ctx(ctx)
|
||||
{
|
||||
h.init(ctx);
|
||||
gwu.init(ctx);
|
||||
}
|
||||
|
||||
// IO
|
||||
void pack_iobs(void);
|
||||
void pack_i3c(void);
|
||||
void pack_mipi(void);
|
||||
void pack_diff_iobs(void);
|
||||
void pack_io_regs(void);
|
||||
void pack_iodelay(void);
|
||||
void pack_iologic(void);
|
||||
|
||||
// 16 SERDES
|
||||
void pack_io16(void);
|
||||
|
||||
// LUTs
|
||||
void pack_wideluts(void);
|
||||
void pack_alus(void);
|
||||
void pack_ssram(void);
|
||||
void pack_inv(void);
|
||||
|
||||
// BSRAM
|
||||
void pack_bsram(void);
|
||||
|
||||
// DSP
|
||||
void pack_dsp(void);
|
||||
|
||||
// PLL
|
||||
void pack_pll(void);
|
||||
|
||||
// Clocks
|
||||
void pack_hclk(void);
|
||||
void pack_dlldly(void);
|
||||
void pack_buffered_nets(void);
|
||||
void pack_dqce(void);
|
||||
void pack_dcs(void);
|
||||
void pack_dhcens(void);
|
||||
|
||||
// ADC
|
||||
void pack_adc(void);
|
||||
|
||||
// Misc
|
||||
void pack_iem(void);
|
||||
void handle_constants(void);
|
||||
void pack_gsr(void);
|
||||
void pack_pincfg(void);
|
||||
void pack_bandgap(void);
|
||||
void pack_userflash(bool have_emcu);
|
||||
void pack_emcu_and_flash(void);
|
||||
|
||||
void run(void);
|
||||
|
||||
private:
|
||||
// IO
|
||||
void make_iob_nets(CellInfo &iob);
|
||||
void config_simple_io(CellInfo &ci);
|
||||
void config_bottom_row(CellInfo &ci, Loc loc, uint8_t cnd = Bottom_io_POD::NORMAL);
|
||||
void trim_nextpnr_iobs(void);
|
||||
BelId bind_io(CellInfo &ci);
|
||||
std::pair<CellInfo *, CellInfo *> get_pn_cells(const CellInfo &ci);
|
||||
void mark_iobs_as_diff(CellInfo &ci, std::pair<CellInfo *, CellInfo *> &pn_cells);
|
||||
void switch_diff_ports(CellInfo &ci, std::pair<CellInfo *, CellInfo *> &pn_cells,
|
||||
std::vector<IdString> &nets_to_remove);
|
||||
|
||||
static bool is_iob(const Context *ctx, CellInfo *cell) { return is_io(cell); }
|
||||
|
||||
// IOLOGIC
|
||||
void set_daaj_nets(CellInfo &ci, BelId bel);
|
||||
BelId get_iologico_bel(CellInfo *iob);
|
||||
BelId get_iologici_bel(CellInfo *iob);
|
||||
void check_iologic_placement(CellInfo &ci, Loc iob_loc, int diff);
|
||||
void pack_bi_output_iol(CellInfo &ci, std::vector<IdString> &nets_to_remove);
|
||||
void pack_single_output_iol(CellInfo &ci, std::vector<IdString> &nets_to_remove);
|
||||
BelId get_aux_iologic_bel(const CellInfo &ci);
|
||||
bool is_diff_io(BelId bel);
|
||||
bool is_mipi_io(BelId bel);
|
||||
CellInfo *create_aux_iologic_cell(CellInfo &ci, IdString mode, bool io16 = false, int idx = 0);
|
||||
void reconnect_ides_outs(CellInfo *ci);
|
||||
void pack_ides_iol(CellInfo &ci, std::vector<IdString> &nets_to_remove);
|
||||
|
||||
// 16 SERDES
|
||||
void check_io16_placement(CellInfo &ci, Loc main_loc, Loc aux_off, int diff);
|
||||
void pack_oser16(CellInfo &ci, std::vector<IdString> &nets_to_remove);
|
||||
void pack_ides16(CellInfo &ci, std::vector<IdString> &nets_to_remove);
|
||||
|
||||
// LUTs
|
||||
std::unique_ptr<CellInfo> alu_add_cin_block(Context *ctx, CellInfo *head, NetInfo *cin_net, bool cin_is_vcc,
|
||||
bool cin_is_gnd);
|
||||
std::unique_ptr<CellInfo> alu_add_cout_block(Context *ctx, CellInfo *tail, NetInfo *cout_net);
|
||||
std::unique_ptr<CellInfo> alu_add_dummy_block(Context *ctx, CellInfo *tail);
|
||||
void optimize_alu_lut(CellInfo *ci, int mode);
|
||||
void constrain_lutffs(void);
|
||||
std::unique_ptr<CellInfo> ssram_make_lut(Context *ctx, CellInfo *ci, int index);
|
||||
|
||||
// BSRAM
|
||||
void bsram_rename_ports(CellInfo *ci, int bit_width, char const *from, char const *to, int offset = 0);
|
||||
void bsram_fix_blksel(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells);
|
||||
void bsram_fix_outreg(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells);
|
||||
void bsram_fix_sp(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells);
|
||||
void pack_ROM(CellInfo *ci);
|
||||
void divide_sdp(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells);
|
||||
void pack_SDPB(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells);
|
||||
void pack_DPB(CellInfo *ci);
|
||||
void divide_sp(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells);
|
||||
void pack_SP(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells);
|
||||
|
||||
// DSP
|
||||
void pass_net_type(CellInfo *ci, IdString port);
|
||||
};
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -0,0 +1,694 @@
|
|||
#include "design_utils.h"
|
||||
#include "log.h"
|
||||
#include "nextpnr.h"
|
||||
|
||||
#define HIMBAECHEL_CONSTIDS "uarch/gowin/constids.inc"
|
||||
#include "himbaechel_constids.h"
|
||||
#include "himbaechel_helpers.h"
|
||||
|
||||
#include "gowin.h"
|
||||
#include "gowin_utils.h"
|
||||
#include "pack.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
// ===================================
|
||||
// Block RAM
|
||||
// ===================================
|
||||
void GowinPacker::bsram_rename_ports(CellInfo *ci, int bit_width, char const *from, char const *to, int offset)
|
||||
{
|
||||
int num = (bit_width == 9 || bit_width == 18 || bit_width == 36) ? 36 : 32;
|
||||
for (int i = 0, j = offset; i < num; ++i, ++j) {
|
||||
if (((i + 1) % 9) == 0 && (bit_width == 16 || bit_width == 32)) {
|
||||
++j;
|
||||
}
|
||||
ci->renamePort(ctx->idf(from, i), ctx->idf(to, offset ? j % 36 : j));
|
||||
}
|
||||
}
|
||||
|
||||
// We solve the BLKSEL problems that are observed on some chips by
|
||||
// connecting the BLKSEL ports to constant networks so that this BSRAM will
|
||||
// be selected, the actual selection is made by manipulating the Clock
|
||||
// Enable pin using a LUT-based decoder.
|
||||
void GowinPacker::bsram_fix_blksel(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells)
|
||||
{
|
||||
// is BSRAM enabled
|
||||
NetInfo *ce_net = ci->getPort(id_CE);
|
||||
if (ce_net == nullptr || ce_net->name == ctx->id("$PACKER_GND")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// port name, BLK_SEL parameter for this port
|
||||
std::vector<std::pair<IdString, int>> dyn_blksel;
|
||||
|
||||
int blk_sel_parameter = ci->params.at(id_BLK_SEL).as_int64();
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
IdString pin_name = ctx->idf("BLKSEL[%d]", i);
|
||||
NetInfo *net = ci->getPort(pin_name);
|
||||
if (net == nullptr || net->name == ctx->id("$PACKER_GND") || net->name == ctx->id("$PACKER_VCC")) {
|
||||
continue;
|
||||
}
|
||||
dyn_blksel.push_back(std::make_pair(pin_name, (blk_sel_parameter >> i) & 1));
|
||||
}
|
||||
|
||||
if (dyn_blksel.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->verbose) {
|
||||
log_info(" apply the BSRAM BLKSEL fix\n");
|
||||
}
|
||||
|
||||
// Make a decoder
|
||||
auto lut_cell = gwu.create_cell(gwu.create_aux_name(ci->name, 0, "_blksel_lut$"), id_LUT4);
|
||||
CellInfo *lut = lut_cell.get();
|
||||
lut->addInput(id_I3);
|
||||
ci->movePortTo(id_CE, lut, id_I3);
|
||||
lut->addOutput(id_F);
|
||||
ci->connectPorts(id_CE, lut, id_F);
|
||||
|
||||
NetInfo *vcc_net = ctx->nets.at(ctx->id("$PACKER_VCC")).get();
|
||||
NetInfo *vss_net = ctx->nets.at(ctx->id("$PACKER_GND")).get();
|
||||
|
||||
// Connected CE to I3 to make it easy to calculate the decoder
|
||||
int init = 0x100; // CE == 0 --> F = 0
|
||||
// CE == 1 --> F = decoder result
|
||||
int idx = 0;
|
||||
for (auto &port : dyn_blksel) {
|
||||
IdString lut_input_name = ctx->idf("I%d", idx);
|
||||
ci->movePortTo(port.first, lut, lut_input_name);
|
||||
if (port.second) {
|
||||
init <<= (1 << idx);
|
||||
ci->connectPort(port.first, vcc_net);
|
||||
} else {
|
||||
ci->connectPort(port.first, vss_net);
|
||||
}
|
||||
++idx;
|
||||
}
|
||||
lut->setParam(id_INIT, init);
|
||||
|
||||
new_cells.push_back(std::move(lut_cell));
|
||||
}
|
||||
|
||||
// Some chips cannot, for some reason, use internal BSRAM registers to
|
||||
// implement READ_MODE=1'b1 (pipeline) with a word width other than 32 or
|
||||
// 36 bits.
|
||||
// We work around this by adding an external DFF and using BSRAM
|
||||
// as READ_MODE=1'b0 (bypass).
|
||||
void GowinPacker::bsram_fix_outreg(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells)
|
||||
{
|
||||
int bit_width = ci->params.at(id_BIT_WIDTH).as_int64();
|
||||
if (bit_width == 32 || bit_width == 36) {
|
||||
return;
|
||||
}
|
||||
int read_mode = ci->params.at(id_READ_MODE).as_int64();
|
||||
if (read_mode == 0) {
|
||||
return;
|
||||
}
|
||||
NetInfo *ce_net = ci->getPort(id_CE);
|
||||
NetInfo *oce_net = ci->getPort(id_OCE);
|
||||
if (ce_net == nullptr || oce_net == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (ce_net->name == ctx->id("$PACKER_GND") || oce_net->name == ctx->id("$PACKER_GND")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->verbose) {
|
||||
log_info(" apply the BSRAM OUTREG fix\n");
|
||||
}
|
||||
ci->setParam(id_READ_MODE, 0);
|
||||
ci->disconnectPort(id_OCE);
|
||||
ci->connectPort(id_OCE, ce_net);
|
||||
|
||||
NetInfo *reset_net = ci->getPort(id_RESET);
|
||||
bool sync_reset = ci->params.at(id_RESET_MODE).as_string() == std::string("SYNC");
|
||||
IdString dff_type = sync_reset ? id_DFFRE : id_DFFCE;
|
||||
IdString reset_port = sync_reset ? id_RESET : id_CLEAR;
|
||||
|
||||
for (int i = 0; i < bit_width; ++i) {
|
||||
IdString do_name = ctx->idf("DO[%d]", i);
|
||||
const NetInfo *net = ci->getPort(do_name);
|
||||
if (net != nullptr) {
|
||||
if (net->users.empty()) {
|
||||
ci->disconnectPort(do_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
// create DFF
|
||||
auto cache_dff_cell = gwu.create_cell(gwu.create_aux_name(ci->name, i, "_cache_dff$"), dff_type);
|
||||
CellInfo *cache_dff = cache_dff_cell.get();
|
||||
cache_dff->addInput(id_CE);
|
||||
cache_dff->connectPort(id_CE, oce_net);
|
||||
|
||||
cache_dff->addInput(reset_port);
|
||||
cache_dff->connectPort(reset_port, reset_net);
|
||||
|
||||
ci->copyPortTo(id_CLK, cache_dff, id_CLK);
|
||||
|
||||
cache_dff->addOutput(id_Q);
|
||||
ci->movePortTo(do_name, cache_dff, id_Q);
|
||||
|
||||
cache_dff->addInput(id_D);
|
||||
ci->connectPorts(do_name, cache_dff, id_D);
|
||||
|
||||
new_cells.push_back(std::move(cache_dff_cell));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Analysis of the images generated by the IDE showed that some components
|
||||
// are being added at the input and output of the BSRAM. Two LUTs are
|
||||
// added on the WRE and CE inputs (strangely, OCE is not affected), a pair
|
||||
// of LUT-DFFs on each DO output, and one or two flipflops of different
|
||||
// types in the auxiliary network.
|
||||
// The semantics of these additions are unclear, but we can replicate this behavior.
|
||||
// Fix BSRAM in single port mode.
|
||||
void GowinPacker::bsram_fix_sp(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells)
|
||||
{
|
||||
int bit_width = ci->params.at(id_BIT_WIDTH).as_int64();
|
||||
|
||||
if (ctx->verbose) {
|
||||
log_info(" apply the SP fix\n");
|
||||
}
|
||||
// create WRE LUT
|
||||
auto wre_lut_cell = gwu.create_cell(gwu.create_aux_name(ci->name, 0, "_wre_lut$"), id_LUT4);
|
||||
CellInfo *wre_lut = wre_lut_cell.get();
|
||||
wre_lut->setParam(id_INIT, 0x8888);
|
||||
ci->movePortTo(id_CE, wre_lut, id_I0);
|
||||
ci->movePortTo(id_WRE, wre_lut, id_I1);
|
||||
wre_lut->addOutput(id_F);
|
||||
ci->connectPorts(id_WRE, wre_lut, id_F);
|
||||
|
||||
// create CE LUT
|
||||
auto ce_lut_cell = gwu.create_cell(gwu.create_aux_name(ci->name, 0, "_ce_lut$"), id_LUT4);
|
||||
CellInfo *ce_lut = ce_lut_cell.get();
|
||||
ce_lut->setParam(id_INIT, 0xeeee);
|
||||
wre_lut->copyPortTo(id_I0, ce_lut, id_I0);
|
||||
wre_lut->copyPortTo(id_I1, ce_lut, id_I1);
|
||||
ce_lut->addOutput(id_F);
|
||||
ci->connectPorts(id_CE, ce_lut, id_F);
|
||||
|
||||
// create ce reg
|
||||
int write_mode = ci->params.at(id_WRITE_MODE).as_int64();
|
||||
IdString dff_type = write_mode ? id_DFF : id_DFFR;
|
||||
auto ce_pre_dff_cell = gwu.create_cell(gwu.create_aux_name(ci->name, 0, "_ce_pre_dff$"), dff_type);
|
||||
CellInfo *ce_pre_dff = ce_pre_dff_cell.get();
|
||||
ce_pre_dff->addInput(id_D);
|
||||
ce_lut->copyPortTo(id_I0, ce_pre_dff, id_D);
|
||||
ci->copyPortTo(id_CLK, ce_pre_dff, id_CLK);
|
||||
if (dff_type == id_DFFR) {
|
||||
wre_lut->copyPortTo(id_I1, ce_pre_dff, id_RESET);
|
||||
}
|
||||
ce_pre_dff->addOutput(id_Q);
|
||||
|
||||
// new ce src with Q pin (used by output pins, not by BSRAM itself)
|
||||
CellInfo *new_ce_net_src = ce_pre_dff;
|
||||
|
||||
// add delay register in pipeline mode
|
||||
int read_mode = ci->params.at(id_READ_MODE).as_int64();
|
||||
if (read_mode) {
|
||||
auto ce_pipe_dff_cell = gwu.create_cell(gwu.create_aux_name(ci->name, 0, "_ce_pipe_dff$"), id_DFF);
|
||||
new_cells.push_back(std::move(ce_pipe_dff_cell));
|
||||
CellInfo *ce_pipe_dff = new_cells.back().get();
|
||||
ce_pipe_dff->addInput(id_D);
|
||||
new_ce_net_src->connectPorts(id_Q, ce_pipe_dff, id_D);
|
||||
ci->copyPortTo(id_CLK, ce_pipe_dff, id_CLK);
|
||||
ce_pipe_dff->addOutput(id_Q);
|
||||
new_ce_net_src = ce_pipe_dff;
|
||||
}
|
||||
|
||||
// used outputs of the BSRAM convert to cached
|
||||
for (int i = 0; i < bit_width; ++i) {
|
||||
IdString do_name = ctx->idf("DO[%d]", i);
|
||||
const NetInfo *net = ci->getPort(do_name);
|
||||
if (net != nullptr) {
|
||||
if (net->users.empty()) {
|
||||
ci->disconnectPort(do_name);
|
||||
continue;
|
||||
}
|
||||
// create cache lut
|
||||
auto cache_lut_cell = gwu.create_cell(gwu.create_aux_name(ci->name, i, "_cache_lut$"), id_LUT4);
|
||||
CellInfo *cache_lut = cache_lut_cell.get();
|
||||
cache_lut->setParam(id_INIT, 0xcaca);
|
||||
cache_lut->addInput(id_I0);
|
||||
cache_lut->addInput(id_I1);
|
||||
cache_lut->addInput(id_I2);
|
||||
ci->movePortTo(do_name, cache_lut, id_F);
|
||||
ci->connectPorts(do_name, cache_lut, id_I1);
|
||||
new_ce_net_src->connectPorts(id_Q, cache_lut, id_I2);
|
||||
|
||||
// create cache DFF
|
||||
auto cache_dff_cell = gwu.create_cell(gwu.create_aux_name(ci->name, i, "_cache_dff$"), id_DFFE);
|
||||
CellInfo *cache_dff = cache_dff_cell.get();
|
||||
cache_dff->addInput(id_CE);
|
||||
cache_dff->addInput(id_D);
|
||||
ci->copyPortTo(id_CLK, cache_dff, id_CLK);
|
||||
new_ce_net_src->connectPorts(id_Q, cache_dff, id_CE);
|
||||
cache_lut->copyPortTo(id_I1, cache_dff, id_D);
|
||||
cache_dff->addOutput(id_Q);
|
||||
cache_dff->connectPorts(id_Q, cache_lut, id_I0);
|
||||
|
||||
new_cells.push_back(std::move(cache_lut_cell));
|
||||
new_cells.push_back(std::move(cache_dff_cell));
|
||||
}
|
||||
}
|
||||
|
||||
new_cells.push_back(std::move(wre_lut_cell));
|
||||
new_cells.push_back(std::move(ce_lut_cell));
|
||||
new_cells.push_back(std::move(ce_pre_dff_cell));
|
||||
}
|
||||
|
||||
void GowinPacker::pack_ROM(CellInfo *ci)
|
||||
{
|
||||
int default_bw = 32;
|
||||
// XXX use block 111
|
||||
ci->setParam(ctx->id("BLK_SEL"), Property(7, 32));
|
||||
if (ci->type == id_pROM) {
|
||||
ci->setAttr(id_BSRAM_SUBTYPE, Property(""));
|
||||
} else {
|
||||
ci->setAttr(id_BSRAM_SUBTYPE, Property("X9"));
|
||||
default_bw = 36;
|
||||
}
|
||||
|
||||
NetInfo *vcc_net = ctx->nets.at(ctx->id("$PACKER_VCC")).get();
|
||||
NetInfo *vss_net = ctx->nets.at(ctx->id("$PACKER_GND")).get();
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
IdString port = ctx->idf("BLKSEL%d", i);
|
||||
ci->addInput(port);
|
||||
ci->connectPort(port, vcc_net);
|
||||
port = ctx->idf("BLKSELB%d", i);
|
||||
ci->addInput(port);
|
||||
ci->connectPort(port, vcc_net);
|
||||
}
|
||||
|
||||
ci->addInput(id_WRE);
|
||||
ci->connectPort(id_WRE, vss_net);
|
||||
ci->addInput(id_WREB);
|
||||
ci->connectPort(id_WREB, vss_net);
|
||||
|
||||
if (!ci->params.count(id_BIT_WIDTH)) {
|
||||
ci->setParam(id_BIT_WIDTH, Property(default_bw, 32));
|
||||
}
|
||||
|
||||
int bit_width = ci->params.at(id_BIT_WIDTH).as_int64();
|
||||
if (bit_width == 32 || bit_width == 36) {
|
||||
ci->copyPortTo(id_CLK, ci, id_CLKB);
|
||||
ci->copyPortTo(id_CE, ci, id_CEB);
|
||||
ci->copyPortTo(id_OCE, ci, id_OCEB);
|
||||
ci->copyPortTo(id_RESET, ci, id_RESETB);
|
||||
|
||||
for (int i = 0; i < 14; ++i) {
|
||||
ci->renamePort(ctx->idf("AD[%d]", i), ctx->idf("ADA%d", i));
|
||||
ci->copyPortTo(ctx->idf("ADA%d", i), ci, ctx->idf("ADB%d", i));
|
||||
}
|
||||
bsram_rename_ports(ci, bit_width, "DO[%d]", "DO%d");
|
||||
} else {
|
||||
// use port B
|
||||
ci->renamePort(id_CLK, id_CLKB);
|
||||
ci->renamePort(id_OCE, id_OCEB);
|
||||
ci->renamePort(id_CE, id_CEB);
|
||||
ci->renamePort(id_RESET, id_RESETB);
|
||||
|
||||
ci->addInput(id_CEA);
|
||||
ci->connectPort(id_CEA, vss_net);
|
||||
for (int i = 0; i < 14; ++i) {
|
||||
ci->renamePort(ctx->idf("AD[%d]", i), ctx->idf("ADB%d", i));
|
||||
}
|
||||
bsram_rename_ports(ci, bit_width, "DO[%d]", "DO%d", 18);
|
||||
}
|
||||
}
|
||||
|
||||
void GowinPacker::divide_sdp(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells)
|
||||
{
|
||||
if (ctx->verbose) {
|
||||
log_info(" divide SDP\n");
|
||||
}
|
||||
|
||||
int bw = ci->params.at(id_BIT_WIDTH_0).as_int64();
|
||||
NetInfo *vcc_net = ctx->nets.at(ctx->id("$PACKER_VCC")).get();
|
||||
NetInfo *vss_net = ctx->nets.at(ctx->id("$PACKER_GND")).get();
|
||||
|
||||
IdString cell_type = bw == 32 ? id_SDPB : id_SDPX9B;
|
||||
IdString name = ctx->idf("%s_AUX", ctx->nameOf(ci));
|
||||
|
||||
auto sdp_cell = gwu.create_cell(name, cell_type);
|
||||
CellInfo *sdp = sdp_cell.get();
|
||||
sdp->setAttr(id_AUX, 1);
|
||||
|
||||
int new_bw = bw / 2;
|
||||
ci->setParam(id_BIT_WIDTH_0, new_bw);
|
||||
ci->setParam(id_BIT_WIDTH_1, new_bw);
|
||||
sdp->params = ci->params;
|
||||
sdp->setParam(id_BIT_WIDTH_0, new_bw);
|
||||
sdp->setParam(id_BIT_WIDTH_1, new_bw);
|
||||
|
||||
// copy control ports
|
||||
ci->copyPortBusTo(ctx->id("BLKSELA"), 0, true, sdp, ctx->id("BLKSELA"), 0, true, 3);
|
||||
ci->copyPortBusTo(ctx->id("BLKSELB"), 0, true, sdp, ctx->id("BLKSELB"), 0, true, 3);
|
||||
ci->copyPortTo(id_CEA, sdp, id_CEA);
|
||||
ci->copyPortTo(id_CEB, sdp, id_CEB);
|
||||
ci->copyPortTo(id_CLKA, sdp, id_CLKA);
|
||||
ci->copyPortTo(id_CLKB, sdp, id_CLKB);
|
||||
ci->copyPortTo(id_OCE, sdp, id_OCE);
|
||||
ci->copyPortTo(id_RESET, sdp, id_RESET);
|
||||
|
||||
// Separate port A
|
||||
ci->movePortTo(ctx->id("ADA[2]"), sdp, ctx->id("ADA[0]"));
|
||||
ci->movePortTo(ctx->id("ADA[3]"), sdp, ctx->id("ADA[1]"));
|
||||
|
||||
ci->addInput(ctx->id("ADA[2]"));
|
||||
ci->addInput(ctx->id("ADA[3]"));
|
||||
ci->connectPort(ctx->id("ADA[2]"), vss_net);
|
||||
ci->connectPort(ctx->id("ADA[3]"), vss_net);
|
||||
|
||||
sdp->addInput(ctx->id("ADA[2]"));
|
||||
sdp->addInput(ctx->id("ADA[3]"));
|
||||
sdp->connectPort(ctx->id("ADA[2]"), vss_net);
|
||||
sdp->connectPort(ctx->id("ADA[3]"), vss_net);
|
||||
|
||||
ci->disconnectPort(ctx->id("ADA[4]"));
|
||||
ci->connectPort(ctx->id("ADA[4]"), vss_net);
|
||||
sdp->addInput(ctx->id("ADA[4]"));
|
||||
sdp->connectPort(ctx->id("ADA[4]"), vcc_net);
|
||||
|
||||
ci->copyPortBusTo(id_ADA, 5, true, sdp, id_ADA, 5, true, 9);
|
||||
|
||||
// Separate port B
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
IdString port = ctx->idf("ADB[%d]", i);
|
||||
ci->disconnectPort(port);
|
||||
ci->connectPort(port, vss_net);
|
||||
ci->copyPortTo(port, sdp, port);
|
||||
}
|
||||
|
||||
ci->disconnectPort(ctx->id("ADB[4]"));
|
||||
ci->connectPort(ctx->id("ADB[4]"), vss_net);
|
||||
sdp->addInput(ctx->id("ADB[4]"));
|
||||
sdp->connectPort(ctx->id("ADB[4]"), vcc_net);
|
||||
|
||||
ci->copyPortBusTo(id_ADB, 5, true, sdp, id_ADB, 5, true, 9);
|
||||
|
||||
ci->movePortBusTo(id_DI, new_bw, true, sdp, id_DI, 0, true, new_bw);
|
||||
ci->movePortBusTo(id_DO, new_bw, true, sdp, id_DO, 0, true, new_bw);
|
||||
|
||||
new_cells.push_back(std::move(sdp_cell));
|
||||
}
|
||||
|
||||
void GowinPacker::pack_SDPB(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells)
|
||||
{
|
||||
int default_bw = 32;
|
||||
if (ci->type == id_SDPB) {
|
||||
ci->setAttr(id_BSRAM_SUBTYPE, Property(""));
|
||||
} else {
|
||||
ci->setAttr(id_BSRAM_SUBTYPE, Property("X9"));
|
||||
default_bw = 36;
|
||||
}
|
||||
|
||||
if (!ci->params.count(id_BIT_WIDTH_0)) {
|
||||
ci->setParam(id_BIT_WIDTH_0, Property(default_bw, 32));
|
||||
}
|
||||
if (!ci->params.count(id_BIT_WIDTH_1)) {
|
||||
ci->setParam(id_BIT_WIDTH_1, Property(default_bw, 32));
|
||||
}
|
||||
|
||||
int bit_width = ci->params.at(id_BIT_WIDTH_0).as_int64();
|
||||
|
||||
if ((bit_width == 32 || bit_width == 36) && gwu.need_SDP_fix()) {
|
||||
int bit_width_b = ci->params.at(id_BIT_WIDTH_1).as_int64();
|
||||
if (bit_width == bit_width_b) {
|
||||
divide_sdp(ci, new_cells);
|
||||
} else {
|
||||
log_error("The fix for SDP when ports A and B have different bit widths has not yet been implemented. "
|
||||
"Cell: '%s'\n",
|
||||
ci->type.c_str(ctx));
|
||||
}
|
||||
}
|
||||
|
||||
NetInfo *vcc_net = ctx->nets.at(ctx->id("$PACKER_VCC")).get();
|
||||
NetInfo *vss_net = ctx->nets.at(ctx->id("$PACKER_GND")).get();
|
||||
|
||||
for (int i = 0; i < 14; ++i) {
|
||||
ci->renamePort(ctx->idf("ADA[%d]", i), ctx->idf("ADA%d", i));
|
||||
ci->renamePort(ctx->idf("ADB[%d]", i), ctx->idf("ADB%d", i));
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ci->renamePort(ctx->idf("BLKSELA[%d]", i), ctx->idf("BLKSELA%d", i));
|
||||
ci->renamePort(ctx->idf("BLKSELB[%d]", i), ctx->idf("BLKSELB%d", i));
|
||||
}
|
||||
|
||||
ci->copyPortTo(id_OCE, ci, id_OCEB);
|
||||
|
||||
// If misconnected RESET
|
||||
if (gwu.need_BSRAM_RESET_fix()) {
|
||||
ci->renamePort(id_RESET, id_RESETB);
|
||||
}
|
||||
|
||||
// Port A
|
||||
ci->addInput(id_WREA);
|
||||
ci->connectPort(id_WREA, vcc_net);
|
||||
|
||||
// Port B
|
||||
ci->addInput(id_WREB);
|
||||
bit_width = ci->params.at(id_BIT_WIDTH_1).as_int64();
|
||||
|
||||
if (bit_width == 32 || bit_width == 36) {
|
||||
ci->connectPort(id_WREB, vcc_net);
|
||||
bsram_rename_ports(ci, bit_width, "DO[%d]", "DO%d");
|
||||
} else {
|
||||
ci->connectPort(id_WREB, vss_net);
|
||||
bsram_rename_ports(ci, bit_width, "DO[%d]", "DO%d", 18);
|
||||
}
|
||||
bsram_rename_ports(ci, bit_width, "DI[%d]", "DI%d");
|
||||
}
|
||||
|
||||
void GowinPacker::pack_DPB(CellInfo *ci)
|
||||
{
|
||||
int default_bw = 16;
|
||||
if (ci->type == id_DPB) {
|
||||
ci->setAttr(id_BSRAM_SUBTYPE, Property(""));
|
||||
} else {
|
||||
ci->setAttr(id_BSRAM_SUBTYPE, Property("X9"));
|
||||
default_bw = 18;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 14; ++i) {
|
||||
ci->renamePort(ctx->idf("ADA[%d]", i), ctx->idf("ADA%d", i));
|
||||
ci->renamePort(ctx->idf("ADB[%d]", i), ctx->idf("ADB%d", i));
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ci->renamePort(ctx->idf("BLKSELA[%d]", i), ctx->idf("BLKSELA%d", i));
|
||||
ci->renamePort(ctx->idf("BLKSELB[%d]", i), ctx->idf("BLKSELB%d", i));
|
||||
}
|
||||
|
||||
if (!ci->params.count(id_BIT_WIDTH_0)) {
|
||||
ci->setParam(id_BIT_WIDTH_0, Property(default_bw, 32));
|
||||
}
|
||||
int bit_width = ci->params.at(id_BIT_WIDTH_0).as_int64();
|
||||
bsram_rename_ports(ci, bit_width, "DIA[%d]", "DIA%d");
|
||||
bsram_rename_ports(ci, bit_width, "DOA[%d]", "DOA%d");
|
||||
|
||||
if (!ci->params.count(id_BIT_WIDTH_1)) {
|
||||
ci->setParam(id_BIT_WIDTH_1, Property(default_bw, 32));
|
||||
}
|
||||
bit_width = ci->params.at(id_BIT_WIDTH_1).as_int64();
|
||||
bsram_rename_ports(ci, bit_width, "DIB[%d]", "DIB%d");
|
||||
bsram_rename_ports(ci, bit_width, "DOB[%d]", "DOB%d");
|
||||
}
|
||||
|
||||
void GowinPacker::divide_sp(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells)
|
||||
{
|
||||
if (ctx->verbose) {
|
||||
log_info(" divide SP\n");
|
||||
}
|
||||
|
||||
int bw = ci->params.at(id_BIT_WIDTH).as_int64();
|
||||
NetInfo *vcc_net = ctx->nets.at(ctx->id("$PACKER_VCC")).get();
|
||||
NetInfo *vss_net = ctx->nets.at(ctx->id("$PACKER_GND")).get();
|
||||
|
||||
IdString cell_type = bw == 32 ? id_SP : id_SPX9;
|
||||
IdString name = ctx->idf("%s_AUX", ctx->nameOf(ci));
|
||||
|
||||
auto sp_cell = gwu.create_cell(name, cell_type);
|
||||
CellInfo *sp = sp_cell.get();
|
||||
sp->setAttr(id_AUX, 1);
|
||||
|
||||
ci->copyPortTo(id_CLK, sp, id_CLK);
|
||||
ci->copyPortTo(id_OCE, sp, id_OCE);
|
||||
ci->copyPortTo(id_CE, sp, id_CE);
|
||||
ci->copyPortTo(id_RESET, sp, id_RESET);
|
||||
ci->copyPortTo(id_WRE, sp, id_WRE);
|
||||
|
||||
// XXX Separate "byte enable" port
|
||||
ci->movePortTo(ctx->id("AD[2]"), sp, ctx->id("AD[0]"));
|
||||
ci->movePortTo(ctx->id("AD[3]"), sp, ctx->id("AD[1]"));
|
||||
ci->connectPort(ctx->id("AD[2]"), vss_net);
|
||||
ci->connectPort(ctx->id("AD[3]"), vss_net);
|
||||
|
||||
sp->addInput(ctx->id("AD[2]"));
|
||||
sp->connectPort(ctx->id("AD[2]"), vss_net);
|
||||
sp->addInput(ctx->id("AD[3]"));
|
||||
sp->connectPort(ctx->id("AD[3]"), vss_net);
|
||||
|
||||
ci->disconnectPort(ctx->id("AD[4]"));
|
||||
ci->connectPort(ctx->id("AD[4]"), vss_net);
|
||||
sp->addInput(ctx->id("AD[4]"));
|
||||
sp->connectPort(ctx->id("AD[4]"), vcc_net);
|
||||
|
||||
ci->copyPortBusTo(id_AD, 5, true, sp, id_AD, 5, true, 9);
|
||||
|
||||
sp->params = ci->params;
|
||||
|
||||
bw /= 2;
|
||||
ci->setParam(id_BIT_WIDTH, Property(bw, 32));
|
||||
sp->setParam(id_BIT_WIDTH, Property(bw, 32));
|
||||
ci->movePortBusTo(id_DI, bw, true, sp, id_DI, 0, true, bw);
|
||||
ci->movePortBusTo(id_DO, bw, true, sp, id_DO, 0, true, bw);
|
||||
|
||||
ci->copyPortBusTo(ctx->id("BLKSEL"), 0, true, sp, ctx->id("BLKSEL"), 0, true, 3);
|
||||
|
||||
new_cells.push_back(std::move(sp_cell));
|
||||
}
|
||||
|
||||
void GowinPacker::pack_SP(CellInfo *ci, std::vector<std::unique_ptr<CellInfo>> &new_cells)
|
||||
{
|
||||
int default_bw = 32;
|
||||
if (ci->type == id_SP) {
|
||||
ci->setAttr(id_BSRAM_SUBTYPE, Property(""));
|
||||
} else {
|
||||
ci->setAttr(id_BSRAM_SUBTYPE, Property("X9"));
|
||||
default_bw = 36;
|
||||
}
|
||||
if (!ci->params.count(id_BIT_WIDTH)) {
|
||||
ci->setParam(id_BIT_WIDTH, Property(default_bw, 32));
|
||||
}
|
||||
|
||||
int bit_width = ci->params.at(id_BIT_WIDTH).as_int64();
|
||||
|
||||
if (!ci->attrs.count(id_AUX)) {
|
||||
// XXX strange WRE<->CE relations
|
||||
// Gowin IDE adds two LUTs to the WRE and CE signals. The logic is
|
||||
// unclear, but without them effects occur. Perhaps this is a
|
||||
// correction of some BSRAM defects.
|
||||
if (gwu.need_SP_fix()) {
|
||||
bsram_fix_sp(ci, new_cells);
|
||||
}
|
||||
|
||||
// Some chips have faulty output registers
|
||||
if (gwu.need_BSRAM_OUTREG_fix()) {
|
||||
bsram_fix_outreg(ci, new_cells);
|
||||
}
|
||||
|
||||
// Some chips have problems with BLKSEL ports
|
||||
if (gwu.need_BLKSEL_fix()) {
|
||||
bsram_fix_blksel(ci, new_cells);
|
||||
}
|
||||
}
|
||||
|
||||
// XXX UG285-1.3.6_E Gowin BSRAM & SSRAM User Guide:
|
||||
// For GW1N-9/GW1NR-9/GW1NS-4 series, 32/36-bit SP/SPX9 is divided into two
|
||||
// SP/SPX9s, which occupy two BSRAMs.
|
||||
// So divide it here
|
||||
if ((bit_width == 32 || bit_width == 36) && !gwu.has_SP32()) {
|
||||
divide_sp(ci, new_cells);
|
||||
bit_width = ci->params.at(id_BIT_WIDTH).as_int64();
|
||||
}
|
||||
|
||||
NetInfo *vcc_net = ctx->nets.at(ctx->id("$PACKER_VCC")).get();
|
||||
NetInfo *gnd_net = ctx->nets.at(ctx->id("$PACKER_GND")).get();
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ci->renamePort(ctx->idf("BLKSEL[%d]", i), ctx->idf("BLKSEL%d", i));
|
||||
if (bit_width == 32 || bit_width == 36) {
|
||||
ci->copyPortTo(ctx->idf("BLKSEL%d", i), ci, ctx->idf("BLKSELB%d", i));
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 14; ++i) {
|
||||
ci->renamePort(ctx->idf("AD[%d]", i), ctx->idf("AD%d", i));
|
||||
if (bit_width == 32 || bit_width == 36) {
|
||||
// Since we are dividing 32/36 bits into two parts between
|
||||
// ports A and B, the ‘Byte Enables’ require special
|
||||
// separation.
|
||||
if (i < 4) {
|
||||
if (i > 1) {
|
||||
ci->movePortTo(ctx->idf("AD%d", i), ci, ctx->idf("ADB%d", i - 2));
|
||||
ci->connectPort(ctx->idf("AD%d", i), gnd_net);
|
||||
ci->addInput(ctx->idf("ADB%d", i));
|
||||
ci->connectPort(ctx->idf("ADB%d", i), gnd_net);
|
||||
}
|
||||
} else {
|
||||
ci->copyPortTo(ctx->idf("AD%d", i), ci, ctx->idf("ADB%d", i));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bit_width == 32 || bit_width == 36) {
|
||||
ci->copyPortTo(id_CLK, ci, id_CLKB);
|
||||
ci->copyPortTo(id_OCE, ci, id_OCEB);
|
||||
ci->copyPortTo(id_CE, ci, id_CEB);
|
||||
ci->copyPortTo(id_RESET, ci, id_RESETB);
|
||||
ci->copyPortTo(id_WRE, ci, id_WREB);
|
||||
ci->disconnectPort(ctx->id("AD4"));
|
||||
ci->connectPort(ctx->id("AD4"), gnd_net);
|
||||
ci->disconnectPort(ctx->id("ADB4"));
|
||||
ci->connectPort(ctx->id("ADB4"), vcc_net);
|
||||
}
|
||||
bsram_rename_ports(ci, bit_width, "DI[%d]", "DI%d");
|
||||
bsram_rename_ports(ci, bit_width, "DO[%d]", "DO%d");
|
||||
}
|
||||
|
||||
void GowinPacker::pack_bsram(void)
|
||||
{
|
||||
std::vector<std::unique_ptr<CellInfo>> new_cells;
|
||||
log_info("Pack BSRAMs...\n");
|
||||
|
||||
auto do_bsram = [&](CellInfo *ci) {
|
||||
if (ctx->verbose) {
|
||||
log_info(" pack %s\n", ci->type.c_str(ctx));
|
||||
}
|
||||
switch (ci->type.hash()) {
|
||||
case ID_pROMX9: /* fallthrough */
|
||||
case ID_pROM:
|
||||
pack_ROM(ci);
|
||||
ci->type = id_ROM;
|
||||
break;
|
||||
case ID_SDPX9B: /* fallthrough */
|
||||
case ID_SDPB:
|
||||
pack_SDPB(ci, new_cells);
|
||||
ci->type = id_SDP;
|
||||
break;
|
||||
case ID_DPX9B: /* fallthrough */
|
||||
case ID_DPB:
|
||||
pack_DPB(ci);
|
||||
ci->type = id_DP;
|
||||
break;
|
||||
case ID_SPX9: /* fallthrough */
|
||||
case ID_SP:
|
||||
pack_SP(ci, new_cells);
|
||||
ci->type = id_SP;
|
||||
break;
|
||||
default:
|
||||
log_error("Unsupported BSRAM type '%s'\n", ci->type.c_str(ctx));
|
||||
}
|
||||
};
|
||||
|
||||
for (auto &cell : ctx->cells) {
|
||||
auto ci = cell.second.get();
|
||||
if (is_bsram(ci)) {
|
||||
do_bsram(ci);
|
||||
}
|
||||
}
|
||||
|
||||
// Process new cells. New cells should not generate more.
|
||||
for (auto &cell : new_cells) {
|
||||
auto ci = cell.get();
|
||||
if (is_bsram(ci)) {
|
||||
do_bsram(ci);
|
||||
}
|
||||
ctx->cells[cell->name] = std::move(cell);
|
||||
}
|
||||
}
|
||||
NEXTPNR_NAMESPACE_END
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,870 @@
|
|||
#include "design_utils.h"
|
||||
#include "log.h"
|
||||
#include "nextpnr.h"
|
||||
|
||||
#define HIMBAECHEL_CONSTIDS "uarch/gowin/constids.inc"
|
||||
#include "himbaechel_constids.h"
|
||||
#include "himbaechel_helpers.h"
|
||||
|
||||
#include "gowin.h"
|
||||
#include "gowin_utils.h"
|
||||
#include "pack.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
// ===================================
|
||||
// IO
|
||||
// ===================================
|
||||
// create IOB connections for gowin_pack
|
||||
// can be called repeatedly when switching inputs, disabled outputs do not change
|
||||
void GowinPacker::make_iob_nets(CellInfo &iob)
|
||||
{
|
||||
for (const auto &port : iob.ports) {
|
||||
const NetInfo *net = iob.getPort(port.first);
|
||||
std::string connected_net = "NET";
|
||||
if (net != nullptr) {
|
||||
if (ctx->verbose) {
|
||||
log_info("%s: %s - %s\n", ctx->nameOf(&iob), port.first.c_str(ctx), ctx->nameOf(net));
|
||||
}
|
||||
if (net->name == ctx->id("$PACKER_VCC")) {
|
||||
connected_net = "VCC";
|
||||
} else if (net->name == ctx->id("$PACKER_GND")) {
|
||||
connected_net = "GND";
|
||||
}
|
||||
iob.setParam(ctx->idf("NET_%s", port.first.c_str(ctx)), connected_net);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GowinPacker::config_simple_io(CellInfo &ci)
|
||||
{
|
||||
if (ci.type.in(id_TBUF, id_IOBUF)) {
|
||||
return;
|
||||
}
|
||||
log_info("simple:%s\n", ctx->nameOf(&ci));
|
||||
ci.addInput(id_OEN);
|
||||
if (ci.type == id_OBUF) {
|
||||
ci.connectPort(id_OEN, ctx->nets.at(ctx->id("$PACKER_GND")).get());
|
||||
} else {
|
||||
NPNR_ASSERT(ci.type == id_IBUF);
|
||||
ci.connectPort(id_OEN, ctx->nets.at(ctx->id("$PACKER_VCC")).get());
|
||||
}
|
||||
}
|
||||
|
||||
void GowinPacker::config_bottom_row(CellInfo &ci, Loc loc, uint8_t cnd)
|
||||
{
|
||||
if (!gwu.has_bottom_io_cnds()) {
|
||||
return;
|
||||
}
|
||||
if (!ci.type.in(id_OBUF, id_TBUF, id_IOBUF)) {
|
||||
return;
|
||||
}
|
||||
if (loc.z != BelZ::IOBA_Z) {
|
||||
return;
|
||||
}
|
||||
auto connect_io_wire = [&](IdString port, IdString net_name) {
|
||||
// XXX it is very convenient that nothing terrible happens in case
|
||||
// of absence/presence of a port
|
||||
ci.disconnectPort(port);
|
||||
ci.addInput(port);
|
||||
if (net_name == id_VSS) {
|
||||
ci.connectPort(port, ctx->nets.at(ctx->id("$PACKER_GND")).get());
|
||||
} else {
|
||||
NPNR_ASSERT(net_name == id_VCC);
|
||||
ci.connectPort(port, ctx->nets.at(ctx->id("$PACKER_VCC")).get());
|
||||
}
|
||||
};
|
||||
|
||||
IdString wire_a_net = gwu.get_bottom_io_wire_a_net(cnd);
|
||||
connect_io_wire(id_BOTTOM_IO_PORT_A, wire_a_net);
|
||||
|
||||
IdString wire_b_net = gwu.get_bottom_io_wire_b_net(cnd);
|
||||
connect_io_wire(id_BOTTOM_IO_PORT_B, wire_b_net);
|
||||
}
|
||||
|
||||
// Attributes of deleted cells are copied
|
||||
void GowinPacker::trim_nextpnr_iobs(void)
|
||||
{
|
||||
// Trim nextpnr IOBs - assume IO buffer insertion has been done in synthesis
|
||||
const pool<CellTypePort> top_ports{
|
||||
CellTypePort(id_IBUF, id_I),
|
||||
CellTypePort(id_OBUF, id_O),
|
||||
CellTypePort(id_TBUF, id_O),
|
||||
CellTypePort(id_IOBUF, id_IO),
|
||||
};
|
||||
std::vector<IdString> to_remove;
|
||||
for (auto &cell : ctx->cells) {
|
||||
auto &ci = *cell.second;
|
||||
if (!ci.type.in(ctx->id("$nextpnr_ibuf"), ctx->id("$nextpnr_obuf"), ctx->id("$nextpnr_iobuf")))
|
||||
continue;
|
||||
NetInfo *i = ci.getPort(id_I);
|
||||
if (i && i->driver.cell) {
|
||||
if (!top_ports.count(CellTypePort(i->driver)))
|
||||
log_error("Top-level port '%s' driven by illegal port %s.%s\n", ctx->nameOf(&ci),
|
||||
ctx->nameOf(i->driver.cell), ctx->nameOf(i->driver.port));
|
||||
for (const auto &attr : ci.attrs) {
|
||||
i->driver.cell->setAttr(attr.first, attr.second);
|
||||
}
|
||||
}
|
||||
NetInfo *o = ci.getPort(id_O);
|
||||
if (o) {
|
||||
for (auto &usr : o->users) {
|
||||
if (!top_ports.count(CellTypePort(usr)))
|
||||
log_error("Top-level port '%s' driving illegal port %s.%s\n", ctx->nameOf(&ci),
|
||||
ctx->nameOf(usr.cell), ctx->nameOf(usr.port));
|
||||
for (const auto &attr : ci.attrs) {
|
||||
usr.cell->setAttr(attr.first, attr.second);
|
||||
}
|
||||
// network/port attributes that can be set in the
|
||||
// restriction file and that need to be transferred to real
|
||||
// networks before nextpnr buffers are removed.
|
||||
NetInfo *dst_net = usr.cell->getPort(id_O);
|
||||
if (dst_net != nullptr) {
|
||||
for (const auto &attr : o->attrs) {
|
||||
if (!attr.first.in(id_CLOCK)) {
|
||||
continue;
|
||||
}
|
||||
dst_net->attrs[attr.first] = attr.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
NetInfo *io = ci.getPort(id_IO);
|
||||
if (io && io->driver.cell) {
|
||||
if (!top_ports.count(CellTypePort(io->driver)))
|
||||
log_error("Top-level port '%s' driven by illegal port %s.%s\n", ctx->nameOf(&ci),
|
||||
ctx->nameOf(io->driver.cell), ctx->nameOf(io->driver.port));
|
||||
for (const auto &attr : ci.attrs) {
|
||||
io->driver.cell->setAttr(attr.first, attr.second);
|
||||
}
|
||||
}
|
||||
ci.disconnectPort(id_I);
|
||||
ci.disconnectPort(id_O);
|
||||
ci.disconnectPort(id_IO);
|
||||
to_remove.push_back(ci.name);
|
||||
}
|
||||
for (IdString cell_name : to_remove)
|
||||
ctx->cells.erase(cell_name);
|
||||
}
|
||||
|
||||
BelId GowinPacker::bind_io(CellInfo &ci)
|
||||
{
|
||||
BelId bel = ctx->getBelByNameStr(ci.attrs.at(id_BEL).as_string());
|
||||
if (bel == BelId()) {
|
||||
log_error("No bel named %s\n", ci.attrs.at(id_BEL).as_string().c_str());
|
||||
}
|
||||
if (!ctx->checkBelAvail(bel)) {
|
||||
log_error("Can't place %s at %s because it's already taken by %s\n", ctx->nameOf(&ci), ctx->nameOfBel(bel),
|
||||
ctx->nameOf(ctx->getBoundBelCell(bel)));
|
||||
}
|
||||
ci.unsetAttr(id_BEL);
|
||||
ctx->bindBel(bel, &ci, PlaceStrength::STRENGTH_LOCKED);
|
||||
return bel;
|
||||
}
|
||||
|
||||
void GowinPacker::pack_iobs(void)
|
||||
{
|
||||
log_info("Pack IOBs...\n");
|
||||
trim_nextpnr_iobs();
|
||||
std::vector<IdString> cells_to_remove;
|
||||
|
||||
for (auto &cell : ctx->cells) {
|
||||
CellInfo &ci = *cell.second;
|
||||
if (!is_io(&ci)) {
|
||||
continue;
|
||||
}
|
||||
// Special case of OBUF without input - we delete such things.
|
||||
if (ci.type == id_OBUF && !ci.getPort(id_I)) {
|
||||
ci.disconnectPort(id_O);
|
||||
cells_to_remove.push_back(ci.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ci.attrs.count(id_BEL) == 0) {
|
||||
log_error("Unconstrained IO:%s\n", ctx->nameOf(&ci));
|
||||
}
|
||||
BelId io_bel = bind_io(ci);
|
||||
Loc io_loc = ctx->getBelLocation(io_bel);
|
||||
if (io_loc.y == ctx->getGridDimY() - 1) {
|
||||
config_bottom_row(ci, io_loc);
|
||||
}
|
||||
if (gwu.is_simple_io_bel(io_bel)) {
|
||||
config_simple_io(ci);
|
||||
}
|
||||
make_iob_nets(ci);
|
||||
}
|
||||
|
||||
for (auto cell : cells_to_remove) {
|
||||
ctx->cells.erase(cell);
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// Differential IO
|
||||
// ===================================
|
||||
|
||||
std::pair<CellInfo *, CellInfo *> GowinPacker::get_pn_cells(const CellInfo &ci)
|
||||
{
|
||||
CellInfo *p, *n;
|
||||
switch (ci.type.hash()) {
|
||||
case ID_ELVDS_TBUF: /* fall-through */
|
||||
case ID_TLVDS_TBUF: /* fall-through */
|
||||
case ID_ELVDS_OBUF: /* fall-through */
|
||||
case ID_TLVDS_OBUF:
|
||||
p = net_only_drives(ctx, ci.ports.at(id_O).net, is_iob, id_I, true);
|
||||
n = net_only_drives(ctx, ci.ports.at(id_OB).net, is_iob, id_I, true);
|
||||
break;
|
||||
case ID_TLVDS_IBUF_ADC: /* fall-through */
|
||||
case ID_ELVDS_IBUF: /* fall-through */
|
||||
case ID_TLVDS_IBUF:
|
||||
p = net_driven_by(ctx, ci.ports.at(id_I).net, is_iob, id_O);
|
||||
n = net_driven_by(ctx, ci.ports.at(id_IB).net, is_iob, id_O);
|
||||
break;
|
||||
case ID_ELVDS_IOBUF: /* fall-through */
|
||||
case ID_TLVDS_IOBUF:
|
||||
p = net_only_drives(ctx, ci.ports.at(id_IO).net, is_iob, id_I);
|
||||
n = net_only_drives(ctx, ci.ports.at(id_IOB).net, is_iob, id_I);
|
||||
break;
|
||||
default:
|
||||
log_error("Bad diff IO '%s' type '%s'\n", ctx->nameOf(&ci), ci.type.c_str(ctx));
|
||||
}
|
||||
return std::make_pair(p, n);
|
||||
}
|
||||
|
||||
void GowinPacker::mark_iobs_as_diff(CellInfo &ci, std::pair<CellInfo *, CellInfo *> &pn_cells)
|
||||
{
|
||||
pn_cells.first->setParam(id_DIFF, std::string("P"));
|
||||
pn_cells.first->setParam(id_DIFF_TYPE, ci.type.str(ctx));
|
||||
pn_cells.second->setParam(id_DIFF, std::string("N"));
|
||||
pn_cells.second->setParam(id_DIFF_TYPE, ci.type.str(ctx));
|
||||
if (ci.params.count(id_ADC_IO)) {
|
||||
pn_cells.first->setParam(id_ADC_IO, ci.params.at(id_ADC_IO));
|
||||
pn_cells.second->setParam(id_ADC_IO, ci.params.at(id_ADC_IO));
|
||||
}
|
||||
}
|
||||
|
||||
void GowinPacker::switch_diff_ports(CellInfo &ci, std::pair<CellInfo *, CellInfo *> &pn_cells,
|
||||
std::vector<IdString> &nets_to_remove)
|
||||
{
|
||||
CellInfo *iob_p = pn_cells.first;
|
||||
CellInfo *iob_n = pn_cells.second;
|
||||
|
||||
if (ci.type.in(id_TLVDS_TBUF, id_TLVDS_OBUF, id_ELVDS_TBUF, id_ELVDS_OBUF)) {
|
||||
nets_to_remove.push_back(ci.getPort(id_O)->name);
|
||||
ci.disconnectPort(id_O);
|
||||
nets_to_remove.push_back(ci.getPort(id_OB)->name);
|
||||
ci.disconnectPort(id_OB);
|
||||
nets_to_remove.push_back(iob_n->getPort(id_I)->name);
|
||||
iob_n->disconnectPort(id_I);
|
||||
|
||||
if (ci.type.in(id_TLVDS_TBUF, id_ELVDS_TBUF)) {
|
||||
NetInfo *oen_net = iob_n->getPort(id_OEN);
|
||||
if (oen_net != nullptr) {
|
||||
nets_to_remove.push_back(oen_net->name);
|
||||
}
|
||||
iob_n->disconnectPort(id_OEN);
|
||||
iob_p->disconnectPort(id_OEN);
|
||||
ci.movePortTo(id_OEN, iob_p, id_OEN);
|
||||
|
||||
// MIPI
|
||||
if (ci.params.count(id_MIPI_OBUF)) {
|
||||
iob_p->setParam(id_MIPI_OBUF, 1);
|
||||
iob_n->setParam(id_MIPI_OBUF, 1);
|
||||
ci.movePortTo(id_IB, iob_n, id_I);
|
||||
iob_p->copyPortTo(id_OEN, iob_n, id_OEN);
|
||||
}
|
||||
}
|
||||
iob_p->disconnectPort(id_I);
|
||||
ci.movePortTo(id_I, iob_p, id_I);
|
||||
return;
|
||||
}
|
||||
if (ci.type.in(id_TLVDS_IBUF, id_ELVDS_IBUF)) {
|
||||
nets_to_remove.push_back(ci.getPort(id_I)->name);
|
||||
ci.disconnectPort(id_I);
|
||||
nets_to_remove.push_back(ci.getPort(id_IB)->name);
|
||||
ci.disconnectPort(id_IB);
|
||||
iob_n->disconnectPort(id_O);
|
||||
iob_p->disconnectPort(id_O);
|
||||
ci.movePortTo(id_O, iob_p, id_O);
|
||||
return;
|
||||
}
|
||||
if (ci.type.in(id_TLVDS_IOBUF, id_ELVDS_IOBUF)) {
|
||||
nets_to_remove.push_back(ci.getPort(id_IO)->name);
|
||||
ci.disconnectPort(id_IO);
|
||||
nets_to_remove.push_back(ci.getPort(id_IOB)->name);
|
||||
ci.disconnectPort(id_IOB);
|
||||
nets_to_remove.push_back(iob_n->getPort(id_I)->name);
|
||||
iob_n->disconnectPort(id_I);
|
||||
iob_n->disconnectPort(id_OEN);
|
||||
|
||||
iob_p->disconnectPort(id_OEN);
|
||||
ci.movePortTo(id_OEN, iob_p, id_OEN);
|
||||
iob_p->disconnectPort(id_I);
|
||||
ci.movePortTo(id_I, iob_p, id_I);
|
||||
iob_p->disconnectPort(id_O);
|
||||
ci.movePortTo(id_O, iob_p, id_O);
|
||||
return;
|
||||
}
|
||||
if (ci.type.in(id_TLVDS_IBUF_ADC)) {
|
||||
nets_to_remove.push_back(ci.getPort(id_I)->name);
|
||||
ci.disconnectPort(id_I);
|
||||
nets_to_remove.push_back(ci.getPort(id_IB)->name);
|
||||
ci.disconnectPort(id_IB);
|
||||
iob_p->disconnectPort(id_O);
|
||||
iob_n->disconnectPort(id_O);
|
||||
|
||||
ci.movePortTo(id_ADCEN, iob_p, id_ADCEN);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// I3C
|
||||
// ===================================
|
||||
void GowinPacker::pack_i3c(void)
|
||||
{
|
||||
log_info("Pack I3C IOs...\n");
|
||||
std::vector<IdString> cells_to_remove;
|
||||
|
||||
for (auto &cell : ctx->cells) {
|
||||
CellInfo &ci = *cell.second;
|
||||
if (!is_i3c(&ci)) {
|
||||
continue;
|
||||
}
|
||||
// check for I3C-capable pin A
|
||||
CellInfo *iob = net_only_drives(ctx, ci.ports.at(id_IO).net, is_iob, id_I);
|
||||
if (iob == nullptr || iob->bel == BelId()) {
|
||||
log_error("I3C %s IO is not connected to the input pin or the pin is not constrained.\n", ctx->nameOf(&ci));
|
||||
}
|
||||
BelId iob_bel = iob->bel;
|
||||
Loc iob_loc = ctx->getBelLocation(iob_bel);
|
||||
|
||||
if (!gwu.get_i3c_capable(iob_loc.x, iob_loc.y)) {
|
||||
log_error("Can't place %s. Not I3C capable X%dY%d.\n", ctx->nameOf(&ci), iob_loc.x, iob_loc.y);
|
||||
}
|
||||
ci.disconnectPort(id_IO);
|
||||
iob->disconnectPort(id_I);
|
||||
ci.movePortTo(id_I, iob, id_I);
|
||||
ci.movePortTo(id_O, iob, id_O);
|
||||
iob->disconnectPort(id_OEN);
|
||||
ci.movePortTo(id_MODESEL, iob, id_OEN);
|
||||
|
||||
iob->setParam(id_I3C_IOBUF, 1);
|
||||
cells_to_remove.push_back(ci.name);
|
||||
}
|
||||
|
||||
for (auto cell : cells_to_remove) {
|
||||
ctx->cells.erase(cell);
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// MIPI IO
|
||||
// ===================================
|
||||
void GowinPacker::pack_mipi(void)
|
||||
{
|
||||
log_info("Pack MIPI IOs...\n");
|
||||
std::vector<std::unique_ptr<CellInfo>> new_cells;
|
||||
|
||||
for (auto &cell : ctx->cells) {
|
||||
CellInfo &ci = *cell.second;
|
||||
if (!is_mipi(&ci)) {
|
||||
continue;
|
||||
}
|
||||
switch (ci.type.hash()) {
|
||||
case ID_MIPI_OBUF_A: /* fall-through */
|
||||
case ID_MIPI_OBUF: {
|
||||
// check for MIPI-capable pin
|
||||
CellInfo *out_iob = net_only_drives(ctx, ci.ports.at(id_O).net, is_iob, id_I, true);
|
||||
if (out_iob == nullptr || out_iob->bel == BelId()) {
|
||||
log_error("MIPI %s is not connected to the output pin or the pin is not constrained.\n",
|
||||
ctx->nameOf(&ci));
|
||||
}
|
||||
if (out_iob->params.count(id_I3C_IOBUF)) {
|
||||
log_error("Can't place MIPI %s. Conflict with I3C %s.\n", ctx->nameOf(&ci), ctx->nameOf(out_iob));
|
||||
}
|
||||
BelId iob_bel = out_iob->bel;
|
||||
Loc iob_loc = ctx->getBelLocation(iob_bel);
|
||||
iob_loc.z = BelZ::MIPIOBUF_Z;
|
||||
BelId mipi_bel = ctx->getBelByLocation(iob_loc);
|
||||
if (mipi_bel == BelId()) {
|
||||
log_error("Can't place MIPI %s at X%dY%d/IOBA.\n", ctx->nameOf(&ci), iob_loc.x, iob_loc.y);
|
||||
}
|
||||
|
||||
if (ci.type == id_MIPI_OBUF_A) {
|
||||
// if serialization is used then IL and input of serializator must be in the same network
|
||||
NetInfo *i_net = ci.getPort(id_I);
|
||||
NetInfo *il_net = ci.getPort(id_IL);
|
||||
if (i_net != il_net) {
|
||||
if (i_net != nullptr && is_iologico(i_net->driver.cell)) {
|
||||
if (i_net->driver.cell->getPort(id_D0) != ci.getPort(id_IL)) {
|
||||
log_error("MIPI %s port IL and IOLOGIC %s port D0 are in differrent networks!\n",
|
||||
ctx->nameOf(&ci), ctx->nameOf(i_net->driver.cell));
|
||||
}
|
||||
} else {
|
||||
log_error("MIPI %s ports IL and I are in differrent networks!\n", ctx->nameOf(&ci));
|
||||
}
|
||||
}
|
||||
ci.disconnectPort(id_IL);
|
||||
}
|
||||
|
||||
ctx->bindBel(mipi_bel, &ci, PlaceStrength::STRENGTH_LOCKED);
|
||||
|
||||
// Create TBUF with additional input IB
|
||||
IdString mipi_tbuf_name = gwu.create_aux_name(ci.name);
|
||||
new_cells.push_back(gwu.create_cell(mipi_tbuf_name, id_TLVDS_TBUF));
|
||||
|
||||
CellInfo *mipi_tbuf = new_cells.back().get();
|
||||
mipi_tbuf->addInput(id_I);
|
||||
mipi_tbuf->addInput(id_IB);
|
||||
mipi_tbuf->addOutput(id_O);
|
||||
mipi_tbuf->addOutput(id_OB);
|
||||
mipi_tbuf->addInput(id_OEN);
|
||||
ci.movePortTo(id_I, mipi_tbuf, id_I);
|
||||
ci.movePortTo(id_IB, mipi_tbuf, id_IB);
|
||||
ci.movePortTo(id_O, mipi_tbuf, id_O);
|
||||
ci.movePortTo(id_OB, mipi_tbuf, id_OB);
|
||||
ci.movePortTo(id_MODESEL, mipi_tbuf, id_OEN);
|
||||
|
||||
mipi_tbuf->setParam(id_MIPI_OBUF, 1);
|
||||
} break;
|
||||
case ID_MIPI_IBUF: {
|
||||
// check for MIPI-capable pin A
|
||||
CellInfo *in_iob = net_only_drives(ctx, ci.ports.at(id_IO).net, is_iob, id_I);
|
||||
if (in_iob == nullptr || in_iob->bel == BelId()) {
|
||||
log_error("MIPI %s IO is not connected to the input pin or the pin is not constrained.\n",
|
||||
ctx->nameOf(&ci));
|
||||
}
|
||||
// check A IO placing
|
||||
if (in_iob->params.count(id_I3C_IOBUF)) {
|
||||
log_error("Can't place MIPI %s. Conflict with I3C %s.\n", ctx->nameOf(&ci), ctx->nameOf(in_iob));
|
||||
}
|
||||
BelId iob_bel = in_iob->bel;
|
||||
Loc iob_loc = ctx->getBelLocation(iob_bel);
|
||||
if (iob_loc.z != BelZ::IOBA_Z) {
|
||||
log_error("MIPI %s IO pin must be connected to the A IO pin.\n", ctx->nameOf(&ci));
|
||||
}
|
||||
|
||||
iob_loc.z = BelZ::MIPIIBUF_Z;
|
||||
BelId mipi_bel = ctx->getBelByLocation(iob_loc);
|
||||
if (mipi_bel == BelId()) {
|
||||
log_error("Can't place MIPI %s at X%dY%d/IOBA.\n", ctx->nameOf(&ci), iob_loc.x, iob_loc.y);
|
||||
}
|
||||
|
||||
// check for MIPI-capable pin B
|
||||
CellInfo *inb_iob = net_only_drives(ctx, ci.ports.at(id_IOB).net, is_iob, id_I);
|
||||
if (inb_iob == nullptr || inb_iob->bel == BelId()) {
|
||||
log_error("MIPI %s IOB is not connected to the input pin or the pin is not constrained.\n",
|
||||
ctx->nameOf(&ci));
|
||||
}
|
||||
// check B IO placing
|
||||
if (inb_iob->params.count(id_I3C_IOBUF)) {
|
||||
log_error("Can't place MIPI %s. Conflict with I3C %s.\n", ctx->nameOf(&ci), ctx->nameOf(inb_iob));
|
||||
}
|
||||
BelId iobb_bel = inb_iob->bel;
|
||||
Loc iobb_loc = ctx->getBelLocation(iobb_bel);
|
||||
if (iobb_loc.z != BelZ::IOBB_Z || iobb_loc.x != iob_loc.x || iobb_loc.y != iob_loc.y) {
|
||||
log_error("MIPI %s IOB pin must be connected to the B IO pin.\n", ctx->nameOf(&ci));
|
||||
}
|
||||
// MIPI IBUF uses next pair of IOs too
|
||||
Loc iob_next_loc(iob_loc);
|
||||
++iob_next_loc.x;
|
||||
iob_next_loc.z = BelZ::IOBA_Z;
|
||||
CellInfo *inc_iob = ctx->getBoundBelCell(ctx->getBelByLocation(iob_next_loc));
|
||||
iob_next_loc.z = BelZ::IOBB_Z;
|
||||
CellInfo *other_cell_b = ctx->getBoundBelCell(ctx->getBelByLocation(iob_next_loc));
|
||||
if (inc_iob != nullptr || other_cell_b != nullptr) {
|
||||
log_error("MIPI %s cannot be placed in same IO with %s.\n", ctx->nameOf(&ci),
|
||||
inc_iob == nullptr ? ctx->nameOf(other_cell_b) : ctx->nameOf(inc_iob));
|
||||
}
|
||||
|
||||
ctx->bindBel(mipi_bel, &ci, PlaceStrength::STRENGTH_LOCKED);
|
||||
|
||||
// reconnect wires
|
||||
// A
|
||||
ci.disconnectPort(id_IO);
|
||||
in_iob->disconnectPort(id_I);
|
||||
ci.movePortTo(id_I, in_iob, id_I);
|
||||
ci.movePortTo(id_OH, in_iob, id_O);
|
||||
in_iob->disconnectPort(id_OEN);
|
||||
ci.movePortTo(id_OEN, in_iob, id_OEN);
|
||||
// B
|
||||
ci.disconnectPort(id_IO);
|
||||
inb_iob->disconnectPort(id_I);
|
||||
ci.movePortTo(id_IB, inb_iob, id_I);
|
||||
ci.movePortTo(id_OB, inb_iob, id_O);
|
||||
inb_iob->disconnectPort(id_OEN);
|
||||
ci.movePortTo(id_OENB, inb_iob, id_OEN);
|
||||
// MIPI enable (?)
|
||||
ci.addInput(ctx->id("MIPIEN0"));
|
||||
ci.connectPort(ctx->id("MIPIEN0"), ctx->nets.at(ctx->id("$PACKER_GND")).get());
|
||||
ci.addInput(ctx->id("MIPIEN1"));
|
||||
ci.connectPort(ctx->id("MIPIEN1"), ctx->nets.at(ctx->id("$PACKER_VCC")).get());
|
||||
|
||||
in_iob->setParam(id_MIPI_IBUF, 1);
|
||||
inb_iob->setParam(id_MIPI_IBUF, 1);
|
||||
} break;
|
||||
default:
|
||||
log_error("MIPI %s is not implemented.\n", ci.type.c_str(ctx));
|
||||
}
|
||||
}
|
||||
for (auto &ncell : new_cells) {
|
||||
ctx->cells[ncell->name] = std::move(ncell);
|
||||
}
|
||||
}
|
||||
|
||||
void GowinPacker::pack_diff_iobs(void)
|
||||
{
|
||||
log_info("Pack diff IOBs...\n");
|
||||
std::vector<IdString> cells_to_remove, nets_to_remove;
|
||||
|
||||
for (auto &cell : ctx->cells) {
|
||||
CellInfo &ci = *cell.second;
|
||||
if (!is_diffio(&ci)) {
|
||||
continue;
|
||||
}
|
||||
if (!gwu.is_diff_io_supported(ci.type)) {
|
||||
log_error("%s is not supported\n", ci.type.c_str(ctx));
|
||||
}
|
||||
cells_to_remove.push_back(ci.name);
|
||||
auto pn_cells = get_pn_cells(ci);
|
||||
NPNR_ASSERT(pn_cells.first != nullptr && pn_cells.second != nullptr);
|
||||
|
||||
mark_iobs_as_diff(ci, pn_cells);
|
||||
switch_diff_ports(ci, pn_cells, nets_to_remove);
|
||||
}
|
||||
|
||||
for (auto cell : cells_to_remove) {
|
||||
ctx->cells.erase(cell);
|
||||
}
|
||||
for (auto net : nets_to_remove) {
|
||||
ctx->nets.erase(net);
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_ff(const Context *ctx, CellInfo *cell) { return is_dff(cell); }
|
||||
|
||||
static bool incompatible_ffs(IdString type_a, IdString type_b)
|
||||
{
|
||||
return type_a != type_b &&
|
||||
((type_a == id_DFFS && type_b != id_DFFR) || (type_a == id_DFFR && type_b != id_DFFS) ||
|
||||
(type_a == id_DFFSE && type_b != id_DFFRE) || (type_a == id_DFFRE && type_b != id_DFFSE) ||
|
||||
(type_a == id_DFFP && type_b != id_DFFC) || (type_a == id_DFFC && type_b != id_DFFP) ||
|
||||
(type_a == id_DFFPE && type_b != id_DFFCE) || (type_a == id_DFFCE && type_b != id_DFFPE) ||
|
||||
(type_a == id_DFFNS && type_b != id_DFFNR) || (type_a == id_DFFNR && type_b != id_DFFNS) ||
|
||||
(type_a == id_DFFNSE && type_b != id_DFFNRE) || (type_a == id_DFFNRE && type_b != id_DFFNSE) ||
|
||||
(type_a == id_DFFNP && type_b != id_DFFNC) || (type_a == id_DFFNC && type_b != id_DFFNP) ||
|
||||
(type_a == id_DFFNPE && type_b != id_DFFNCE) || (type_a == id_DFFNCE && type_b != id_DFFNPE) ||
|
||||
(type_a == id_DFF && type_b != id_DFF) || (type_a == id_DFFN && type_b != id_DFFN) ||
|
||||
(type_a == id_DFFE && type_b != id_DFFE) || (type_a == id_DFFNE && type_b != id_DFFNE));
|
||||
}
|
||||
|
||||
void GowinPacker::pack_io_regs(void)
|
||||
{
|
||||
log_info("Pack FFs into IO cells...\n");
|
||||
std::vector<IdString> cells_to_remove;
|
||||
std::vector<IdString> nets_to_remove;
|
||||
std::vector<std::unique_ptr<CellInfo>> new_cells;
|
||||
|
||||
for (auto &cell : ctx->cells) {
|
||||
CellInfo &ci = *cell.second;
|
||||
if (!is_io(&ci)) {
|
||||
continue;
|
||||
}
|
||||
if (ci.attrs.count(id_NOIOBFF)) {
|
||||
if (ctx->debug) {
|
||||
log_info(" NOIOBFF attribute at %s. Skipping FF placement.\n", ctx->nameOf(&ci));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// In the case of placing multiple registers in the IO it should be
|
||||
// noted that the CLK, ClockEnable and LocalSetReset nets must
|
||||
// match.
|
||||
const NetInfo *clk_net = nullptr;
|
||||
const NetInfo *ce_net = nullptr;
|
||||
const NetInfo *lsr_net = nullptr;
|
||||
IdString reg_type;
|
||||
|
||||
// input reg in IO
|
||||
CellInfo *iologic_i = nullptr;
|
||||
if ((ci.type == id_IBUF && (ctx->settings.count(id_IREG_IN_IOB) || ci.attrs.count(id_IOBFF))) ||
|
||||
(ci.type == id_IOBUF && (ctx->settings.count(id_IOREG_IN_IOB) || ci.attrs.count(id_IOBFF)))) {
|
||||
|
||||
if (ci.getPort(id_O) == nullptr) {
|
||||
continue;
|
||||
}
|
||||
// OBUF O -> D FF
|
||||
CellInfo *ff = net_only_drives(ctx, ci.ports.at(id_O).net, is_ff, id_D);
|
||||
if (ff == nullptr) {
|
||||
if (ci.attrs.count(id_IOBFF)) {
|
||||
log_warning("Port O of %s is not connected to FF.\n", ctx->nameOf(&ci));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (ci.ports.at(id_O).net->users.entries() != 1) {
|
||||
if (ci.attrs.count(id_IOBFF)) {
|
||||
log_warning("Port O of %s is the driver of %s multi-sink network.\n", ctx->nameOf(&ci),
|
||||
ctx->nameOf(ci.ports.at(id_O).net));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
BelId l_bel = get_iologici_bel(&ci);
|
||||
if (l_bel == BelId()) {
|
||||
continue;
|
||||
}
|
||||
if (ctx->debug) {
|
||||
log_info(" trying %s ff as Input Register of %s IO\n", ctx->nameOf(ff), ctx->nameOf(&ci));
|
||||
}
|
||||
|
||||
clk_net = ff->getPort(id_CLK);
|
||||
ce_net = ff->getPort(id_CE);
|
||||
for (IdString port : {id_SET, id_RESET, id_PRESET, id_CLEAR}) {
|
||||
lsr_net = ff->getPort(port);
|
||||
if (lsr_net != nullptr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
reg_type = ff->type;
|
||||
|
||||
// create IOLOGIC cell for flipflop
|
||||
IdString iologic_name = gwu.create_aux_name(ci.name, 0, "_iobff$");
|
||||
auto iologic_cell = gwu.create_cell(iologic_name, id_IOLOGICI_EMPTY);
|
||||
new_cells.push_back(std::move(iologic_cell));
|
||||
iologic_i = new_cells.back().get();
|
||||
|
||||
// move ports
|
||||
for (auto &port : ff->ports) {
|
||||
IdString port_name = port.first;
|
||||
ff->movePortTo(port_name, iologic_i, port_name != id_Q ? port_name : id_Q4);
|
||||
}
|
||||
if (ctx->verbose) {
|
||||
log_info(" place FF %s into IBUF %s, make iologic_i %s\n", ctx->nameOf(ff), ctx->nameOf(&ci),
|
||||
ctx->nameOf(iologic_i));
|
||||
}
|
||||
iologic_i->setAttr(id_HAS_REG, 1);
|
||||
iologic_i->setAttr(id_IREG_TYPE, ff->type.str(ctx));
|
||||
cells_to_remove.push_back(ff->name);
|
||||
}
|
||||
|
||||
// output reg in IO
|
||||
CellInfo *iologic_o = nullptr;
|
||||
if ((ci.type == id_OBUF && (ctx->settings.count(id_OREG_IN_IOB) || ci.attrs.count(id_IOBFF))) ||
|
||||
(ci.type == id_IOBUF && (ctx->settings.count(id_IOREG_IN_IOB) || ci.attrs.count(id_IOBFF)))) {
|
||||
do {
|
||||
if (ci.getPort(id_I) == nullptr) {
|
||||
break;
|
||||
}
|
||||
// OBUF I <- Q FF
|
||||
CellInfo *ff = net_driven_by(ctx, ci.ports.at(id_I).net, is_ff, id_Q);
|
||||
if (ff == nullptr) {
|
||||
if (ci.attrs.count(id_IOBFF)) {
|
||||
log_warning("Port I of %s is not connected to FF.\n", ctx->nameOf(&ci));
|
||||
}
|
||||
} else {
|
||||
if (ci.ports.at(id_I).net->users.entries() != 1) {
|
||||
if (ci.attrs.count(id_IOBFF)) {
|
||||
log_warning("Port I of %s is not the only sink on the %s network.\n", ctx->nameOf(&ci),
|
||||
ctx->nameOf(ci.ports.at(id_I).net));
|
||||
}
|
||||
break;
|
||||
}
|
||||
BelId l_bel = get_iologico_bel(&ci);
|
||||
if (l_bel == BelId()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const NetInfo *this_clk_net = ff->getPort(id_CLK);
|
||||
const NetInfo *this_ce_net = ff->getPort(id_CE);
|
||||
const NetInfo *this_lsr_net;
|
||||
for (IdString port : {id_SET, id_RESET, id_PRESET, id_CLEAR}) {
|
||||
this_lsr_net = ff->getPort(port);
|
||||
if (this_lsr_net != nullptr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// The IOBUF may already have registers placed
|
||||
if (ci.type == id_IOBUF) {
|
||||
if (iologic_i != nullptr) {
|
||||
if (incompatible_ffs(ff->type, reg_type)) {
|
||||
if (ci.attrs.count(id_IOBFF)) {
|
||||
log_warning("OREG type conflict:%s:%s vs %s IREG:%s\n", ctx->nameOf(ff),
|
||||
ff->type.c_str(ctx), ctx->nameOf(&ci), reg_type.c_str(ctx));
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
if (clk_net != this_clk_net || ce_net != this_ce_net || lsr_net != this_lsr_net) {
|
||||
if (clk_net != this_clk_net) {
|
||||
if (ci.attrs.count(id_IOBFF)) {
|
||||
log_warning("Conflicting OREG CLK nets at %s:'%s' vs '%s'\n",
|
||||
ctx->nameOf(&ci), ctx->nameOf(clk_net),
|
||||
ctx->nameOf(this_clk_net));
|
||||
}
|
||||
}
|
||||
if (ce_net != this_ce_net) {
|
||||
if (ci.attrs.count(id_IOBFF)) {
|
||||
log_warning("Conflicting OREG CE nets at %s:'%s' vs '%s'\n",
|
||||
ctx->nameOf(&ci), ctx->nameOf(ce_net),
|
||||
ctx->nameOf(this_ce_net));
|
||||
}
|
||||
}
|
||||
if (lsr_net != this_lsr_net) {
|
||||
if (ci.attrs.count(id_IOBFF)) {
|
||||
log_warning("Conflicting OREG LSR nets at %s:'%s' vs '%s'\n",
|
||||
ctx->nameOf(&ci), ctx->nameOf(lsr_net),
|
||||
ctx->nameOf(this_lsr_net));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
clk_net = this_clk_net;
|
||||
ce_net = this_ce_net;
|
||||
lsr_net = this_lsr_net;
|
||||
reg_type = ff->type;
|
||||
}
|
||||
}
|
||||
|
||||
// create IOLOGIC cell for flipflop
|
||||
IdString iologic_name = gwu.create_aux_name(ci.name, 1, "_iobff$");
|
||||
auto iologic_cell = gwu.create_cell(iologic_name, id_IOLOGICO_EMPTY);
|
||||
new_cells.push_back(std::move(iologic_cell));
|
||||
iologic_o = new_cells.back().get();
|
||||
|
||||
// move ports
|
||||
for (auto &port : ff->ports) {
|
||||
IdString port_name = port.first;
|
||||
ff->movePortTo(port_name, iologic_o, port_name != id_D ? port_name : id_D0);
|
||||
}
|
||||
if (ctx->verbose) {
|
||||
log_info(" place FF %s into OBUF %s, make iologic_o %s\n", ctx->nameOf(ff), ctx->nameOf(&ci),
|
||||
ctx->nameOf(iologic_o));
|
||||
}
|
||||
iologic_o->setAttr(id_HAS_REG, 1);
|
||||
iologic_o->setAttr(id_OREG_TYPE, ff->type.str(ctx));
|
||||
cells_to_remove.push_back(ff->name);
|
||||
}
|
||||
} while (false);
|
||||
}
|
||||
|
||||
// output enable reg in IO
|
||||
if (ci.type == id_IOBUF && (ctx->settings.count(id_IOREG_IN_IOB) || ci.attrs.count(id_IOBFF))) {
|
||||
do {
|
||||
if (ci.getPort(id_OEN) == nullptr) {
|
||||
break;
|
||||
}
|
||||
// IOBUF OEN <- Q FF
|
||||
CellInfo *ff = net_driven_by(ctx, ci.ports.at(id_OEN).net, is_ff, id_Q);
|
||||
if (ff != nullptr) {
|
||||
if (ci.ports.at(id_OEN).net->users.entries() != 1) {
|
||||
if (ci.attrs.count(id_IOBFF)) {
|
||||
log_warning("Port OEN of %s is not the only sink on the %s network.\n", ctx->nameOf(&ci),
|
||||
ctx->nameOf(ci.ports.at(id_OEN).net));
|
||||
}
|
||||
break;
|
||||
}
|
||||
BelId l_bel = get_iologico_bel(&ci);
|
||||
if (l_bel == BelId()) {
|
||||
break;
|
||||
}
|
||||
if (ctx->debug) {
|
||||
log_info(" trying %s ff as Output Enable Register of %s IO\n", ctx->nameOf(ff),
|
||||
ctx->nameOf(&ci));
|
||||
}
|
||||
|
||||
const NetInfo *this_clk_net = ff->getPort(id_CLK);
|
||||
const NetInfo *this_ce_net = ff->getPort(id_CE);
|
||||
const NetInfo *this_lsr_net;
|
||||
for (IdString port : {id_SET, id_RESET, id_PRESET, id_CLEAR}) {
|
||||
this_lsr_net = ff->getPort(port);
|
||||
if (this_lsr_net != nullptr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// The IOBUF may already have registers placed
|
||||
if (iologic_i != nullptr || iologic_o != nullptr) {
|
||||
if (iologic_o == nullptr) {
|
||||
iologic_o = iologic_i;
|
||||
}
|
||||
if (incompatible_ffs(ff->type, reg_type)) {
|
||||
if (ci.attrs.count(id_IOBFF)) {
|
||||
log_warning("TREG type conflict:%s:%s vs %s IREG/OREG:%s\n", ctx->nameOf(ff),
|
||||
ff->type.c_str(ctx), ctx->nameOf(&ci), reg_type.c_str(ctx));
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
if (clk_net != this_clk_net || ce_net != this_ce_net || lsr_net != this_lsr_net) {
|
||||
if (clk_net != this_clk_net) {
|
||||
if (ci.attrs.count(id_IOBFF)) {
|
||||
log_warning("Conflicting TREG CLK nets at %s:'%s' vs '%s'\n", ctx->nameOf(&ci),
|
||||
ctx->nameOf(clk_net), ctx->nameOf(this_clk_net));
|
||||
}
|
||||
}
|
||||
if (ce_net != this_ce_net) {
|
||||
if (ci.attrs.count(id_IOBFF)) {
|
||||
log_warning("Conflicting TREG CE nets at %s:'%s' vs '%s'\n", ctx->nameOf(&ci),
|
||||
ctx->nameOf(ce_net), ctx->nameOf(this_ce_net));
|
||||
}
|
||||
}
|
||||
if (lsr_net != this_lsr_net) {
|
||||
if (ci.attrs.count(id_IOBFF)) {
|
||||
log_warning("Conflicting TREG LSR nets at %s:'%s' vs '%s'\n", ctx->nameOf(&ci),
|
||||
ctx->nameOf(lsr_net), ctx->nameOf(this_lsr_net));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (iologic_o == nullptr) {
|
||||
// create IOLOGIC cell for flipflop
|
||||
IdString iologic_name = gwu.create_aux_name(ci.name, 2, "_iobff$");
|
||||
auto iologic_cell = gwu.create_cell(iologic_name, id_IOLOGICO_EMPTY);
|
||||
new_cells.push_back(std::move(iologic_cell));
|
||||
iologic_o = new_cells.back().get();
|
||||
}
|
||||
|
||||
// move ports
|
||||
for (auto &port : ff->ports) {
|
||||
IdString port_name = port.first;
|
||||
if (port_name == id_Q) {
|
||||
continue;
|
||||
}
|
||||
ff->movePortTo(port_name, iologic_o, port_name != id_D ? port_name : id_TX);
|
||||
}
|
||||
|
||||
nets_to_remove.push_back(ci.getPort(id_OEN)->name);
|
||||
ci.disconnectPort(id_OEN);
|
||||
ff->disconnectPort(id_Q);
|
||||
|
||||
if (ctx->verbose) {
|
||||
log_info(" place FF %s into IOBUF %s, make iologic_o %s\n", ctx->nameOf(ff), ctx->nameOf(&ci),
|
||||
ctx->nameOf(iologic_o));
|
||||
}
|
||||
iologic_o->setAttr(id_HAS_REG, 1);
|
||||
iologic_o->setAttr(id_TREG_TYPE, ff->type.str(ctx));
|
||||
cells_to_remove.push_back(ff->name);
|
||||
}
|
||||
} while (false);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto cell : cells_to_remove) {
|
||||
ctx->cells.erase(cell);
|
||||
}
|
||||
|
||||
for (auto &ncell : new_cells) {
|
||||
ctx->cells[ncell->name] = std::move(ncell);
|
||||
}
|
||||
|
||||
for (auto net : nets_to_remove) {
|
||||
ctx->nets.erase(net);
|
||||
}
|
||||
}
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
|
@ -0,0 +1,850 @@
|
|||
#include "design_utils.h"
|
||||
#include "log.h"
|
||||
#include "nextpnr.h"
|
||||
|
||||
#define HIMBAECHEL_CONSTIDS "uarch/gowin/constids.inc"
|
||||
#include "himbaechel_constids.h"
|
||||
#include "himbaechel_helpers.h"
|
||||
|
||||
#include "gowin.h"
|
||||
#include "gowin_utils.h"
|
||||
#include "pack.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
// ===================================
|
||||
// IO logic
|
||||
// ===================================
|
||||
// the functions of these two inputs are yet to be discovered, so we set as observed
|
||||
// in the exemplary images
|
||||
void GowinPacker::set_daaj_nets(CellInfo &ci, BelId bel)
|
||||
{
|
||||
std::vector<IdString> pins = ctx->getBelPins(bel);
|
||||
if (std::find(pins.begin(), pins.end(), id_DAADJ0) != pins.end()) {
|
||||
ci.addInput(id_DAADJ0);
|
||||
ci.connectPort(id_DAADJ0, ctx->nets.at(ctx->id("$PACKER_GND")).get());
|
||||
}
|
||||
if (std::find(pins.begin(), pins.end(), id_DAADJ1) != pins.end()) {
|
||||
ci.addInput(id_DAADJ1);
|
||||
ci.connectPort(id_DAADJ1, ctx->nets.at(ctx->id("$PACKER_VCC")).get());
|
||||
}
|
||||
}
|
||||
|
||||
BelId GowinPacker::get_iologico_bel(CellInfo *iob)
|
||||
{
|
||||
NPNR_ASSERT(iob->bel != BelId());
|
||||
Loc loc = ctx->getBelLocation(iob->bel);
|
||||
loc.z = loc.z - BelZ::IOBA_Z + BelZ::IOLOGICA_Z;
|
||||
BelId bel = ctx->getBelByLocation(loc);
|
||||
if (bel != BelId()) {
|
||||
if (ctx->getBelType(bel) == id_IOLOGICO) {
|
||||
return bel;
|
||||
}
|
||||
}
|
||||
return BelId();
|
||||
}
|
||||
|
||||
BelId GowinPacker::get_iologici_bel(CellInfo *iob)
|
||||
{
|
||||
NPNR_ASSERT(iob->bel != BelId());
|
||||
Loc loc = ctx->getBelLocation(iob->bel);
|
||||
loc.z = loc.z - BelZ::IOBA_Z + BelZ::IOLOGICA_Z + 2;
|
||||
BelId bel = ctx->getBelByLocation(loc);
|
||||
if (bel != BelId()) {
|
||||
if (ctx->getBelType(bel) == id_IOLOGICI) {
|
||||
return bel;
|
||||
}
|
||||
}
|
||||
return BelId();
|
||||
}
|
||||
|
||||
void GowinPacker::check_iologic_placement(CellInfo &ci, Loc iob_loc, int diff /* 1 - diff */)
|
||||
{
|
||||
if (ci.type.in(id_ODDR, id_ODDRC, id_IDDR, id_IDDRC, id_OSER4, id_IOLOGICI_EMPTY, id_IOLOGICO_EMPTY) || diff) {
|
||||
return;
|
||||
}
|
||||
BelId l_bel = ctx->getBelByLocation(Loc(iob_loc.x, iob_loc.y, BelZ::IOBA_Z + 1 - (iob_loc.z - BelZ::IOBA_Z)));
|
||||
if (!ctx->checkBelAvail(l_bel)) {
|
||||
log_error("Can't place %s at %s because it's already taken by %s\n", ctx->nameOf(&ci), ctx->nameOfBel(l_bel),
|
||||
ctx->nameOf(ctx->getBoundBelCell(l_bel)));
|
||||
}
|
||||
}
|
||||
|
||||
// While we require an exact match of the type, in the future the criteria
|
||||
// may be relaxed and there will be a comparison of the control networks
|
||||
// used.
|
||||
bool are_iologic_compatible(CellInfo *ci_0, CellInfo *ci_1)
|
||||
{
|
||||
switch (ci_0->type.hash()) {
|
||||
case ID_ODDR:
|
||||
return ci_1->type == id_IDDR;
|
||||
case ID_ODDRC:
|
||||
return ci_1->type == id_IDDRC;
|
||||
case ID_IDDR:
|
||||
return ci_1->type == id_ODDR;
|
||||
case ID_IDDRC:
|
||||
return ci_1->type == id_ODDRC;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GowinPacker::pack_bi_output_iol(CellInfo &ci, std::vector<IdString> &nets_to_remove)
|
||||
{
|
||||
// These primitives have an additional pin to control the tri-state iob - Q1.
|
||||
IdString out_port = id_Q0;
|
||||
IdString tx_port = id_Q1;
|
||||
|
||||
CellInfo *out_iob = net_only_drives(ctx, ci.ports.at(out_port).net, is_iob, id_I, true);
|
||||
NPNR_ASSERT(out_iob != nullptr && out_iob->bel != BelId());
|
||||
BelId iob_bel = out_iob->bel;
|
||||
|
||||
BelId l_bel = get_iologico_bel(out_iob);
|
||||
// check compatible Input and Output iologic if any
|
||||
BelId in_l_bel = get_iologici_bel(out_iob);
|
||||
if (in_l_bel != BelId() && !ctx->checkBelAvail(in_l_bel)) {
|
||||
CellInfo *in_iologic_ci = ctx->getBoundBelCell(in_l_bel);
|
||||
if (!are_iologic_compatible(&ci, in_iologic_ci)) {
|
||||
log_error("IOLOGIC %s at %s cannot coexist with %s\n", ctx->nameOf(&ci), ctx->nameOfBel(iob_bel),
|
||||
ctx->nameOf(in_iologic_ci));
|
||||
}
|
||||
}
|
||||
if (l_bel == BelId()) {
|
||||
log_error("Can't place IOLOGIC %s at %s\n", ctx->nameOf(&ci), ctx->nameOfBel(iob_bel));
|
||||
}
|
||||
// mark IOB as used by IOLOGIC
|
||||
out_iob->setParam(id_IOLOGIC_IOB, 1);
|
||||
check_iologic_placement(ci, ctx->getBelLocation(iob_bel),
|
||||
out_iob->params.count(id_DIFF_TYPE) || out_iob->params.count(id_MIPI_OBUF));
|
||||
|
||||
if (!ctx->checkBelAvail(l_bel)) {
|
||||
log_error("Can't place %s at %s because it's already taken by %s\n", ctx->nameOf(&ci), ctx->nameOfBel(l_bel),
|
||||
ctx->nameOf(ctx->getBoundBelCell(l_bel)));
|
||||
}
|
||||
ctx->bindBel(l_bel, &ci, PlaceStrength::STRENGTH_LOCKED);
|
||||
std::string out_mode;
|
||||
switch (ci.type.hash()) {
|
||||
case ID_ODDR:
|
||||
case ID_ODDRC:
|
||||
out_mode = "ODDRX1";
|
||||
break;
|
||||
case ID_OSER4:
|
||||
out_mode = "ODDRX2";
|
||||
break;
|
||||
case ID_OSER8:
|
||||
out_mode = "ODDRX4";
|
||||
break;
|
||||
}
|
||||
ci.setParam(ctx->id("OUTMODE"), out_mode);
|
||||
|
||||
// disconnect Q output: it is wired internally
|
||||
nets_to_remove.push_back(ci.getPort(out_port)->name);
|
||||
out_iob->disconnectPort(id_I);
|
||||
ci.disconnectPort(out_port);
|
||||
set_daaj_nets(ci, iob_bel);
|
||||
|
||||
Loc io_loc = ctx->getBelLocation(iob_bel);
|
||||
if (io_loc.y == ctx->getGridDimY() - 1) {
|
||||
config_bottom_row(*out_iob, io_loc, Bottom_io_POD::DDR);
|
||||
}
|
||||
|
||||
// if Q1 is connected then disconnect it too
|
||||
if (gwu.port_used(&ci, tx_port)) {
|
||||
NPNR_ASSERT(out_iob == net_only_drives(ctx, ci.ports.at(tx_port).net, is_iob, id_OEN, true));
|
||||
nets_to_remove.push_back(ci.getPort(tx_port)->name);
|
||||
out_iob->disconnectPort(id_OEN);
|
||||
ci.disconnectPort(tx_port);
|
||||
} else { // disconnect TXx ports, ignore these nets
|
||||
switch (ci.type.hash()) {
|
||||
case ID_OSER8:
|
||||
ci.disconnectPort(id_TX3);
|
||||
ci.disconnectPort(id_TX2); /* fall-through */
|
||||
case ID_OSER4:
|
||||
ci.disconnectPort(id_TX1);
|
||||
ci.disconnectPort(id_TX0);
|
||||
break;
|
||||
case ID_ODDR: /* fall-through */
|
||||
case ID_ODDRC: /* fall-through */
|
||||
ci.disconnectPort(id_TX);
|
||||
break;
|
||||
}
|
||||
}
|
||||
make_iob_nets(*out_iob);
|
||||
}
|
||||
|
||||
void GowinPacker::pack_single_output_iol(CellInfo &ci, std::vector<IdString> &nets_to_remove)
|
||||
{
|
||||
IdString out_port = id_Q;
|
||||
|
||||
CellInfo *out_iob = net_only_drives(ctx, ci.ports.at(out_port).net, is_iob, id_I, true);
|
||||
NPNR_ASSERT(out_iob != nullptr && out_iob->bel != BelId());
|
||||
BelId iob_bel = out_iob->bel;
|
||||
|
||||
BelId l_bel = get_iologico_bel(out_iob);
|
||||
if (l_bel == BelId()) {
|
||||
log_error("Can't place IOLOGIC %s at %s\n", ctx->nameOf(&ci), ctx->nameOfBel(iob_bel));
|
||||
}
|
||||
// mark IOB as used by IOLOGIC
|
||||
out_iob->setParam(id_IOLOGIC_IOB, 1);
|
||||
check_iologic_placement(ci, ctx->getBelLocation(iob_bel),
|
||||
out_iob->params.count(id_DIFF_TYPE) || out_iob->params.count(id_MIPI_OBUF));
|
||||
|
||||
if (!ctx->checkBelAvail(l_bel)) {
|
||||
log_error("Can't place %s at %s because it's already taken by %s\n", ctx->nameOf(&ci), ctx->nameOfBel(l_bel),
|
||||
ctx->nameOf(ctx->getBoundBelCell(l_bel)));
|
||||
}
|
||||
ctx->bindBel(l_bel, &ci, PlaceStrength::STRENGTH_LOCKED);
|
||||
std::string out_mode;
|
||||
switch (ci.type.hash()) {
|
||||
case ID_IOLOGICO_EMPTY:
|
||||
out_mode = "EMPTY";
|
||||
break;
|
||||
case ID_OVIDEO:
|
||||
out_mode = "VIDEORX";
|
||||
break;
|
||||
case ID_OSER10:
|
||||
out_mode = "ODDRX5";
|
||||
break;
|
||||
}
|
||||
ci.setParam(ctx->id("OUTMODE"), out_mode);
|
||||
// disconnect Q output: it is wired internally
|
||||
nets_to_remove.push_back(ci.getPort(out_port)->name);
|
||||
out_iob->disconnectPort(id_I);
|
||||
ci.disconnectPort(out_port);
|
||||
if (ci.type == id_IOLOGICO_EMPTY) {
|
||||
if (ci.attrs.count(id_HAS_REG) == 0) {
|
||||
ci.movePortTo(id_D, out_iob, id_I);
|
||||
}
|
||||
return;
|
||||
}
|
||||
set_daaj_nets(ci, iob_bel);
|
||||
|
||||
Loc io_loc = ctx->getBelLocation(iob_bel);
|
||||
if (io_loc.y == ctx->getGridDimY() - 1) {
|
||||
config_bottom_row(*out_iob, io_loc, Bottom_io_POD::DDR);
|
||||
}
|
||||
make_iob_nets(*out_iob);
|
||||
}
|
||||
|
||||
BelId GowinPacker::get_aux_iologic_bel(const CellInfo &ci)
|
||||
{
|
||||
return ctx->getBelByLocation(gwu.get_pair_iologic_bel(ctx->getBelLocation(ci.bel)));
|
||||
}
|
||||
|
||||
bool GowinPacker::is_diff_io(BelId bel) { return ctx->getBoundBelCell(bel)->params.count(id_DIFF_TYPE) != 0; }
|
||||
bool GowinPacker::is_mipi_io(BelId bel)
|
||||
{
|
||||
return ctx->getBoundBelCell(bel)->params.count(id_MIPI_IBUF) ||
|
||||
ctx->getBoundBelCell(bel)->params.count(id_MIPI_OBUF);
|
||||
}
|
||||
|
||||
CellInfo *GowinPacker::create_aux_iologic_cell(CellInfo &ci, IdString mode, bool io16, int idx)
|
||||
{
|
||||
if (ci.type.in(id_ODDR, id_ODDRC, id_OSER4, id_IDDR, id_IDDRC, id_IDES4, id_IOLOGICI_EMPTY, id_IOLOGICO_EMPTY)) {
|
||||
return nullptr;
|
||||
}
|
||||
IdString aux_name = gwu.create_aux_name(ci.name, idx);
|
||||
BelId bel = get_aux_iologic_bel(ci);
|
||||
BelId io_bel = gwu.get_io_bel_from_iologic(bel);
|
||||
if (!ctx->checkBelAvail(io_bel)) {
|
||||
if (!(is_diff_io(io_bel) || is_mipi_io(io_bel))) {
|
||||
log_error("Can't place %s at %s because of a conflict with another IO %s\n", ctx->nameOf(&ci),
|
||||
ctx->nameOfBel(bel), ctx->nameOf(ctx->getBoundBelCell(io_bel)));
|
||||
}
|
||||
}
|
||||
|
||||
ctx->createCell(aux_name, id_IOLOGIC_DUMMY);
|
||||
CellInfo *aux = ctx->cells.at(aux_name).get();
|
||||
ci.copyPortTo(id_PCLK, aux, id_PCLK);
|
||||
ci.copyPortTo(id_RESET, aux, id_RESET);
|
||||
if (io16) {
|
||||
aux->setParam(mode, Property("DDRENABLE16"));
|
||||
} else {
|
||||
aux->setParam(mode, Property("DDRENABLE"));
|
||||
}
|
||||
aux->setAttr(ctx->id("IOLOGIC_TYPE"), Property("DUMMY"));
|
||||
aux->setAttr(ctx->id("MAIN_CELL"), Property(ci.name.str(ctx)));
|
||||
ctx->bindBel(bel, aux, PlaceStrength::STRENGTH_LOCKED);
|
||||
return aux;
|
||||
}
|
||||
|
||||
void GowinPacker::reconnect_ides_outs(CellInfo *ci)
|
||||
{
|
||||
IdString dest_ports[] = {id_Q9, id_Q8, id_Q7, id_Q6, id_Q5, id_Q4, id_Q3, id_Q2};
|
||||
switch (ci->type.hash()) {
|
||||
case ID_IDDR: /* fall-through*/
|
||||
case ID_IDDRC:
|
||||
ci->renamePort(id_Q1, id_Q9);
|
||||
ci->renamePort(id_Q0, id_Q8);
|
||||
break;
|
||||
case ID_IDES4:
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
ci->renamePort(ctx->idf("Q%d", 3 - i), dest_ports[i]);
|
||||
}
|
||||
break;
|
||||
case ID_IVIDEO:
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
ci->renamePort(ctx->idf("Q%d", 6 - i), dest_ports[i]);
|
||||
}
|
||||
break;
|
||||
case ID_IDES8:
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
ci->renamePort(ctx->idf("Q%d", 7 - i), dest_ports[i]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GowinPacker::pack_ides_iol(CellInfo &ci, std::vector<IdString> &nets_to_remove)
|
||||
{
|
||||
IdString in_port = id_D;
|
||||
|
||||
CellInfo *in_iob = net_driven_by(ctx, ci.ports.at(in_port).net, is_iob, id_O);
|
||||
NPNR_ASSERT(in_iob != nullptr && in_iob->bel != BelId());
|
||||
BelId iob_bel = in_iob->bel;
|
||||
|
||||
BelId l_bel = get_iologici_bel(in_iob);
|
||||
if (l_bel == BelId()) {
|
||||
log_error("Can't place IOLOGIC %s at %s\n", ctx->nameOf(&ci), ctx->nameOfBel(iob_bel));
|
||||
}
|
||||
// mark IOB as used by IOLOGIC
|
||||
in_iob->setParam(id_IOLOGIC_IOB, 1);
|
||||
check_iologic_placement(ci, ctx->getBelLocation(iob_bel),
|
||||
in_iob->params.count(id_DIFF_TYPE) || in_iob->params.count(id_MIPI_IBUF));
|
||||
|
||||
if (!ctx->checkBelAvail(l_bel)) {
|
||||
log_error("Can't place %s at %s because it's already taken by %s\n", ctx->nameOf(&ci), ctx->nameOfBel(l_bel),
|
||||
ctx->nameOf(ctx->getBoundBelCell(l_bel)));
|
||||
}
|
||||
ctx->bindBel(l_bel, &ci, PlaceStrength::STRENGTH_LOCKED);
|
||||
std::string in_mode;
|
||||
switch (ci.type.hash()) {
|
||||
case ID_IOLOGICI_EMPTY:
|
||||
in_mode = "EMPTY";
|
||||
break;
|
||||
case ID_IDDR:
|
||||
case ID_IDDRC:
|
||||
in_mode = "IDDRX1";
|
||||
break;
|
||||
case ID_IDES4:
|
||||
in_mode = "IDDRX2";
|
||||
break;
|
||||
case ID_IDES8:
|
||||
in_mode = "IDDRX4";
|
||||
break;
|
||||
case ID_IDES10:
|
||||
in_mode = "IDDRX5";
|
||||
break;
|
||||
case ID_IVIDEO:
|
||||
in_mode = "VIDEORX";
|
||||
break;
|
||||
}
|
||||
ci.setParam(ctx->id("INMODE"), in_mode);
|
||||
// disconnect D input: it is wired internally
|
||||
nets_to_remove.push_back(ci.getPort(in_port)->name);
|
||||
in_iob->disconnectPort(id_O);
|
||||
ci.disconnectPort(in_port);
|
||||
if (ci.type == id_IOLOGICI_EMPTY) {
|
||||
if (ci.attrs.count(id_HAS_REG) == 0) {
|
||||
ci.movePortTo(id_Q, in_iob, id_O);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
set_daaj_nets(ci, iob_bel);
|
||||
reconnect_ides_outs(&ci);
|
||||
|
||||
make_iob_nets(*in_iob);
|
||||
}
|
||||
|
||||
void GowinPacker::pack_iem(void)
|
||||
{
|
||||
log_info("Pack Input Edge Monitors...\n");
|
||||
std::vector<IdString> cells_to_remove;
|
||||
std::vector<std::unique_ptr<CellInfo>> new_cells;
|
||||
|
||||
for (auto &cell : ctx->cells) {
|
||||
CellInfo &ci = *cell.second;
|
||||
if (ci.type != id_IEM) {
|
||||
continue;
|
||||
}
|
||||
if (ctx->debug) {
|
||||
log_info("pack %s of type %s.\n", ctx->nameOf(&ci), ci.type.c_str(ctx));
|
||||
}
|
||||
// IEM is part of IOLOGIC but functions independently of the
|
||||
// presence/absence of other IOLOGIC components. Therefore, we use
|
||||
// the existing cell whenever possible.
|
||||
const NetInfo *d_net = ci.ports.at(id_D).net;
|
||||
CellInfo *in_iob = net_driven_by(ctx, d_net, is_iob, id_O);
|
||||
NPNR_ASSERT(in_iob != nullptr && in_iob->bel != BelId());
|
||||
BelId iob_bel = in_iob->bel;
|
||||
|
||||
BelId l_bel = get_iologici_bel(in_iob);
|
||||
if (l_bel == BelId()) {
|
||||
log_error("Can't place IOLOGIC %s at %s\n", ctx->nameOf(&ci), ctx->nameOfBel(iob_bel));
|
||||
}
|
||||
CellInfo *iologic = nullptr;
|
||||
for (auto &usr : d_net->users) {
|
||||
if (is_iologici(usr.cell)) {
|
||||
if (ctx->debug) {
|
||||
log_info(" found IOLOGIC cell %s of type %s, use it.\n", ctx->nameOf(usr.cell),
|
||||
usr.cell->type.c_str(ctx));
|
||||
}
|
||||
iologic = usr.cell;
|
||||
if (iologic->ports.count(id_CLK)) {
|
||||
NPNR_ASSERT(iologic->ports.at(id_CLK).net == ci.ports.at(id_CLK).net);
|
||||
} else {
|
||||
if (iologic->ports.count(id_PCLK)) {
|
||||
NPNR_ASSERT(iologic->ports.at(id_PCLK).net == ci.ports.at(id_CLK).net);
|
||||
}
|
||||
iologic->addInput(ctx->id("CLK"));
|
||||
}
|
||||
if (iologic->ports.count(id_RESET)) {
|
||||
NPNR_ASSERT(iologic->ports.at(id_RESET).net == ci.ports.at(id_RESET).net);
|
||||
} else {
|
||||
iologic->addInput(ctx->id("RESET"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (iologic == nullptr) {
|
||||
IdString iologic_name = gwu.create_aux_name(ci.name);
|
||||
if (ctx->debug) {
|
||||
log_info(" create IOLOGIC cell %s.\n", iologic_name.c_str(ctx));
|
||||
}
|
||||
auto iologic_cell = gwu.create_cell(iologic_name, id_IOLOGICI_EMPTY);
|
||||
new_cells.push_back(std::move(iologic_cell));
|
||||
iologic = new_cells.back().get();
|
||||
ci.copyPortTo(id_D, iologic, id_D);
|
||||
ci.copyPortTo(id_CLK, iologic, id_CLK);
|
||||
ci.copyPortTo(id_RESET, iologic, id_RESET);
|
||||
}
|
||||
ci.movePortTo(id_MCLK, iologic, id_MCLK);
|
||||
ci.movePortTo(id_LAG, iologic, id_LAG);
|
||||
ci.movePortTo(id_LEAD, iologic, id_LEAD);
|
||||
|
||||
ci.disconnectPort(id_D);
|
||||
ci.disconnectPort(id_CLK);
|
||||
ci.disconnectPort(id_RESET);
|
||||
|
||||
// WINSIZE attribute defines routing to ports WINSIZE0/1
|
||||
iologic->addInput(id_WINSIZE0);
|
||||
iologic->addInput(id_WINSIZE1);
|
||||
if (ci.params.count(id_WINSIZE) == 0) {
|
||||
ci.setParam(id_WINSIZE, Property("SMALL"));
|
||||
}
|
||||
|
||||
NetInfo *vcc_net = ctx->nets.at(ctx->id("$PACKER_VCC")).get();
|
||||
NetInfo *vss_net = ctx->nets.at(ctx->id("$PACKER_GND")).get();
|
||||
IdString winsize = ctx->id(ci.params.at(id_WINSIZE).as_string());
|
||||
switch (winsize.hash()) {
|
||||
case ID_SMALL:
|
||||
iologic->connectPort(id_WINSIZE0, vss_net);
|
||||
iologic->connectPort(id_WINSIZE1, vss_net);
|
||||
break;
|
||||
case ID_MIDSMALL:
|
||||
iologic->connectPort(id_WINSIZE0, vcc_net);
|
||||
iologic->connectPort(id_WINSIZE1, vss_net);
|
||||
break;
|
||||
case ID_MIDLARGE:
|
||||
iologic->connectPort(id_WINSIZE0, vss_net);
|
||||
iologic->connectPort(id_WINSIZE1, vcc_net);
|
||||
break;
|
||||
case ID_LARGE:
|
||||
iologic->connectPort(id_WINSIZE0, vcc_net);
|
||||
iologic->connectPort(id_WINSIZE1, vcc_net);
|
||||
break;
|
||||
default:
|
||||
log_error("%s has incorrect WINSIZE:%s\n", ctx->nameOf(&ci), ci.params.at(id_WINSIZE).c_str());
|
||||
}
|
||||
|
||||
if (ci.params.count(id_GSREN) != 0) {
|
||||
if (iologic->params.count(id_GSREN) == 0) {
|
||||
iologic->setParam(id_GSREN, ci.params.at(id_GSREN));
|
||||
} else {
|
||||
if (ci.params.at(id_GSREN) != iologic->params.at(id_GSREN)) {
|
||||
log_error("GSREN parameter values of %s and %s do not match.\n", ctx->nameOf(&ci),
|
||||
ctx->nameOf(iologic));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ci.params.count(id_LSREN) != 0) {
|
||||
if (iologic->params.count(id_LSREN) == 0) {
|
||||
iologic->setParam(id_LSREN, ci.params.at(id_LSREN));
|
||||
} else {
|
||||
if (ci.params.at(id_LSREN) != iologic->params.at(id_LSREN)) {
|
||||
log_error("LSREN parameter values of %s and %s do not match.\n", ctx->nameOf(&ci),
|
||||
ctx->nameOf(iologic));
|
||||
}
|
||||
}
|
||||
}
|
||||
cells_to_remove.push_back(ci.name);
|
||||
}
|
||||
|
||||
for (auto cell : cells_to_remove) {
|
||||
ctx->cells.erase(cell);
|
||||
}
|
||||
|
||||
for (auto &ncell : new_cells) {
|
||||
ctx->cells[ncell->name] = std::move(ncell);
|
||||
}
|
||||
}
|
||||
|
||||
void GowinPacker::pack_iodelay(void)
|
||||
{
|
||||
log_info("Pack IODELAY...\n");
|
||||
std::vector<IdString> cells_to_remove;
|
||||
std::vector<IdString> nets_to_remove;
|
||||
std::vector<std::unique_ptr<CellInfo>> new_cells;
|
||||
|
||||
for (auto &cell : ctx->cells) {
|
||||
CellInfo &ci = *cell.second;
|
||||
if (ci.type != id_IODELAY) {
|
||||
continue;
|
||||
}
|
||||
if (ctx->debug) {
|
||||
log_info("pack %s of type %s.\n", ctx->nameOf(&ci), ci.type.c_str(ctx));
|
||||
}
|
||||
// There is only one delay line in the IO block, which can be either
|
||||
// input or output. Define which case we are dealing with.
|
||||
bool is_idelay = false;
|
||||
NetInfo *di_net = ci.ports.at(id_DI).net;
|
||||
NetInfo *do_net = ci.ports.at(id_DO).net;
|
||||
CellInfo *iob = net_driven_by(ctx, di_net, is_iob, id_O);
|
||||
if (iob != nullptr) {
|
||||
NPNR_ASSERT(iob->bel != BelId());
|
||||
if (di_net->users.entries() != 1) {
|
||||
log_error("IODELAY %s should be the only sink in the %s network.\n", ctx->nameOf(&ci),
|
||||
ctx->nameOf(di_net));
|
||||
}
|
||||
is_idelay = true;
|
||||
} else {
|
||||
iob = net_only_drives(ctx, do_net, is_iob, id_I, true);
|
||||
if (iob != nullptr) {
|
||||
NPNR_ASSERT(iob->bel != BelId());
|
||||
} else {
|
||||
log_error("IODELAY %s is not connected to the pin.\n", ctx->nameOf(&ci));
|
||||
}
|
||||
}
|
||||
|
||||
BelId iob_bel = iob->bel;
|
||||
BelId l_bel = get_iologici_bel(iob);
|
||||
if (l_bel == BelId()) {
|
||||
log_error("Can't place IOLOGIC %s at %s\n", ctx->nameOf(&ci), ctx->nameOfBel(iob_bel));
|
||||
}
|
||||
|
||||
// find IOLOGIC connected or create dummy one
|
||||
CellInfo *iologic = nullptr;
|
||||
Property attr;
|
||||
IdString dummy_iol_type;
|
||||
if (is_idelay) {
|
||||
attr = Property("IN");
|
||||
dummy_iol_type = id_IOLOGICI_EMPTY;
|
||||
for (auto &usr : do_net->users) {
|
||||
if (is_iologici(usr.cell)) {
|
||||
iologic = usr.cell;
|
||||
if (iologic->attrs.count(id_IODELAY) != 0) {
|
||||
log_error("Only one IODELAY allowed per IO block %s.\n", ctx->nameOfBel(iob->bel));
|
||||
}
|
||||
if (ctx->debug) {
|
||||
log_info(" found IOLOGIC cell %s of type %s, use it.\n", ctx->nameOf(iologic),
|
||||
iologic->type.c_str(ctx));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
attr = Property("OUT");
|
||||
dummy_iol_type = id_IOLOGICO_EMPTY;
|
||||
if (is_iologico(di_net->driver.cell)) {
|
||||
iologic = di_net->driver.cell;
|
||||
if (iologic->attrs.count(id_IODELAY) != 0) {
|
||||
log_error("Only one IODELAY allowed per IO block %s.\n", ctx->nameOfBel(iob->bel));
|
||||
}
|
||||
if (ctx->debug) {
|
||||
log_info(" found IOLOGIC cell %s of type %s, use it.\n", ctx->nameOf(iologic),
|
||||
iologic->type.c_str(ctx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (iologic == nullptr) {
|
||||
IdString iologic_name = gwu.create_aux_name(ci.name);
|
||||
if (ctx->debug) {
|
||||
log_info(" create IOLOGIC cell %s.\n", iologic_name.c_str(ctx));
|
||||
}
|
||||
auto iologic_cell = gwu.create_cell(iologic_name, dummy_iol_type);
|
||||
new_cells.push_back(std::move(iologic_cell));
|
||||
iologic = new_cells.back().get();
|
||||
iologic->addInput(id_D);
|
||||
iologic->addOutput(id_Q);
|
||||
ci.movePortTo(id_DI, iologic, id_D);
|
||||
ci.movePortTo(id_DO, iologic, id_Q);
|
||||
} else {
|
||||
if (is_idelay) {
|
||||
iob->disconnectPort(id_O);
|
||||
ci.disconnectPort(id_I);
|
||||
ci.movePortTo(id_DO, iob, id_O);
|
||||
} else {
|
||||
IdString iol_out = di_net->driver.port;
|
||||
ci.disconnectPort(id_DI);
|
||||
iologic->disconnectPort(iol_out);
|
||||
ci.movePortTo(id_DO, iologic, iol_out);
|
||||
}
|
||||
nets_to_remove.push_back(di_net->name);
|
||||
}
|
||||
|
||||
ci.movePortTo(id_SDTAP, iologic, id_SDTAP);
|
||||
ci.movePortTo(id_SETN, iologic, id_SETN);
|
||||
ci.movePortTo(id_VALUE, iologic, id_VALUE);
|
||||
ci.movePortTo(id_DF, iologic, id_DF);
|
||||
|
||||
if (ci.params.count(id_C_STATIC_DLY)) {
|
||||
iologic->setParam(id_C_STATIC_DLY, ci.params.at(id_C_STATIC_DLY));
|
||||
}
|
||||
iologic->setAttr(id_IODELAY, attr);
|
||||
cells_to_remove.push_back(ci.name);
|
||||
}
|
||||
for (auto cell : cells_to_remove) {
|
||||
ctx->cells.erase(cell);
|
||||
}
|
||||
|
||||
for (auto &ncell : new_cells) {
|
||||
ctx->cells[ncell->name] = std::move(ncell);
|
||||
}
|
||||
|
||||
for (auto net : nets_to_remove) {
|
||||
ctx->nets.erase(net);
|
||||
}
|
||||
}
|
||||
|
||||
void GowinPacker::pack_iologic(void)
|
||||
{
|
||||
log_info("Pack IO logic...\n");
|
||||
std::vector<IdString> nets_to_remove;
|
||||
|
||||
for (auto &cell : ctx->cells) {
|
||||
CellInfo &ci = *cell.second;
|
||||
if (!(is_iologici(&ci) || is_iologico(&ci))) {
|
||||
continue;
|
||||
}
|
||||
if (ctx->debug) {
|
||||
log_info("pack %s of type %s.\n", ctx->nameOf(&ci), ci.type.c_str(ctx));
|
||||
}
|
||||
if (ci.type.in(id_ODDR, id_ODDRC, id_OSER4, id_OSER8)) {
|
||||
pack_bi_output_iol(ci, nets_to_remove);
|
||||
create_aux_iologic_cell(ci, ctx->id("OUTMODE"));
|
||||
continue;
|
||||
}
|
||||
if (ci.type.in(id_OVIDEO, id_OSER10, id_IOLOGICO_EMPTY)) {
|
||||
pack_single_output_iol(ci, nets_to_remove);
|
||||
create_aux_iologic_cell(ci, ctx->id("OUTMODE"));
|
||||
continue;
|
||||
}
|
||||
if (ci.type.in(id_IDDR, id_IDDRC, id_IDES4, id_IDES8, id_IDES10, id_IVIDEO, id_IOLOGICI_EMPTY)) {
|
||||
pack_ides_iol(ci, nets_to_remove);
|
||||
create_aux_iologic_cell(ci, ctx->id("INMODE"));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto net : nets_to_remove) {
|
||||
ctx->nets.erase(net);
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// IDES16 / OSER16
|
||||
// ===================================
|
||||
void GowinPacker::check_io16_placement(CellInfo &ci, Loc main_loc, Loc aux_off, int diff /* 1 - diff */)
|
||||
{
|
||||
if (main_loc.z != BelZ::IOBA_Z) {
|
||||
log_error("Can't place %s at %s because OSER16/IDES16 must be placed at A pin\n", ctx->nameOf(&ci),
|
||||
ctx->nameOfBel(ctx->getBelByLocation(main_loc)));
|
||||
}
|
||||
|
||||
int mod[][3] = {{0, 0, 1}, {1, 1, 0}, {1, 1, 1}};
|
||||
for (int i = diff; i < 3; ++i) {
|
||||
Loc aux_loc(main_loc.x + mod[i][0] * aux_off.x, main_loc.y + mod[i][1] * aux_off.y, main_loc.z + mod[i][2]);
|
||||
BelId l_bel = ctx->getBelByLocation(aux_loc);
|
||||
if (!ctx->checkBelAvail(l_bel)) {
|
||||
log_error("Can't place %s at %s because it's already taken by %s\n", ctx->nameOf(&ci),
|
||||
ctx->nameOfBel(l_bel), ctx->nameOf(ctx->getBoundBelCell(l_bel)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GowinPacker::pack_oser16(CellInfo &ci, std::vector<IdString> &nets_to_remove)
|
||||
{
|
||||
IdString out_port = id_Q;
|
||||
|
||||
CellInfo *out_iob = net_only_drives(ctx, ci.ports.at(out_port).net, is_iob, id_I, true);
|
||||
NPNR_ASSERT(out_iob != nullptr && out_iob->bel != BelId());
|
||||
// mark IOB as used by IOLOGIC
|
||||
out_iob->setParam(id_IOLOGIC_IOB, 1);
|
||||
|
||||
BelId iob_bel = out_iob->bel;
|
||||
|
||||
Loc iob_loc = ctx->getBelLocation(iob_bel);
|
||||
Loc aux_offset = gwu.get_tile_io16_offs(iob_loc.x, iob_loc.y);
|
||||
|
||||
if (aux_offset.x == 0 && aux_offset.y == 0) {
|
||||
log_error("OSER16 %s can not be placed at %s\n", ctx->nameOf(&ci), ctx->nameOfBel(iob_bel));
|
||||
}
|
||||
check_io16_placement(ci, iob_loc, aux_offset, out_iob->params.count(id_DIFF_TYPE));
|
||||
|
||||
BelId main_bel = ctx->getBelByLocation(Loc(iob_loc.x, iob_loc.y, BelZ::OSER16_Z));
|
||||
ctx->bindBel(main_bel, &ci, PlaceStrength::STRENGTH_LOCKED);
|
||||
|
||||
// disconnect Q output: it is wired internally
|
||||
nets_to_remove.push_back(ci.getPort(out_port)->name);
|
||||
out_iob->disconnectPort(id_I);
|
||||
ci.disconnectPort(out_port);
|
||||
|
||||
// to simplify packaging, the parts of the OSER16 are presented as IOLOGIC cells
|
||||
// and one of these aux cells is declared as main
|
||||
IdString main_name = gwu.create_aux_name(ci.name);
|
||||
|
||||
IdString aux_name = gwu.create_aux_name(ci.name, 1);
|
||||
ctx->createCell(aux_name, id_IOLOGIC_DUMMY);
|
||||
CellInfo *aux = ctx->cells.at(aux_name).get();
|
||||
|
||||
aux->setAttr(ctx->id("MAIN_CELL"), Property(main_name.str(ctx)));
|
||||
aux->setParam(ctx->id("OUTMODE"), Property("ODDRX8"));
|
||||
aux->setParam(ctx->id("UPDATE"), Property("SAME"));
|
||||
aux->setAttr(ctx->id("IOLOGIC_TYPE"), Property("DUMMY"));
|
||||
ci.copyPortTo(id_PCLK, aux, id_PCLK);
|
||||
ci.copyPortTo(id_RESET, aux, id_RESET);
|
||||
ctx->bindBel(ctx->getBelByLocation(Loc(iob_loc.x, iob_loc.y, BelZ::IOLOGICA_Z)), aux,
|
||||
PlaceStrength::STRENGTH_LOCKED);
|
||||
|
||||
// make aux cell in the first cell
|
||||
aux = create_aux_iologic_cell(*aux, ctx->id("OUTMODE"), true, 2);
|
||||
aux->setAttr(ctx->id("MAIN_CELL"), Property(main_name.str(ctx)));
|
||||
aux->setParam(ctx->id("UPDATE"), Property("SAME"));
|
||||
|
||||
// make cell in the next location
|
||||
ctx->createCell(main_name, id_IOLOGIC);
|
||||
aux = ctx->cells.at(main_name).get();
|
||||
|
||||
aux->setAttr(ctx->id("MAIN_CELL"), Property(main_name.str(ctx)));
|
||||
aux->setParam(ctx->id("OUTMODE"), Property("DDRENABLE16"));
|
||||
aux->setParam(ctx->id("UPDATE"), Property("SAME"));
|
||||
aux->setAttr(ctx->id("IOLOGIC_TYPE"), Property("DUMMY"));
|
||||
ci.copyPortTo(id_PCLK, aux, id_PCLK);
|
||||
ci.copyPortTo(id_RESET, aux, id_RESET);
|
||||
ci.movePortTo(id_FCLK, aux, id_FCLK);
|
||||
ci.movePortTo(id_D12, aux, id_D0);
|
||||
ci.movePortTo(id_D13, aux, id_D1);
|
||||
ci.movePortTo(id_D14, aux, id_D2);
|
||||
ci.movePortTo(id_D15, aux, id_D3);
|
||||
Loc next_io16(iob_loc.x + aux_offset.x, iob_loc.y + aux_offset.y, BelZ::IOLOGICA_Z);
|
||||
ctx->bindBel(ctx->getBelByLocation(next_io16), aux, PlaceStrength::STRENGTH_LOCKED);
|
||||
|
||||
Loc io_loc = ctx->getBelLocation(iob_bel);
|
||||
if (io_loc.y == ctx->getGridDimY() - 1) {
|
||||
config_bottom_row(*out_iob, io_loc, Bottom_io_POD::DDR);
|
||||
}
|
||||
make_iob_nets(*out_iob);
|
||||
}
|
||||
|
||||
void GowinPacker::pack_ides16(CellInfo &ci, std::vector<IdString> &nets_to_remove)
|
||||
{
|
||||
IdString in_port = id_D;
|
||||
|
||||
CellInfo *in_iob = net_driven_by(ctx, ci.ports.at(in_port).net, is_iob, id_O);
|
||||
NPNR_ASSERT(in_iob != nullptr && in_iob->bel != BelId());
|
||||
// mark IOB as used by IOLOGIC
|
||||
in_iob->setParam(id_IOLOGIC_IOB, 1);
|
||||
|
||||
BelId iob_bel = in_iob->bel;
|
||||
|
||||
Loc iob_loc = ctx->getBelLocation(iob_bel);
|
||||
Loc aux_offset = gwu.get_tile_io16_offs(iob_loc.x, iob_loc.y);
|
||||
|
||||
if (aux_offset.x == 0 && aux_offset.y == 0) {
|
||||
log_error("IDES16 %s can not be placed at %s\n", ctx->nameOf(&ci), ctx->nameOfBel(iob_bel));
|
||||
}
|
||||
check_io16_placement(ci, iob_loc, aux_offset, in_iob->params.count(id_DIFF_TYPE));
|
||||
|
||||
BelId main_bel = ctx->getBelByLocation(Loc(iob_loc.x, iob_loc.y, BelZ::IDES16_Z));
|
||||
ctx->bindBel(main_bel, &ci, PlaceStrength::STRENGTH_LOCKED);
|
||||
|
||||
// disconnect Q output: it is wired internally
|
||||
nets_to_remove.push_back(ci.getPort(in_port)->name);
|
||||
in_iob->disconnectPort(id_O);
|
||||
ci.disconnectPort(in_port);
|
||||
|
||||
// to simplify packaging, the parts of the IDES16 are presented as IOLOGIC cells
|
||||
// and one of these aux cells is declared as main
|
||||
IdString main_name = gwu.create_aux_name(ci.name);
|
||||
|
||||
IdString aux_name = gwu.create_aux_name(ci.name, 1);
|
||||
ctx->createCell(aux_name, id_IOLOGIC_DUMMY);
|
||||
CellInfo *aux = ctx->cells.at(aux_name).get();
|
||||
|
||||
aux->setAttr(ctx->id("MAIN_CELL"), Property(main_name.str(ctx)));
|
||||
aux->setParam(ctx->id("INMODE"), Property("IDDRX8"));
|
||||
aux->setAttr(ctx->id("IOLOGIC_TYPE"), Property("DUMMY"));
|
||||
ci.copyPortTo(id_PCLK, aux, id_PCLK);
|
||||
ci.copyPortTo(id_RESET, aux, id_RESET);
|
||||
ctx->bindBel(ctx->getBelByLocation(Loc(iob_loc.x, iob_loc.y, BelZ::IOLOGICA_Z)), aux,
|
||||
PlaceStrength::STRENGTH_LOCKED);
|
||||
|
||||
// make aux cell in the first cell
|
||||
aux = create_aux_iologic_cell(*aux, ctx->id("INMODE"), true, 2);
|
||||
aux->setAttr(ctx->id("MAIN_CELL"), Property(main_name.str(ctx)));
|
||||
ci.copyPortTo(id_CALIB, aux, id_CALIB);
|
||||
|
||||
// make cell in the next location
|
||||
ctx->createCell(main_name, id_IOLOGIC);
|
||||
aux = ctx->cells.at(main_name).get();
|
||||
|
||||
aux->setAttr(ctx->id("MAIN_CELL"), Property(main_name.str(ctx)));
|
||||
aux->setParam(ctx->id("INMODE"), Property("DDRENABLE16"));
|
||||
aux->setAttr(ctx->id("IOLOGIC_TYPE"), Property("DUMMY"));
|
||||
ci.copyPortTo(id_PCLK, aux, id_PCLK);
|
||||
ci.copyPortTo(id_RESET, aux, id_RESET);
|
||||
ci.copyPortTo(id_CALIB, aux, id_CALIB);
|
||||
ci.movePortTo(id_FCLK, aux, id_FCLK);
|
||||
ci.movePortTo(id_Q0, aux, id_Q6);
|
||||
ci.movePortTo(id_Q1, aux, id_Q7);
|
||||
ci.movePortTo(id_Q2, aux, id_Q8);
|
||||
ci.movePortTo(id_Q3, aux, id_Q9);
|
||||
Loc next_io16(iob_loc.x + aux_offset.x, iob_loc.y + aux_offset.y, BelZ::IOLOGICA_Z);
|
||||
ctx->bindBel(ctx->getBelByLocation(next_io16), aux, PlaceStrength::STRENGTH_LOCKED);
|
||||
|
||||
make_iob_nets(*in_iob);
|
||||
}
|
||||
|
||||
void GowinPacker::pack_io16(void)
|
||||
{
|
||||
std::vector<IdString> nets_to_remove;
|
||||
log_info("Pack DESER16 logic...\n");
|
||||
|
||||
for (auto &cell : ctx->cells) {
|
||||
CellInfo &ci = *cell.second;
|
||||
if (ci.type == id_OSER16) {
|
||||
if (ctx->debug) {
|
||||
log_info("pack %s of type %s.\n", ctx->nameOf(&ci), ci.type.c_str(ctx));
|
||||
}
|
||||
pack_oser16(ci, nets_to_remove);
|
||||
continue;
|
||||
}
|
||||
if (ci.type == id_IDES16) {
|
||||
if (ctx->debug) {
|
||||
log_info("pack %s of type %s.\n", ctx->nameOf(&ci), ci.type.c_str(ctx));
|
||||
}
|
||||
pack_ides16(ci, nets_to_remove);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (auto net : nets_to_remove) {
|
||||
ctx->nets.erase(net);
|
||||
}
|
||||
}
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
|
@ -0,0 +1,600 @@
|
|||
#include "design_utils.h"
|
||||
#include "log.h"
|
||||
#include "nextpnr.h"
|
||||
|
||||
#define HIMBAECHEL_CONSTIDS "uarch/gowin/constids.inc"
|
||||
#include "himbaechel_constids.h"
|
||||
#include "himbaechel_helpers.h"
|
||||
|
||||
#include "gowin.h"
|
||||
#include "gowin_utils.h"
|
||||
#include "pack.h"
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
NEXTPNR_NAMESPACE_BEGIN
|
||||
|
||||
// ===================================
|
||||
// Constant nets
|
||||
// ===================================
|
||||
void GowinPacker::handle_constants(void)
|
||||
{
|
||||
log_info("Create constant nets...\n");
|
||||
const dict<IdString, Property> vcc_params;
|
||||
const dict<IdString, Property> gnd_params;
|
||||
h.replace_constants(CellTypePort(id_GOWIN_VCC, id_V), CellTypePort(id_GOWIN_GND, id_G), vcc_params, gnd_params);
|
||||
|
||||
// disconnect the constant LUT inputs
|
||||
log_info("Modify LUTs...\n");
|
||||
for (IdString netname : {ctx->id("$PACKER_GND"), ctx->id("$PACKER_VCC")}) {
|
||||
auto net = ctx->nets.find(netname);
|
||||
if (net == ctx->nets.end()) {
|
||||
continue;
|
||||
}
|
||||
NetInfo *constnet = net->second.get();
|
||||
constnet->constant_value = (constnet->name == ctx->id("$PACKER_GND")) ? id_VSS : id_VCC;
|
||||
for (auto user : constnet->users) {
|
||||
CellInfo *uc = user.cell;
|
||||
if (is_lut(uc) && (user.port.str(ctx).at(0) == 'I')) {
|
||||
if (ctx->debug) {
|
||||
log_info("%s user %s/%s\n", ctx->nameOf(constnet), ctx->nameOf(uc), user.port.c_str(ctx));
|
||||
}
|
||||
|
||||
auto it_param = uc->params.find(id_INIT);
|
||||
if (it_param == uc->params.end())
|
||||
log_error("No initialization for lut found.\n");
|
||||
|
||||
int64_t uc_init = it_param->second.intval;
|
||||
int64_t mask = 0;
|
||||
uint8_t amt = 0;
|
||||
|
||||
if (user.port == id_I0) {
|
||||
mask = 0x5555;
|
||||
amt = 1;
|
||||
} else if (user.port == id_I1) {
|
||||
mask = 0x3333;
|
||||
amt = 2;
|
||||
} else if (user.port == id_I2) {
|
||||
mask = 0x0F0F;
|
||||
amt = 4;
|
||||
} else if (user.port == id_I3) {
|
||||
mask = 0x00FF;
|
||||
amt = 8;
|
||||
} else {
|
||||
log_error("Port number invalid.\n");
|
||||
}
|
||||
|
||||
if ((constnet->name == ctx->id("$PACKER_GND"))) {
|
||||
uc_init = (uc_init & mask) | ((uc_init & mask) << amt);
|
||||
} else {
|
||||
uc_init = (uc_init & (mask << amt)) | ((uc_init & (mask << amt)) >> amt);
|
||||
}
|
||||
|
||||
size_t uc_init_len = it_param->second.to_string().length();
|
||||
uc_init &= (1LL << uc_init_len) - 1;
|
||||
|
||||
if (ctx->verbose && it_param->second.intval != uc_init)
|
||||
log_info("%s lut config modified from 0x%" PRIX64 " to 0x%" PRIX64 "\n", ctx->nameOf(uc),
|
||||
it_param->second.intval, uc_init);
|
||||
|
||||
it_param->second = Property(uc_init, uc_init_len);
|
||||
uc->disconnectPort(user.port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// Wideluts
|
||||
// ===================================
|
||||
void GowinPacker::pack_wideluts(void)
|
||||
{
|
||||
log_info("Pack wide LUTs...\n");
|
||||
// children's offsets
|
||||
struct _children
|
||||
{
|
||||
IdString port;
|
||||
int dx, dz;
|
||||
} mux_inputs[4][2] = {{{id_I0, 1, -7}, {id_I1, 0, -7}},
|
||||
{{id_I0, 0, 4}, {id_I1, 0, -4}},
|
||||
{{id_I0, 0, 2}, {id_I1, 0, -2}},
|
||||
{{id_I0, 0, -BelZ::MUX20_Z}, {id_I1, 0, 2 - BelZ::MUX20_Z}}};
|
||||
typedef std::function<void(CellInfo &, CellInfo *, int, int)> recurse_func_t;
|
||||
recurse_func_t make_cluster = [&, this](CellInfo &ci_root, CellInfo *ci_cursor, int dx, int dz) {
|
||||
_children *inputs;
|
||||
if (is_lut(ci_cursor)) {
|
||||
return;
|
||||
}
|
||||
switch (ci_cursor->type.hash()) {
|
||||
case ID_MUX2_LUT8:
|
||||
inputs = mux_inputs[0];
|
||||
break;
|
||||
case ID_MUX2_LUT7:
|
||||
inputs = mux_inputs[1];
|
||||
break;
|
||||
case ID_MUX2_LUT6:
|
||||
inputs = mux_inputs[2];
|
||||
break;
|
||||
case ID_MUX2_LUT5:
|
||||
inputs = mux_inputs[3];
|
||||
break;
|
||||
default:
|
||||
log_error("Bad MUX2 node:%s\n", ctx->nameOf(ci_cursor));
|
||||
}
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
// input src
|
||||
NetInfo *in = ci_cursor->getPort(inputs[i].port);
|
||||
NPNR_ASSERT(in && in->driver.cell && in->driver.cell->cluster == ClusterId());
|
||||
int child_dx = dx + inputs[i].dx;
|
||||
int child_dz = dz + inputs[i].dz;
|
||||
ci_root.constr_children.push_back(in->driver.cell);
|
||||
in->driver.cell->cluster = ci_root.name;
|
||||
in->driver.cell->constr_abs_z = false;
|
||||
in->driver.cell->constr_x = child_dx;
|
||||
in->driver.cell->constr_y = 0;
|
||||
in->driver.cell->constr_z = child_dz;
|
||||
make_cluster(ci_root, in->driver.cell, child_dx, child_dz);
|
||||
}
|
||||
};
|
||||
|
||||
// look for MUX2
|
||||
// MUX2_LUT8 create right away, collect others
|
||||
std::vector<IdString> muxes[3];
|
||||
int packed[4] = {0, 0, 0, 0};
|
||||
for (auto &cell : ctx->cells) {
|
||||
auto &ci = *cell.second;
|
||||
if (ci.cluster != ClusterId()) {
|
||||
continue;
|
||||
}
|
||||
if (ci.type == id_MUX2_LUT8) {
|
||||
ci.cluster = ci.name;
|
||||
ci.constr_abs_z = false;
|
||||
make_cluster(ci, &ci, 0, 0);
|
||||
++packed[0];
|
||||
continue;
|
||||
}
|
||||
if (ci.type.in(id_MUX2_LUT7, id_MUX2_LUT6, id_MUX2_LUT5)) {
|
||||
switch (ci.type.hash()) {
|
||||
case ID_MUX2_LUT7:
|
||||
muxes[0].push_back(cell.first);
|
||||
break;
|
||||
case ID_MUX2_LUT6:
|
||||
muxes[1].push_back(cell.first);
|
||||
break;
|
||||
default: // ID_MUX2_LUT5
|
||||
muxes[2].push_back(cell.first);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// create others
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
for (IdString cell_name : muxes[i]) {
|
||||
auto &ci = *ctx->cells.at(cell_name);
|
||||
if (ci.cluster != ClusterId()) {
|
||||
continue;
|
||||
}
|
||||
ci.cluster = ci.name;
|
||||
ci.constr_abs_z = false;
|
||||
make_cluster(ci, &ci, 0, 0);
|
||||
++packed[i + 1];
|
||||
}
|
||||
}
|
||||
log_info("Packed MUX2_LUT8:%d, MUX2_LU7:%d, MUX2_LUT6:%d, MUX2_LUT5:%d\n", packed[0], packed[1], packed[2],
|
||||
packed[3]);
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// ALU
|
||||
// ===================================
|
||||
// create ALU CIN block
|
||||
std::unique_ptr<CellInfo> GowinPacker::alu_add_cin_block(Context *ctx, CellInfo *head, NetInfo *cin_net,
|
||||
bool cin_is_vcc, bool cin_is_gnd)
|
||||
{
|
||||
std::string name = head->name.str(ctx) + "_HEAD_ALULC";
|
||||
IdString name_id = ctx->id(name);
|
||||
|
||||
NetInfo *cout_net = ctx->createNet(name_id);
|
||||
head->disconnectPort(id_CIN);
|
||||
head->connectPort(id_CIN, cout_net);
|
||||
|
||||
auto cin_ci = std::make_unique<CellInfo>(ctx, name_id, id_ALU);
|
||||
cin_ci->addOutput(id_COUT);
|
||||
cin_ci->connectPort(id_COUT, cout_net);
|
||||
|
||||
if (cin_is_gnd) {
|
||||
cin_ci->setParam(id_ALU_MODE, std::string("C2L"));
|
||||
cin_ci->addInput(id_I2);
|
||||
cin_ci->connectPort(id_I2, ctx->nets.at(ctx->id("$PACKER_VCC")).get());
|
||||
return cin_ci;
|
||||
}
|
||||
if (cin_is_vcc) {
|
||||
cin_ci->setParam(id_ALU_MODE, std::string("ONE2C"));
|
||||
cin_ci->addInput(id_I2);
|
||||
cin_ci->connectPort(id_I2, ctx->nets.at(ctx->id("$PACKER_VCC")).get());
|
||||
return cin_ci;
|
||||
}
|
||||
// CIN from logic
|
||||
cin_ci->addInput(id_I2);
|
||||
cin_ci->connectPort(id_I2, ctx->nets.at(ctx->id("$PACKER_VCC")).get());
|
||||
cin_ci->addInput(id_I0);
|
||||
cin_ci->connectPort(id_I0, cin_net);
|
||||
cin_ci->setParam(id_RAW_ALU_LUT, 0x505a); // 0101_0000_0101_1010 -> ignore I1 and I3, out carry = I0
|
||||
cin_ci->setParam(id_CIN_NETTYPE, Property("LOGIC"));
|
||||
return cin_ci;
|
||||
}
|
||||
|
||||
// create ALU COUT block
|
||||
std::unique_ptr<CellInfo> GowinPacker::alu_add_cout_block(Context *ctx, CellInfo *tail, NetInfo *cout_net)
|
||||
{
|
||||
std::string name = tail->name.str(ctx) + "_TAIL_ALULC";
|
||||
IdString name_id = ctx->id(name);
|
||||
|
||||
NetInfo *cin_net = ctx->createNet(name_id);
|
||||
tail->disconnectPort(id_COUT);
|
||||
tail->connectPort(id_COUT, cin_net);
|
||||
|
||||
auto cout_ci = std::make_unique<CellInfo>(ctx, name_id, id_ALU);
|
||||
cout_ci->addOutput(id_COUT); // may be needed for the ALU filler
|
||||
cout_ci->addInput(id_CIN);
|
||||
cout_ci->connectPort(id_CIN, cin_net);
|
||||
cout_ci->addOutput(id_SUM);
|
||||
cout_ci->connectPort(id_SUM, cout_net);
|
||||
cout_ci->addInput(id_I2);
|
||||
cout_ci->connectPort(id_I2, ctx->nets.at(ctx->id("$PACKER_VCC")).get());
|
||||
|
||||
cout_ci->setParam(id_ALU_MODE, std::string("C2L"));
|
||||
return cout_ci;
|
||||
}
|
||||
|
||||
// create ALU filler block
|
||||
std::unique_ptr<CellInfo> GowinPacker::alu_add_dummy_block(Context *ctx, CellInfo *tail)
|
||||
{
|
||||
std::string name = tail->name.str(ctx) + "_DUMMY_ALULC";
|
||||
IdString name_id = ctx->id(name);
|
||||
|
||||
auto dummy_ci = std::make_unique<CellInfo>(ctx, name_id, id_ALU);
|
||||
dummy_ci->setParam(id_ALU_MODE, std::string("C2L"));
|
||||
return dummy_ci;
|
||||
}
|
||||
|
||||
// optimize ALU wiring
|
||||
// A very simple ALU optimization: once we detect that one of the inputs is
|
||||
// a constant, we modify the main LUT that describes the ALU function so
|
||||
// that this primitive input is ignored, and then disconnect it from the
|
||||
// network, freeing up the PIP.
|
||||
// For example (unrealistic, since a real ALU LUT has a larger size and
|
||||
// service bits in the middle, etc.), the addition function of A and B when
|
||||
// A = 1 is converted from the general case (A isn't a constant and B isn't a
|
||||
// constant) to a special case:
|
||||
// 0110 -> 0011
|
||||
void GowinPacker::optimize_alu_lut(CellInfo *ci, int mode)
|
||||
{
|
||||
auto uni_shift = [&](unsigned int val, int amount) {
|
||||
if (amount < 0) {
|
||||
return val >> -amount;
|
||||
}
|
||||
return val << amount;
|
||||
};
|
||||
|
||||
IdString vcc_net_name = ctx->id("$PACKER_VCC");
|
||||
IdString gnd_net_name = ctx->id("$PACKER_GND");
|
||||
bool optimized = false;
|
||||
switch (mode) {
|
||||
case 2: {
|
||||
// ALU LUT for mode 2 is 0110_0000_1001_1010 for all chips
|
||||
// We will change this feature if the next
|
||||
// unreleased Gowin chip series changes this
|
||||
// representation.
|
||||
// If ADDSUB dynamically switches between + and -,
|
||||
// optimization is not possible.
|
||||
int possible_carry = 0b1100U;
|
||||
IdString inp_net_name = ci->getPort(id_I3)->name;
|
||||
if (inp_net_name != vcc_net_name && inp_net_name != gnd_net_name) {
|
||||
break;
|
||||
}
|
||||
if (inp_net_name == gnd_net_name) {
|
||||
possible_carry = 0b0011U;
|
||||
}
|
||||
unsigned int alu_lut = 0b0110000010011010U;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (i == 2) {
|
||||
break;
|
||||
}
|
||||
IdString inp_name = ctx->idf("I%d", i);
|
||||
inp_net_name = ci->getPort(inp_name)->name;
|
||||
if (inp_net_name == vcc_net_name || inp_net_name == gnd_net_name) {
|
||||
ci->disconnectPort(inp_name);
|
||||
optimized = true;
|
||||
|
||||
// fix the carry
|
||||
if (i == 0) {
|
||||
if (inp_net_name == vcc_net_name) {
|
||||
alu_lut |= 0xfU;
|
||||
} else {
|
||||
alu_lut &= ~0xfU;
|
||||
alu_lut |= possible_carry;
|
||||
}
|
||||
}
|
||||
|
||||
// We rearrange bits to account for constant networks
|
||||
int bit_n = 4;
|
||||
int copy_dist = 1 << i;
|
||||
if (inp_net_name == vcc_net_name) {
|
||||
bit_n += copy_dist;
|
||||
copy_dist = -copy_dist;
|
||||
}
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
alu_lut &= ~(1 << (bit_n + copy_dist));
|
||||
alu_lut |= uni_shift(alu_lut & (1 << bit_n), copy_dist);
|
||||
switch (i) {
|
||||
case 0: // skip the service bits
|
||||
bit_n += j == 1 ? 5 : 1;
|
||||
break;
|
||||
case 1: // skip the service bits
|
||||
bit_n += j == 1 ? 6 : 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
++bit_n;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (optimized) {
|
||||
ci->setParam(id_RAW_ALU_LUT, alu_lut);
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// create ALU chain
|
||||
void GowinPacker::pack_alus(void)
|
||||
{
|
||||
const CellTypePort cell_alu_cout = CellTypePort(id_ALU, id_COUT);
|
||||
const CellTypePort cell_alu_cin = CellTypePort(id_ALU, id_CIN);
|
||||
std::vector<std::unique_ptr<CellInfo>> new_cells;
|
||||
|
||||
log_info("Pack ALUs...\n");
|
||||
for (auto &cell : ctx->cells) {
|
||||
auto ci = cell.second.get();
|
||||
if (ci->cluster != ClusterId()) {
|
||||
continue;
|
||||
}
|
||||
if (is_alu(ci)) {
|
||||
// The ALU head is when the input carry is not a dedicated wire from the previous ALU
|
||||
NetInfo *cin_net = ci->getPort(id_CIN);
|
||||
if (!cin_net || !cin_net->driver.cell) {
|
||||
log_error("CIN disconnected at ALU:%s\n", ctx->nameOf(ci));
|
||||
}
|
||||
if (CellTypePort(cin_net->driver) != cell_alu_cout || cin_net->users.entries() > 1) {
|
||||
if (ctx->debug) {
|
||||
log_info("ALU head found %s. CIN net is %s\n", ctx->nameOf(ci), ctx->nameOf(cin_net));
|
||||
}
|
||||
|
||||
bool cin_is_vcc = cin_net->name == ctx->id("$PACKER_VCC");
|
||||
bool cin_is_gnd = cin_net->name == ctx->id("$PACKER_GND");
|
||||
bool cin_is_logic = !cin_is_vcc && !cin_is_gnd;
|
||||
CellInfo *cin_block_ci;
|
||||
int alu_chain_len;
|
||||
|
||||
// According to the documentation, GW5A can use CIN from
|
||||
// logic using the input MUX, but in practice this has not
|
||||
// yet been achieved. We are leaving the old mechanism in
|
||||
// place for this case.
|
||||
if ((!gwu.has_CIN_MUX()) || cin_is_logic) {
|
||||
// prepend first ALU with carry generator block
|
||||
// three cases: CIN == 0, CIN == 1 and CIN == ?
|
||||
new_cells.push_back(alu_add_cin_block(ctx, ci, cin_net, cin_is_vcc, cin_is_gnd));
|
||||
cin_block_ci = new_cells.back().get();
|
||||
// CIN block is the cluster root and is always placed in ALU0
|
||||
alu_chain_len = 1;
|
||||
} else {
|
||||
cin_block_ci = ci;
|
||||
ci->disconnectPort(id_CIN);
|
||||
if (cin_is_vcc) {
|
||||
ci->setParam(id_CIN_NETTYPE, Property("VCC"));
|
||||
} else {
|
||||
ci->setParam(id_CIN_NETTYPE, Property("GND"));
|
||||
}
|
||||
alu_chain_len = 0;
|
||||
}
|
||||
cin_block_ci->cluster = cin_block_ci->name;
|
||||
cin_block_ci->constr_z = BelZ::ALU0_Z;
|
||||
cin_block_ci->constr_abs_z = true;
|
||||
|
||||
while (true) {
|
||||
if (ci != cin_block_ci) {
|
||||
// add to cluster
|
||||
if (ctx->debug) {
|
||||
log_info("Add ALU to the chain (len:%d): %s\n", alu_chain_len, ctx->nameOf(ci));
|
||||
}
|
||||
cin_block_ci->constr_children.push_back(ci);
|
||||
NPNR_ASSERT(ci->cluster == ClusterId());
|
||||
ci->cluster = cin_block_ci->name;
|
||||
ci->constr_abs_z = false;
|
||||
ci->constr_x = alu_chain_len / 6;
|
||||
ci->constr_y = 0;
|
||||
ci->constr_z = alu_chain_len % 6;
|
||||
}
|
||||
// optimize only MODE=2 for now
|
||||
if (ci->params.at(id_ALU_MODE).as_int64() == 2) {
|
||||
optimize_alu_lut(ci, 2);
|
||||
}
|
||||
// XXX I2 is pin C which must be set to 1 for all ALU modes except MUL
|
||||
// we use only mode 2 ADDSUB so create and connect this pin
|
||||
ci->addInput(id_I2);
|
||||
ci->connectPort(id_I2, ctx->nets.at(ctx->id("$PACKER_VCC")).get());
|
||||
|
||||
++alu_chain_len;
|
||||
|
||||
// check for the chain end
|
||||
NetInfo *cout_net = ci->getPort(id_COUT);
|
||||
if (!cout_net || cout_net->users.empty()) {
|
||||
break;
|
||||
}
|
||||
if (CellTypePort(*cout_net->users.begin()) != cell_alu_cin || cout_net->users.entries() > 1) {
|
||||
new_cells.push_back(alu_add_cout_block(ctx, ci, cout_net));
|
||||
CellInfo *cout_block_ci = new_cells.back().get();
|
||||
cin_block_ci->constr_children.push_back(cout_block_ci);
|
||||
NPNR_ASSERT(cout_block_ci->cluster == ClusterId());
|
||||
cout_block_ci->cluster = cin_block_ci->name;
|
||||
cout_block_ci->constr_abs_z = false;
|
||||
cout_block_ci->constr_x = alu_chain_len / 6;
|
||||
cout_block_ci->constr_y = 0;
|
||||
cout_block_ci->constr_z = alu_chain_len % 6;
|
||||
if (ctx->debug) {
|
||||
log_info("Add ALU carry out to the chain (len:%d): %s COUT-net: %s\n", alu_chain_len,
|
||||
ctx->nameOf(cout_block_ci), ctx->nameOf(cout_net));
|
||||
}
|
||||
|
||||
++alu_chain_len;
|
||||
|
||||
break;
|
||||
}
|
||||
ci = (*cout_net->users.begin()).cell;
|
||||
}
|
||||
// ALUs are always paired
|
||||
if (alu_chain_len & 1) {
|
||||
// create dummy cell
|
||||
new_cells.push_back(alu_add_dummy_block(ctx, ci));
|
||||
CellInfo *dummy_block_ci = new_cells.back().get();
|
||||
cin_block_ci->constr_children.push_back(dummy_block_ci);
|
||||
NPNR_ASSERT(dummy_block_ci->cluster == ClusterId());
|
||||
dummy_block_ci->cluster = cin_block_ci->name;
|
||||
dummy_block_ci->constr_abs_z = false;
|
||||
dummy_block_ci->constr_x = alu_chain_len / 6;
|
||||
dummy_block_ci->constr_y = 0;
|
||||
dummy_block_ci->constr_z = alu_chain_len % 6;
|
||||
if (ctx->debug) {
|
||||
log_info("Add ALU dummy cell to the chain (len:%d): %s\n", alu_chain_len,
|
||||
ctx->nameOf(dummy_block_ci));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto &ncell : new_cells) {
|
||||
ctx->cells[ncell->name] = std::move(ncell);
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// glue LUT and FF
|
||||
// ===================================
|
||||
void GowinPacker::constrain_lutffs(void)
|
||||
{
|
||||
// Constrain directly connected LUTs and FFs together to use dedicated resources
|
||||
const pool<CellTypePort> lut_outs{{id_LUT1, id_F}, {id_LUT2, id_F}, {id_LUT3, id_F}, {id_LUT4, id_F}};
|
||||
const pool<CellTypePort> dff_ins{{id_DFF, id_D}, {id_DFFE, id_D}, {id_DFFN, id_D}, {id_DFFNE, id_D},
|
||||
{id_DFFS, id_D}, {id_DFFSE, id_D}, {id_DFFNS, id_D}, {id_DFFNSE, id_D},
|
||||
{id_DFFR, id_D}, {id_DFFRE, id_D}, {id_DFFNR, id_D}, {id_DFFNRE, id_D},
|
||||
{id_DFFP, id_D}, {id_DFFPE, id_D}, {id_DFFNP, id_D}, {id_DFFNPE, id_D},
|
||||
{id_DFFC, id_D}, {id_DFFCE, id_D}, {id_DFFNC, id_D}, {id_DFFNCE, id_D}};
|
||||
|
||||
int lutffs = h.constrain_cell_pairs(lut_outs, dff_ins, 1, 1);
|
||||
log_info("Constrained %d LUTFF pairs.\n", lutffs);
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// SSRAM cluster
|
||||
// ===================================
|
||||
std::unique_ptr<CellInfo> GowinPacker::ssram_make_lut(Context *ctx, CellInfo *ci, int index)
|
||||
{
|
||||
IdString name_id = ctx->idf("%s_LUT%d", ci->name.c_str(ctx), index);
|
||||
auto lut_ci = std::make_unique<CellInfo>(ctx, name_id, id_LUT4);
|
||||
if (index) {
|
||||
for (IdString port : {id_I0, id_I1, id_I2, id_I3}) {
|
||||
lut_ci->addInput(port);
|
||||
}
|
||||
}
|
||||
IdString init_name = ctx->idf("INIT_%d", index);
|
||||
if (ci->params.count(init_name)) {
|
||||
lut_ci->setParam(id_INIT, ci->params.at(init_name));
|
||||
} else {
|
||||
lut_ci->setParam(id_INIT, std::string("1111111111111111"));
|
||||
}
|
||||
return lut_ci;
|
||||
}
|
||||
|
||||
void GowinPacker::pack_ssram(void)
|
||||
{
|
||||
std::vector<std::unique_ptr<CellInfo>> new_cells;
|
||||
std::vector<IdString> cells_to_remove;
|
||||
|
||||
log_info("Pack SSRAMs...\n");
|
||||
for (auto &cell : ctx->cells) {
|
||||
auto ci = cell.second.get();
|
||||
if (ci->cluster != ClusterId()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_ssram(ci)) {
|
||||
if (ci->type == id_ROM16) {
|
||||
new_cells.push_back(ssram_make_lut(ctx, ci, 0));
|
||||
CellInfo *lut_ci = new_cells.back().get();
|
||||
// inputs
|
||||
ci->movePortBusTo(id_AD, 0, true, lut_ci, id_I, 0, false, 4);
|
||||
// output
|
||||
ci->movePortTo(id_DO, lut_ci, id_F);
|
||||
|
||||
cells_to_remove.push_back(ci->name);
|
||||
continue;
|
||||
}
|
||||
// make cluster root
|
||||
ci->cluster = ci->name;
|
||||
ci->constr_abs_z = true;
|
||||
ci->constr_x = 0;
|
||||
ci->constr_y = 0;
|
||||
ci->constr_z = BelZ::RAMW_Z;
|
||||
|
||||
ci->addInput(id_CE);
|
||||
ci->connectPort(id_CE, ctx->nets.at(ctx->id("$PACKER_VCC")).get());
|
||||
|
||||
// RAD networks
|
||||
NetInfo *rad[4];
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
rad[i] = ci->getPort(ctx->idf("RAD[%d]", i));
|
||||
}
|
||||
|
||||
// active LUTs
|
||||
int luts_num = 4;
|
||||
if (ci->type == id_RAM16SDP1) {
|
||||
luts_num = 1;
|
||||
} else {
|
||||
if (ci->type == id_RAM16SDP2) {
|
||||
luts_num = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// make actual storage cells
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
new_cells.push_back(ssram_make_lut(ctx, ci, i));
|
||||
CellInfo *lut_ci = new_cells.back().get();
|
||||
ci->constr_children.push_back(lut_ci);
|
||||
lut_ci->cluster = ci->name;
|
||||
lut_ci->constr_abs_z = true;
|
||||
lut_ci->constr_x = 0;
|
||||
lut_ci->constr_y = 0;
|
||||
lut_ci->constr_z = i * 2;
|
||||
// inputs
|
||||
// LUT0 is already connected when generating the base
|
||||
if (i && i < luts_num) {
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
lut_ci->connectPort(ctx->idf("I%d", j), rad[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto &ncell : new_cells) {
|
||||
ctx->cells[ncell->name] = std::move(ncell);
|
||||
}
|
||||
for (auto cell : cells_to_remove) {
|
||||
ctx->cells.erase(cell);
|
||||
}
|
||||
}
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
Loading…
Reference in New Issue