Skip to content

Commit

Permalink
implement MMCME2_ADV for xc7
Browse files Browse the repository at this point in the history
  • Loading branch information
hansfbaier committed Dec 15, 2023
1 parent f793875 commit bf25ddc
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 21 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ on [arXiv](https://arxiv.org/abs/1903.10407).
- Currently supported:
- xc7 and xcup: LUTs (including fractured), FFs, DRAM (only RAM64X1D), carry (XORCY and MUXCY or CARRY4), SRL16E and SRLC32E (no cascading), BRAM and IO
- xcup: OSERDESE3, ISERDESE3, IDDRE1, ODDRE1, IDELAYE3, ODELAYE3, IDELAYCTRL, BUFGCTRL, BUFG, BUFGCE, BUFG_PS, PLLE4_ADV, PLLE4_BASIC, MMCME4_ADV, MMCME4_BASIC, URAM288E, DSP48E2 (no cascading)
- xc7: OSERDESE2, ISERDESE2, IDDR, ODDR, IDELAYE2, ODELAYE2, IDELAYCTRL, BUFGCTRL, BUFG, PLLE2_BASIC, PLLE2_ADV, DSP48E1 (cascading works)
- xc7: OSERDESE2, ISERDESE2, IDDR, ODDR, IDELAYE2, ODELAYE2, IDELAYCTRL, BUFGCTRL, BUFG, PLLE2_BASE, PLLE2_ADV, MMCME2_BASE, MMCME2_ADV, DSP48E1 (cascading works)

- Bels, tile wires and pips are deduplicated but nodes (connections between tile wires) are not. This means
that databases for larger devices will be several gigabytes in size (but significantly smaller than a fully flat database).
24 changes: 12 additions & 12 deletions xilinx/arch.cc
Original file line number Diff line number Diff line change
Expand Up @@ -350,10 +350,6 @@ void Arch::setup_pip_blacklist()
if (dest_name.find("FREQ_REF") != std::string::npos)
blacklist_pips[td.type].insert(j);
}
} else if (boost::starts_with(type, "CMT_TOP_L_LOWER")) {
for (int j = 0; j < td.num_pips; j++) {
blacklist_pips[td.type].insert(j);
}
} else if (boost::starts_with(type, "CLK_HROW_TOP")) {
for (int j = 0; j < td.num_pips; j++) {
auto &pd = td.pip_data[j];
Expand Down Expand Up @@ -750,19 +746,24 @@ void Arch::routeClock()
NetInfo *clk_net = net.second.get();
if (clk_net->driver.cell == nullptr)
continue;
auto clk_driver = clk_net->driver.cell;
auto cell_type = clk_driver->type;
auto no_users = clk_net->users.entries();
auto clk_net_user = no_users == 1 ? (*clk_net->users.begin()).cell : nullptr;
auto clk_net_user_type = clk_net_user == nullptr ? IdString() : clk_net_user->type;
auto from_pll_or_mmcm = cell_type == id_PLLE2_ADV_PLLE2_ADV || cell_type == id_MMCME2_ADV_MMCME2_ADV;
auto to_pll_or_mmcm = clk_net_user_type == id_PLLE2_ADV_PLLE2_ADV || (clk_net_user_type == id_MMCME2_ADV_MMCME2_ADV);

// check if we have a global clock net, skip otherwise
bool is_global = false;
if ((clk_net->driver.cell->type.in(id_BUFGCTRL, id_BUFCE_BUFG_PS, id_BUFCE_BUFCE, id_BUFGCE_DIV_BUFGCE_DIV)) &&
clk_net->driver.port == id_O)
is_global = true;
else if (clk_net->driver.cell->type == id_PLLE2_ADV_PLLE2_ADV && clk_net->users.entries() == 1 &&
((*clk_net->users.begin()).cell->type == id_BUFGCTRL ||
(*clk_net->users.begin()).cell->type == id_BUFCE_BUFCE ||
(*clk_net->users.begin()).cell->type == id_BUFGCE_DIV_BUFGCE_DIV))
else if (no_users == 1 && from_pll_or_mmcm &&
(clk_net_user_type == id_BUFGCTRL || clk_net_user_type == id_BUFCE_BUFCE ||
clk_net_user_type == id_BUFGCE_DIV_BUFGCE_DIV))
is_global = true;
else if (clk_net->users.entries() == 1 && (*clk_net->users.begin()).cell->type == id_PLLE2_ADV_PLLE2_ADV &&
(*clk_net->users.begin()).port == id_CLKIN1)
else if (to_pll_or_mmcm && (*clk_net->users.begin()).port == id_CLKIN1)
is_global = true;
if (!is_global)
continue;
Expand Down Expand Up @@ -814,8 +815,7 @@ void Arch::routeClock()
}
if (dest == WireId()) {
log_info(" failed to find a route using dedicated resources.\n");
if (clk_net->users.entries() == 1 && (*clk_net->users.begin()).cell->type == id_PLLE2_ADV_PLLE2_ADV &&
(*clk_net->users.begin()).port == id_CLKIN1) {
if (to_pll_or_mmcm && (*clk_net->users.begin()).port == id_CLKIN1) {
// Due to some missing pips, currently special case more lenient solution
std::queue<WireId> empty;
std::swap(visit, empty);
Expand Down
2 changes: 2 additions & 0 deletions xilinx/constids.inc
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,8 @@ X(IS_IGNORE1_INVERTED)
X(IS_OCLK_INVERTED)
X(IS_ODATAIN_INVERTED)
X(IS_PRE_INVERTED)
X(IS_PSEN_INVERTED)
X(IS_PSINCDEC_INVERTED)
X(IS_PWRDWN_INVERTED)
X(IS_RST_INVERTED)
X(IS_R_INVERTED)
Expand Down
120 changes: 120 additions & 0 deletions xilinx/fasm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1197,6 +1197,8 @@ struct FasmBackend
pop(2);
} else if (ci->type == id_PLLE2_ADV_PLLE2_ADV) {
write_pll(ci);
} else if (ci->type == id_MMCME2_ADV_MMCME2_ADV) {
write_mmcm(ci);
}
blank();
}
Expand Down Expand Up @@ -1484,6 +1486,124 @@ struct FasmBackend
pop(2);
}

void write_mmcm_clkout(const std::string &name, CellInfo *ci)
{
// FIXME: variable duty cycle
int high = 1, low = 1, phasemux = 0, delaytime = 0, frac = 0;
bool no_count = false, edge = false;
double divide = float_or_default(ci, name + ((name == "CLKFBOUT") ? "_MULT_F" :
(name == "CLKOUT0" ? "_DIVIDE_F" : "_DIVIDE")), 1);
double phase = float_or_default(ci, name + "_PHASE", 1);
if (divide <= 1) {
no_count = true;
} else {
high = floor(divide / 2);
low = int(floor(divide) - high);
if (high != low)
edge = true;
if (name == "CLKOUT0" || name == "CLKFBOUT")
frac = floor(divide * 8) - floor(divide) * 8;
int phase_eights = floor((phase / 360) * divide * 8);
phasemux = phase_eights % 8;
delaytime = phase_eights / 8;
}
bool used = false;
if (name == "DIVCLK" || name == "CLKFBOUT") {
used = true;
} else {
used = ci->getPort(ctx->id(name)) != nullptr;
}
if (name == "DIVCLK") {
write_int_vector("DIVCLK_DIVCLK_HIGH_TIME[5:0]", high, 6);
write_int_vector("DIVCLK_DIVCLK_LOW_TIME[5:0]", low, 6);
write_bit("DIVCLK_DIVCLK_EDGE[0]", edge);
write_bit("DIVCLK_DIVCLK_NO_COUNT[0]", no_count);
} else if (used) {
auto is_clkout_5_or_6 = name == "CLKOUT5" || name == "CLKOUT6";
auto is_clkout0 = name == "CLKOUT0";
auto is_clkfbout = name == "CLKFBOUT";

if ((is_clkout0 || is_clkfbout) && frac != 0) {
--high;
--low;

auto frac_shifted = frac >> 1;
// CLKOUT0 controls CLKOUT5_CLKOUT2, CLKFBOUT controls CLKOUT6_CLKOUT2
std::string frac_conf_name = is_clkout0 ? "CLKOUT5_CLKOUT2_" : "CLKOUT6_CLKOUT2_";

if (1 <= frac_shifted) {
write_bit(frac_conf_name + "FRACTIONAL_FRAC_WF_F[0]");
write_int_vector(frac_conf_name + "FRACTIONAL_PHASE_MUX_F[1:0]", frac_shifted, 2);
}
}

write_bit(name + "_CLKOUT1_OUTPUT_ENABLE[0]");
write_int_vector(name + "_CLKOUT1_HIGH_TIME[5:0]", high, 6);
write_int_vector(name + "_CLKOUT1_LOW_TIME[5:0]", low, 6);

auto phase_mux_feature = name + (is_clkout_5_or_6 ? "_CLKOUT2_FRACTIONAL_PHASE_MUX_F[0]" : "_CLKOUT2_PHASE_MUX[0]");
write_int_vector(name + "_CLKOUT1_PHASE_MUX[2:0]", phasemux, 3);

auto edge_feature = name + (is_clkout_5_or_6 ? "_CLKOUT2_FRACTIONAL_EDGE[0]" : "_CLKOUT2_EDGE[0]");
write_bit(edge_feature, edge);

auto no_count_feature = name + (is_clkout_5_or_6 ? "_CLKOUT2_FRACTIONAL_NO_COUNT[0]" : "_CLKOUT2_NO_COUNT[0]");
write_bit(no_count_feature, no_count);

auto delay_time_feature = name + (is_clkout_5_or_6 ? "_CLKOUT2_FRACTIONAL_DELAY_TIME[5:0]" : "_CLKOUT2_DELAY_TIME[5:0]");
write_int_vector(delay_time_feature, delaytime, 6);

if (!is_clkout_5_or_6 && frac != 0) {
write_bit(name + "_CLKOUT2_FRAC_EN[0]", 1);
write_bit(name + "_CLKOUT2_FRAC_WF_R[0]", 1);
write_int_vector(name + "_CLKOUT2_FRAC[2:0]", frac, 3);
}
}
}

void write_mmcm(CellInfo *ci)
{
push(get_tile_name(ci->bel.tile));
push("MMCME2_ADV");
write_bit("IN_USE");
// FIXME: should be INV not ZINV (XRay error?)
write_bit("ZINV_PWRDWN", bool_or_default(ci->params, id_IS_PWRDWN_INVERTED, false));
write_bit("ZINV_RST", bool_or_default(ci->params, id_IS_RST_INVERTED, false));
write_bit("ZINV_PSEN", bool_or_default(ci->params, id_IS_PSEN_INVERTED, false));
write_bit("ZINV_PSINCDEC", bool_or_default(ci->params, id_IS_PSINCDEC_INVERTED, false));
write_bit("INV_CLKINSEL", bool_or_default(ci->params, id_IS_CLKINSEL_INVERTED, false));
write_mmcm_clkout("DIVCLK", ci);
write_mmcm_clkout("CLKFBOUT", ci);
write_mmcm_clkout("CLKOUT0", ci);
write_mmcm_clkout("CLKOUT1", ci);
write_mmcm_clkout("CLKOUT2", ci);
write_mmcm_clkout("CLKOUT3", ci);
write_mmcm_clkout("CLKOUT4", ci);
write_mmcm_clkout("CLKOUT5", ci);
write_mmcm_clkout("CLKOUT6", ci);

std::string comp = str_or_default(ci->params, id_COMPENSATION, "INTERNAL");
push("COMP");
if (comp == "INTERNAL" || comp == "ZHOLD") {
// does not seem to make a difference in vivado
// both modes set this bit
write_bit("Z_ZHOLD");
} else {
NPNR_ASSERT_FALSE("unsupported compensation type");
}
pop();

// FIXME: should these be calculated somehow?
write_int_vector("FILTREG1_RESERVED[11:0]", 0x8, 12);
write_int_vector("LKTABLE[39:0]", 0xe73e8fa401ULL, 40);
// 0x9900 enables fractional counters, we just leave them on all the time
// only int counters would be 0x1 << 8
write_int_vector("POWER_REG_POWER_REG_POWER_REG[15:0]", 0x9900, 16);
write_bit("LOCKREG3_RESERVED[0]");
write_int_vector("TABLE[9:0]", 0x3d4, 10);
pop(2);
}

void write_dsp_cell(CellInfo *ci)
{
auto tile_name = get_tile_name(ci->bel.tile);
Expand Down
4 changes: 2 additions & 2 deletions xilinx/pack_clocking_xc7.cc
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ void XC7Packer::pack_plls()
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
// Preplace PLLs to make use of dedicated/short routing paths
if (ci->type.in(id_MMCM_MMCM_TOP, id_PLL_PLL_TOP))
if (ci->type == id_MMCME2_ADV_MMCME2_ADV || ci->type == id_PLLE2_ADV_PLLE2_ADV)
try_preplace(ci, id_CLKIN1);
if (ci->type == id_MMCM_MMCM_TOP) {
if (ci->type == id_MMCME2_ADV_MMCME2_ADV) {
// Fixup parameters
for (int i = 1; i <= 2; i++)
set_default(ci, ctx->id("CLKIN" + std::to_string(i) + "_PERIOD"), Property("0.0"));
Expand Down
30 changes: 24 additions & 6 deletions xilinx/pins.cc
Original file line number Diff line number Diff line change
Expand Up @@ -500,12 +500,30 @@ void get_tied_pins(Context *ctx, dict<IdString, dict<IdString, bool>> &tied_pins
for (int i = 0; i < 4; i++)
tied_pins[id_IOBUFDSE3][ctx->id("OSC[" + std::to_string(i) + "]")] = false;

// MMCM
tied_pins[id_MMCME4_ADV][id_CDDCREQ] = false;
tied_pins[id_MMCME4_ADV][id_CLKFBIN] = false;
tied_pins[id_MMCME4_ADV][id_CLKIN1] = false;
tied_pins[id_MMCME4_ADV][id_CLKIN2] = false;
tied_pins[id_MMCME4_ADV][id_CLKINSEL] = true;
// MMCME2
tied_pins[ctx->id("MMCME2_ADV")][ctx->id("CLKFBIN")] = false;
tied_pins[ctx->id("MMCME2_ADV")][ctx->id("CLKIN1")] = false;
tied_pins[ctx->id("MMCME2_ADV")][ctx->id("CLKIN2")] = false;
tied_pins[ctx->id("MMCME2_ADV")][ctx->id("CLKINSEL")] = true;
for (int i = 0; i < 7; i++)
tied_pins[ctx->id("MMCME2_ADV")][ctx->id("DADDR[" + std::to_string(i) + "]")] = false;
tied_pins[ctx->id("MMCME2_ADV")][ctx->id("DCLK")] = false;
tied_pins[ctx->id("MMCME2_ADV")][ctx->id("DEN")] = false;
for (int i = 0; i < 16; i++)
tied_pins[ctx->id("MMCME2_ADV")][ctx->id("DI[" + std::to_string(i) + "]")] = false;
tied_pins[ctx->id("MMCME2_ADV")][ctx->id("DWE")] = false;
tied_pins[ctx->id("MMCME2_ADV")][ctx->id("PSCLK")] = false;
tied_pins[ctx->id("MMCME2_ADV")][ctx->id("PSEN")] = false;
tied_pins[ctx->id("MMCME2_ADV")][ctx->id("PSINCDEC")] = false;
tied_pins[ctx->id("MMCME2_ADV")][ctx->id("PWRDWN")] = false;
tied_pins[ctx->id("MMCME2_ADV")][ctx->id("RST")] = false;

// MMCME4
tied_pins[ctx->id("MMCME4_ADV")][ctx->id("CDDCREQ")] = false;
tied_pins[ctx->id("MMCME4_ADV")][ctx->id("CLKFBIN")] = false;
tied_pins[ctx->id("MMCME4_ADV")][ctx->id("CLKIN1")] = false;
tied_pins[ctx->id("MMCME4_ADV")][ctx->id("CLKIN2")] = false;
tied_pins[ctx->id("MMCME4_ADV")][ctx->id("CLKINSEL")] = true;
for (int i = 0; i < 7; i++)
tied_pins[id_MMCME4_ADV][ctx->id("DADDR[" + std::to_string(i) + "]")] = false;
tied_pins[id_MMCME4_ADV][id_DCLK] = false;
Expand Down

0 comments on commit bf25ddc

Please sign in to comment.