Gatemate FPGA initial support (#1473)

* Initial code for GateMate

* Initial work on forming bitstream

* Add CCF parsing

* Use CCF to set IO location

* Propagate errors

* Restructure code

* Add support for reading from config

* Start adding infrastructure for reading bitstream

* Fix script

* GPIO initial work

* Add IN1->RAM_O2 propagation

* Fixed typo

* Cleanup

* More parameter checks

* Add LVDS support

* Cleanup

* Keep just used connections for now

* Naive lut tree CPE pack

* Naive pack CC_DFF

* pack DFF fixes

* Handle MUX flags

* Fix DFF pack

* Prevent pass trough issues

* Cleanup

* Use device wrapper class

* Update due to API changes

* Use pin  connection aliases

* Start work on BUFG support

* Fix CC_L2T5 pack

* Add CPE input inverters

* Constrain routes to have correct inversion state

* Add clock inversion pip

* Added MX2 and MX4 support

* Fix script

* BUFG support

* debug print if route found with wrong polarity

* Some CC_DFF improvements

* Create reproducible chip database

* Simplify inversion of special signals

* Few more DFF features

* Add forgotten virtual port renames

* Handle muxes with constant inputs

* Allow inversion for muxes

* cleanup

* DFF input can be constant

* init DFF only when needed

* cleanup

* Add basic PLL support

* Add some timings

* Add USR_RSTN support

* Display few more primitives

* Use pass trough signals to validate architecture data

* Use extra tile information from chip database

* Updates needed for a build system changes

* Implement SB_DRIVE support

* Properly named configuration bits

* autogenerated constids.inc

* small fix

* Initial code for CPE halfs

* Some cleanup

* make sure FFs are compatible

* reverted due to db change

* Merge DFF where applicable

* memory allocation issue

* fix

* better MX2

* ram_i handling

* Cleanup MX4

* Support latches

* compare L_D flag as well

* Move virtual pips

* Naive addf pack

* carry chains grouping

* Keep chip database reproducible

* split addf vectors

* Block CPEs when GPIO is used

* Prepare placement code

* RAM_I/RAM_O rewrite

* fix ram_i/o index

* Display RAM and add new primitives

* PLL wip code

* CC_PLL_ADV packing

* PLL handling cleanup

* Add PLL comments

* Keep only high fan-out BUFG

* Add skeleton for tests

* Utilize move_ram_o

* GPIO wip

* GPIO wip

* PLL fixes

* cleanup

* FF_OBF support

* Handle FF_IBF

* Make SLEW FAST if not defined as in latest p_r

* Make sure FF_OBF only driving GPIO

* Moved pll calc into separate file

* IDDR handling and started ODDR

* Route DDR input for CC_ODDR

* Notify error in case ODDR or IDDR are used but not with I/O pin

* cleanup for CC_USR_RSTN

* Extract proper RAM location  for bitstream

* Code cleanup

* Allow auto place of pads

* Use clock source flag

* Configure GPIO clock signals

* Handle conflicting clk

* Use BUGF in proper order

* Connected CLK, works without but good for debugging

* CC_CFG_CTRL placement

* Group RAM data 40 bytes per row

* Write BRAM content

* RAM wip

* Use relative constraints from chipdb

* fix broken build

* Memory wip

* Handle custom clock for memories

* Support FIFO

* optimize move_ram_io

* Fix SR signal handling acorrding to findings

* set placer beta

* Pre place what we can

* Revert "debug print if route found with wrong polarity"

This reverts commit cf9ded2f18.

* Revert "Constrain routes to have correct inversion state"

This reverts commit 795c284d48.

* Remove virtual pips

* Implement post processing inversion

* ADDF add ability to route additional CO

* Merge two ADDFs in one CPE

* Added TODO

* clangformat

* Cleanup

* Add serdes handling in config file

* Cleanup

* Cleanup

* Cleanup

* Fix in PLL handling

* Fixed ADDF edge case

* No need for this

* Fix latch

* Sanity checks

* Support CC_BRAM_20K merge

* Start creating testing environment

* LVDS fixes

* Add connection helper

* Cleanup

* Fix tabs

* Formatting fix

* Remove optimization tests for now

* remove read_bitstream

* removed .c_str()

* Removed config parsing

* using snake_case

* Use bool_or_default where applicable

* refactored bitstream write code

* Add allow-unconstrained option

* Update DFF related messages

* Add clock constraint propagation

---------

Co-authored-by: Lofty <dan.ravensloft@gmail.com>
This commit is contained in:
Miodrag Milanović 2025-04-22 16:41:01 +02:00 committed by GitHub
parent 7a3a43e150
commit d6483adb4d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 6769 additions and 1 deletions

View File

@ -64,7 +64,7 @@ else()
endif() endif()
set(HIMBAECHEL_UARCHES example gowin xilinx ng-ultra) set(HIMBAECHEL_UARCHES example gowin xilinx ng-ultra gatemate)
set(HIMBAECHEL_UARCH "" CACHE STRING "Microarchitectures for nextpnr-himbaechel build") set(HIMBAECHEL_UARCH "" CACHE STRING "Microarchitectures for nextpnr-himbaechel build")
set_property(CACHE HIMBAECHEL_UARCH PROPERTY STRINGS ${HIMBAECHEL_UARCHES}) set_property(CACHE HIMBAECHEL_UARCH PROPERTY STRINGS ${HIMBAECHEL_UARCHES})

View File

@ -0,0 +1,67 @@
set(SOURCES
bitstream.cc
ccf.cc
cells.cc
config.cc
config.h
constids.inc
extra_data.h
gatemate.cc
gatemate.h
gfx.cc
gfxids.inc
pack.cc
pack.h
pll.cc
)
set(TEST_SOURCES
tests/lut.cc
tests/testing.cc
tests/main.cc
)
add_nextpnr_himbaechel_microarchitecture(${uarch}
CORE_SOURCES ${SOURCES}
TEST_SOURCES ${TEST_SOURCES}
)
set(HIMBAECHEL_PEPPERCORN_PATH "" CACHE STRING
"Path to a Project Peppercorn database")
if (NOT HIMBAECHEL_PEPPERCORN_PATH)
message(FATAL_ERROR "HIMBAECHEL_PEPPERCORN_PATH must be set to a Project Peppercorn checkout")
endif()
#set(ALL_HIMBAECHE_GATEMATE_DEVICES CCGM1A1 CCGM1A2 CCGM1A4 CCGM1A9 CCGM1A16 CCGM1A25)
set(ALL_HIMBAECHE_GATEMATE_DEVICES CCGM1A1)
set(HIMBAECHEL_GATEMATE_DEVICES ${ALL_HIMBAECHE_GATEMATE_DEVICES} CACHE STRING
"Include support for these GateMate devices (available: ${ALL_HIMBAECHE_GATEMATE_DEVICES})")
message(STATUS "Enabled Himbaechel-GateMate devices: ${HIMBAECHEL_GATEMATE_DEVICES}")
foreach (device ${HIMBAECHEL_GATEMATE_DEVICES})
if (NOT device IN_LIST ALL_HIMBAECHE_GATEMATE_DEVICES)
message(FATAL_ERROR "Device ${device} is not a supported GateMate device")
endif()
add_bba_produce_command(
TARGET nextpnr-himbaechel-gatemate-bba
COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/gen/arch_gen.py
--lib ${HIMBAECHEL_PEPPERCORN_PATH}/gatemate
--device ${device}
--bba ${CMAKE_CURRENT_BINARY_DIR}/chipdb-${device}.bba.new
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/chipdb-${device}.bba
INPUTS
${CMAKE_CURRENT_SOURCE_DIR}/gen/arch_gen.py
${CMAKE_CURRENT_SOURCE_DIR}/constids.inc
${CMAKE_CURRENT_SOURCE_DIR}/gfxids.inc
)
add_bba_compile_command(
TARGET nextpnr-himbaechel-gatemate-chipdb
OUTPUT himbaechel/gatemate/chipdb-${device}.bin
INPUT ${CMAKE_CURRENT_BINARY_DIR}/chipdb-${device}.bba
MODE binary
)
endforeach()

View File

@ -0,0 +1,252 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include <fstream>
#include <boost/algorithm/string.hpp>
#include "config.h"
#include "gatemate.h"
#include "gatemate_util.h"
#define HIMBAECHEL_CONSTIDS "uarch/gatemate/constids.inc"
#include "himbaechel_constids.h"
NEXTPNR_NAMESPACE_BEGIN
namespace {
struct BitstreamBackend
{
Context *ctx;
GateMateImpl *uarch;
const std::string &device;
std::ostream &out;
BitstreamBackend(Context *ctx, GateMateImpl *uarch, const std::string &device, std::ostream &out)
: ctx(ctx), uarch(uarch), device(device), out(out) {};
const GateMateTileExtraDataPOD *tile_extra_data(int tile) const
{
return reinterpret_cast<const GateMateTileExtraDataPOD *>(ctx->chip_info->tile_insts[tile].extra_data.get());
}
std::vector<bool> int_to_bitvector(int val, int size)
{
std::vector<bool> bv;
for (int i = 0; i < size; i++) {
bv.push_back((val & (1 << i)) != 0);
}
return bv;
}
CfgLoc get_config_loc(int tile)
{
auto ti = *tile_extra_data(tile);
CfgLoc loc;
loc.die = ti.die;
loc.x = ti.bit_x;
loc.y = ti.bit_y;
return loc;
}
CfgLoc get_ram_config_loc(int tile)
{
auto ti = *tile_extra_data(tile);
CfgLoc loc;
loc.die = ti.die;
loc.x = (ti.bit_x - 17) / 16;
loc.y = (ti.bit_y - 1) / 8;
return loc;
}
void export_connection(ChipConfig &cc, PipId pip)
{
const auto extra_data =
*reinterpret_cast<const GateMatePipExtraDataPOD *>(chip_pip_info(ctx->chip_info, pip).extra_data.get());
if (extra_data.type == PipExtra::PIP_EXTRA_MUX && (extra_data.flags & MUX_VISIBLE)) {
IdString name = IdString(extra_data.name);
CfgLoc loc = get_config_loc(pip.tile);
std::string word = name.c_str(ctx);
if (extra_data.flags & MUX_CONFIG) {
cc.configs[loc.die].add_word(word, int_to_bitvector(extra_data.value, extra_data.bits));
} else {
int id = tile_extra_data(pip.tile)->prim_id;
if (boost::starts_with(word, "IM."))
boost::replace_all(word, "IM.", stringf("IM%d.", id));
else if (boost::starts_with(word, "OM."))
boost::replace_all(word, "OM.", stringf("OM%d.", id));
else if (boost::starts_with(word, "CPE."))
boost::replace_all(word, "CPE.", stringf("CPE%d.", id));
else if (boost::starts_with(word, "IOES."))
boost::replace_all(word, "IOES.", stringf("IOES%d.", id));
else if (boost::starts_with(word, "LES."))
boost::replace_all(word, "LES.", stringf("LES%d.", id));
else if (boost::starts_with(word, "BES."))
boost::replace_all(word, "BES.", stringf("BES%d.", id));
else if (boost::starts_with(word, "RES."))
boost::replace_all(word, "RES.", stringf("RES%d.", id));
else if (boost::starts_with(word, "TES."))
boost::replace_all(word, "TES.", stringf("TES%d.", id));
if (boost::starts_with(word, "SB_DRIVE.")) {
Loc l;
tile_xy(ctx->chip_info, pip.tile, l.x, l.y);
l.z = 0;
BelId cpe_bel = ctx->getBelByLocation(l);
// Only if switchbox is inside core (same as sharing location with CPE)
if (cpe_bel != BelId() && ctx->getBelType(cpe_bel).in(id_CPE_HALF_L, id_CPE_HALF_U)) {
// Convert coordinates into in-tile coordinates
int xt = ((l.x - 2 - 1) + 16) % 8;
int yt = ((l.y - 2 - 1) + 16) % 8;
// Bitstream data for certain SB_DRIVES is located in other tiles
switch (word[14]) {
case '3':
if (xt >= 4) {
loc.x -= 2;
word[14] = '1';
};
break;
case '4':
if (yt >= 4) {
loc.y -= 2;
word[14] = '2';
};
break;
case '1':
if (xt <= 3) {
loc.x += 2;
word[14] = '3';
};
break;
case '2':
if (yt <= 3) {
loc.y += 2;
word[14] = '4';
};
break;
default:
break;
}
}
}
cc.tiles[loc].add_word(word, int_to_bitvector(extra_data.value, extra_data.bits));
}
}
}
void write_bitstream()
{
ChipConfig cc;
cc.chip_name = device;
cc.configs[0].add_word("GPIO.BANK_E1", int_to_bitvector(1, 1));
cc.configs[0].add_word("GPIO.BANK_E2", int_to_bitvector(1, 1));
cc.configs[0].add_word("GPIO.BANK_N1", int_to_bitvector(1, 1));
cc.configs[0].add_word("GPIO.BANK_N2", int_to_bitvector(1, 1));
cc.configs[0].add_word("GPIO.BANK_S1", int_to_bitvector(1, 1));
cc.configs[0].add_word("GPIO.BANK_S2", int_to_bitvector(1, 1));
cc.configs[0].add_word("GPIO.BANK_W1", int_to_bitvector(1, 1));
cc.configs[0].add_word("GPIO.BANK_W2", int_to_bitvector(1, 1));
for (auto &cell : ctx->cells) {
CfgLoc loc = get_config_loc(cell.second.get()->bel.tile);
auto &params = cell.second.get()->params;
switch (cell.second->type.index) {
case id_CC_IBUF.index:
case id_CC_TOBUF.index:
case id_CC_OBUF.index:
case id_CC_IOBUF.index:
case id_CC_LVDS_IBUF.index:
case id_CC_LVDS_TOBUF.index:
case id_CC_LVDS_OBUF.index:
case id_CC_LVDS_IOBUF.index:
for (auto &p : params) {
cc.tiles[loc].add_word(stringf("GPIO.%s", p.first.c_str(ctx)), p.second.as_bits());
}
break;
case id_CPE_HALF_U.index:
case id_CPE_HALF_L.index: {
int id = tile_extra_data(cell.second.get()->bel.tile)->prim_id;
for (auto &p : params) {
cc.tiles[loc].add_word(stringf("CPE%d.%s", id, p.first.c_str(ctx)), p.second.as_bits());
}
} break;
case id_BUFG.index: {
Loc l = ctx->getBelLocation(cell.second->bel);
cc.configs[0].add_word(stringf("GLBOUT.GLB%d_EN", l.z), int_to_bitvector(1, 1));
} break;
case id_PLL.index: {
Loc l = ctx->getBelLocation(cell.second->bel);
for (auto &p : params) {
cc.configs[0].add_word(stringf("PLL%d.%s", l.z - 4, p.first.c_str(ctx)), p.second.as_bits());
}
} break;
case id_RAM.index: {
CfgLoc loc = get_ram_config_loc(cell.second.get()->bel.tile);
auto &bram = cc.brams[loc];
for (auto &p : params) {
std::string name = p.first.c_str(ctx);
if (boost::starts_with(name, "RAM_cfg"))
bram.add_word(name, p.second.as_bits());
}
bool is_fifo = params.count(id_RAM_cfg_fifo_sync_enable) | params.count(id_RAM_cfg_fifo_async_enable);
if (!is_fifo) {
auto &bram_data = cc.bram_data[loc];
bram_data = std::vector<uint8_t>(5120);
for (int i = 0; i < 128; i++) {
for (int j = 0; j < 40; j++) {
bram_data[i * 40 + j] = extract_bits(params, ctx->idf("INIT_%02X", i), j * 8, 8);
}
}
}
} break;
case id_USR_RSTN.index:
case id_CFG_CTRL.index:
break;
default:
log_error("Unhandled cell %s of type %s\n", cell.second.get()->name.c_str(ctx),
cell.second->type.c_str(ctx));
}
}
for (auto &net : ctx->nets) {
NetInfo *ni = net.second.get();
if (ni->wires.empty())
continue;
for (auto &w : ni->wires) {
if (w.second.pip != PipId())
export_connection(cc, w.second.pip);
}
}
out << cc;
}
};
} // namespace
void GateMateImpl::write_bitstream(const std::string &device, const std::string &filename)
{
std::ofstream out(filename);
if (!out)
log_error("failed to open file %s for writing (%s)\n", filename.c_str(), strerror(errno));
BitstreamBackend be(ctx, this, device, out);
be.write_bitstream();
}
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,215 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include <algorithm>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <fstream>
#include <regex>
#include "extra_data.h"
#include "himbaechel_api.h"
#include "log.h"
#include "nextpnr.h"
#include "util.h"
#include "gatemate.h"
#define HIMBAECHEL_CONSTIDS "uarch/gatemate/constids.inc"
#include "himbaechel_constids.h"
NEXTPNR_NAMESPACE_BEGIN
struct GateMateCCFReader
{
Context *ctx;
GateMateImpl *uarch;
std::istream &in;
int lineno;
dict<IdString, Property> defaults;
GateMateCCFReader(Context *ctx, GateMateImpl *uarch, std::istream &in) : ctx(ctx), uarch(uarch), in(in){};
std::string strip_quotes(const std::string &str)
{
if (str.at(0) == '"') {
if (str.back() != '"') {
log_error("expected '\"' at end of string '%s' (on line %d).\n", str.c_str(), lineno);
}
return str.substr(1, str.size() - 2);
} else {
return str;
}
};
void parse_params(std::vector<std::string> &params, bool is_default, dict<IdString, Property> *props)
{
for (auto param : params) {
std::vector<std::string> expr;
boost::split(expr, param, boost::is_any_of("="));
if (expr.size() != 2)
log_error("each parameter must be in form NAME=VALUE (on line %d)\n", lineno);
std::string name = expr.at(0);
boost::algorithm::trim(name);
boost::algorithm::to_upper(name);
std::string value = strip_quotes(expr.at(1));
boost::algorithm::trim(value);
boost::algorithm::to_upper(value);
if (name == "LOC") {
if (is_default)
log_error("Value '%s' can not be defined for default GPIO in line %d.\n", name.c_str(), lineno);
if (ctx->get_package_pin_bel(ctx->id(value)) == BelId())
log_error("Unknown location '%s' used in line %d.\n", value.c_str(), lineno);
if (!uarch->available_pads.count(ctx->id(value)))
log_error("Pad '%s' used in line %d not available.\n", value.c_str(), lineno);
props->emplace(id_LOC, Property(value));
uarch->available_pads.erase(ctx->id(value));
} else if (name == "SCHMITT_TRIGGER" || name == "PULLUP" || name == "PULLDOWN" || name == "KEEPER" ||
name == "FF_IBF" || name == "FF_OBF" || name == "LVDS_BOOST" || name == "LVDS_RTERM") {
if (value == "TRUE") {
props->emplace(ctx->id(name), Property(Property::State::S1));
} else if (value == "FALSE") {
props->emplace(ctx->id(name), Property(Property::State::S0));
} else
log_error("Uknown value '%s' for parameter '%s' in line %d, must be TRUE or FALSE.\n",
value.c_str(), name.c_str(), lineno);
} else if (name == "SLEW") {
if (value == "FAST" || value == "SLOW") {
props->emplace(ctx->id(name), Property(value));
} else
log_error("Uknown value '%s' for parameter '%s' in line %d, must be SLOW or FAST.\n", value.c_str(),
name.c_str(), lineno);
} else if (name == "DRIVE") {
try {
int drive = boost::lexical_cast<int>(value.c_str());
if (drive == 3 || drive == 6 || drive == 9 || drive == 12) {
props->emplace(ctx->id(name), Property(drive, 2));
} else
log_error("Parameter '%s' must have value 3,6,9 or 12 in line %d.\n", name.c_str(), lineno);
} catch (boost::bad_lexical_cast const &) {
log_error("Parameter '%s' must be number in line %d.\n", name.c_str(), lineno);
}
} else if (name == "DELAY_IBF" || name == "DELAY_OBF") {
try {
int delay = boost::lexical_cast<int>(value.c_str());
if (delay >= 0 && delay <= 15) {
props->emplace(ctx->id(name), Property(delay, 4));
} else
log_error("Parameter '%s' must have value from 0 to 15 in line %d.\n", name.c_str(), lineno);
} catch (boost::bad_lexical_cast const &) {
log_error("Parameter '%s' must be number in line %d.\n", name.c_str(), lineno);
}
} else {
log_error("Uknown parameter name '%s' in line %d.\n", name.c_str(), lineno);
}
}
}
void run()
{
log_info("Parsing CCF file..\n");
std::string line;
std::string linebuf;
defaults.clear();
auto isempty = [](const std::string &str) {
return std::all_of(str.begin(), str.end(), [](char c) { return isblank(c) || c == '\r' || c == '\n'; });
};
lineno = 0;
while (std::getline(in, line)) {
++lineno;
// Both // and # are considered start of comment
size_t com_start = line.find("//");
if (com_start != std::string::npos)
line = line.substr(0, com_start);
com_start = line.find('#');
if (com_start != std::string::npos)
line = line.substr(0, com_start);
if (isempty(line))
continue;
linebuf += line;
size_t pos = linebuf.find(';');
// Need to concatenate lines until there is closing ; sign
while (pos != std::string::npos) {
std::string content = linebuf.substr(0, pos);
std::vector<std::string> params;
boost::split(params, content, boost::is_any_of("|"));
std::string command = params.at(0);
std::stringstream ss(command);
std::vector<std::string> words;
std::string tmp;
while (ss >> tmp)
words.push_back(tmp);
std::string type = words.at(0);
boost::algorithm::to_lower(type);
if (type == "default_gpio") {
if (words.size() != 1)
log_error("line with default_GPIO should not contain only parameters (in line %d).\n", lineno);
params.erase(params.begin());
parse_params(params, true, &defaults);
} else if (type == "net" || type == "pin_in" || type == "pin_out" || type == "pin_inout") {
if (words.size() < 3 || words.size() > 5)
log_error("pin definition line not properly formed (in line %d).\n", lineno);
std::string pin_name = strip_quotes(words.at(1));
// put back other words and use them as parameters
std::stringstream ss;
for (size_t i = 2; i < words.size(); i++)
ss << words.at(i);
params[0] = ss.str();
IdString cellname = ctx->id(pin_name);
if (ctx->cells.count(cellname)) {
CellInfo *cell = ctx->cells.at(cellname).get();
for (auto p : defaults)
cell->params[p.first] = p.second;
parse_params(params, false, &cell->params);
} else
log_warning("Pad with name '%s' not found in netlist.\n", pin_name.c_str());
} else {
log_error("unknown type '%s' in line %d.\n", type.c_str(), lineno);
}
linebuf = linebuf.substr(pos + 1);
pos = linebuf.find(';');
}
}
if (!isempty(linebuf))
log_error("unexpected end of CCF file\n");
}
};
void GateMateImpl::parse_ccf(const std::string &filename)
{
std::ifstream in(filename);
if (!in)
log_error("failed to open CCF file '%s'\n", filename.c_str());
GateMateCCFReader reader(ctx, this, in);
reader.run();
}
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,59 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "nextpnr.h"
#include "pack.h"
#define HIMBAECHEL_CONSTIDS "uarch/gatemate/constids.inc"
#include "himbaechel_constids.h"
NEXTPNR_NAMESPACE_BEGIN
CellInfo *GateMatePacker::create_cell_ptr(IdString type, IdString name)
{
CellInfo *cell = ctx->createCell(name, type);
auto add_port = [&](const IdString id, PortType dir) {
cell->ports[id].name = id;
cell->ports[id].type = dir;
};
if (type.in(id_CPE_HALF, id_CPE_HALF_U, id_CPE_HALF_L)) {
add_port(id_I1, PORT_IN);
add_port(id_I2, PORT_IN);
add_port(id_I3, PORT_IN);
add_port(id_I4, PORT_IN);
add_port(id_RAM_I, PORT_IN);
add_port(id_OUT, PORT_OUT);
add_port(id_RAM_O, PORT_OUT);
add_port(id_EN, PORT_IN);
add_port(id_CLK, PORT_IN);
add_port(id_SR, PORT_IN);
if (type == id_CPE_HALF_L) {
add_port(id_COUTY1, PORT_OUT);
}
} else if (type.in(id_CC_BUFG)) {
add_port(id_I, PORT_IN);
add_port(id_O, PORT_OUT);
} else {
log_error("Trying to create unknown cell type %s\n", type.c_str(ctx));
}
return cell;
}
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,121 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "config.h"
#include <boost/range/adaptor/reversed.hpp>
#include <iomanip>
#include <set>
#include "log.h"
NEXTPNR_NAMESPACE_BEGIN
#define fmt(x) (static_cast<const std::ostringstream &>(std::ostringstream() << x).str())
inline std::string to_string(const std::vector<bool> &bv)
{
std::ostringstream os;
for (auto bit : boost::adaptors::reverse(bv))
os << (bit ? '1' : '0');
return os.str();
}
std::ostream &operator<<(std::ostream &out, const ConfigWord &cw)
{
out << cw.name << " " << to_string(cw.value) << std::endl;
return out;
}
std::ostream &operator<<(std::ostream &out, const TileConfig &tc)
{
for (const auto &cword : tc.cwords)
out << cword;
return out;
}
void TileConfig::add_word(const std::string &name, const std::vector<bool> &value)
{
if (!added.count(name)) {
cwords.push_back({name, value});
added.emplace(name, value);
} else {
auto val = added.at(name);
if (val != value)
log_error("Trying to add value to already assigned word %s\n", name.c_str());
}
}
std::string TileConfig::to_string() const
{
std::stringstream ss;
ss << *this;
return ss.str();
}
bool TileConfig::empty() const { return cwords.empty(); }
std::ostream &operator<<(std::ostream &out, const ChipConfig &cc)
{
out << ".device " << cc.chip_name << std::endl << std::endl;
for (const auto &config : cc.configs) {
if (!config.second.empty()) {
out << ".config " << config.first << " " << std::endl;
out << config.second;
out << std::endl;
}
}
for (const auto &ser : cc.serdes) {
if (!ser.second.empty()) {
out << ".serdes " << ser.first << " " << std::endl;
out << ser.second;
out << std::endl;
}
}
for (const auto &tile : cc.tiles) {
if (!tile.second.empty()) {
out << ".tile " << tile.first.die << " " << tile.first.x << " " << tile.first.y << std::endl;
out << tile.second;
out << std::endl;
}
}
for (const auto &bram : cc.brams) {
if (!bram.second.empty()) {
out << ".bram " << bram.first.die << " " << bram.first.x << " " << bram.first.y << std::endl;
out << bram.second;
out << std::endl;
}
}
for (const auto &bram : cc.bram_data) {
if (!bram.second.empty()) {
out << ".bram_init " << bram.first.die << " " << bram.first.x << " " << bram.first.y << std::endl;
std::ios_base::fmtflags f(out.flags());
for (size_t i = 0; i < bram.second.size(); i++) {
out << std::setw(2) << std::setfill('0') << std::hex << (int)bram.second.at(i);
if (i % 40 == 39)
out << std::endl;
else
out << " ";
}
out.flags(f);
out << std::endl;
}
}
return out;
}
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,85 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef GATEMATE_CONFIG_H
#define GATEMATE_CONFIG_H
#include <map>
#include "nextpnr.h"
NEXTPNR_NAMESPACE_BEGIN
struct ConfigWord
{
std::string name;
std::vector<bool> value;
inline bool operator==(const ConfigWord &other) const { return other.name == name && other.value == value; }
};
std::ostream &operator<<(std::ostream &out, const ConfigWord &cw);
struct TileConfig
{
std::vector<ConfigWord> cwords;
std::map<std::string, std::vector<bool>> added;
void add_word(const std::string &name, const std::vector<bool> &value);
std::string to_string() const;
bool empty() const;
};
std::ostream &operator<<(std::ostream &out, const TileConfig &tc);
struct CfgLoc
{
int die;
int x;
int y;
inline bool operator==(const CfgLoc &other) const { return other.die == die && other.x == x && other.y == y; }
inline bool operator!=(const CfgLoc &other) const { return other.die != die || x != other.x || y == other.y; }
inline bool operator<(const CfgLoc &other) const
{
return die < other.die ||
((die == other.die && y < other.y) || (die == other.die && y == other.y && x < other.x));
}
};
class ChipConfig
{
public:
std::string chip_name;
std::string chip_package;
std::map<CfgLoc, TileConfig> tiles;
std::map<CfgLoc, TileConfig> brams;
std::map<int, TileConfig> serdes;
std::map<int, TileConfig> configs;
// Block RAM initialisation
std::map<CfgLoc, std::vector<uint8_t>> bram_data;
};
std::ostream &operator<<(std::ostream &out, const ChipConfig &cc);
NEXTPNR_NAMESPACE_END
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,97 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef GATEMATE_EXTRA_DATA_H
#define GATEMATE_EXTRA_DATA_H
#include "nextpnr.h"
NEXTPNR_NAMESPACE_BEGIN
NPNR_PACKED_STRUCT(struct GateMateTileExtraDataPOD {
uint8_t die;
uint8_t bit_x;
uint8_t bit_y;
uint8_t prim_id;
});
NPNR_PACKED_STRUCT(struct GateMatePipExtraDataPOD {
int32_t name;
uint8_t bits;
uint8_t value;
uint8_t flags;
uint8_t type;
});
NPNR_PACKED_STRUCT(struct GateMateBelPinConstraintPOD {
int32_t name;
int16_t constr_x;
int16_t constr_y;
int16_t constr_z;
int16_t dummy;
});
NPNR_PACKED_STRUCT(struct GateMateBelExtraDataPOD { RelSlice<GateMateBelPinConstraintPOD> constraints; });
enum MuxFlags
{
MUX_INVERT = 1,
MUX_VISIBLE = 2,
MUX_CONFIG = 4,
};
enum PipExtra
{
PIP_EXTRA_MUX = 1,
};
enum CPEFunction
{
C_ADDF = 1,
C_ADDF2 = 2,
C_MULT = 3,
C_MX4 = 4,
C_EN_CIN = 5,
C_CONCAT = 6,
C_ADDCIN = 7,
};
enum ClusterPlacement
{
PLACE_DB_CONSTR = 32,
};
struct PllCfgRecord
{
double weight;
double f_core;
double f_dco;
double f_core_delta;
double core_weight;
int32_t K;
int32_t N1;
int32_t N2;
int32_t M1;
int32_t M2;
int32_t PDIV1;
};
NEXTPNR_NAMESPACE_END
#endif

View File

@ -0,0 +1,419 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "gatemate.h"
#include "design_utils.h"
#include "placer_heap.h"
#define GEN_INIT_CONSTIDS
#define HIMBAECHEL_CONSTIDS "uarch/gatemate/constids.inc"
#include "himbaechel_constids.h"
NEXTPNR_NAMESPACE_BEGIN
GateMateImpl::~GateMateImpl() {};
void GateMateImpl::init_database(Arch *arch)
{
const ArchArgs &args = arch->args;
init_uarch_constids(arch);
arch->load_chipdb(stringf("gatemate/chipdb-%s.bin", args.device.c_str()));
arch->set_package("FBGA324");
arch->set_speed_grade("DEFAULT");
}
void GateMateImpl::init(Context *ctx)
{
HimbaechelAPI::init(ctx);
for (const auto &pad : ctx->package_info->pads) {
available_pads.emplace(IdString(pad.package_pin));
BelId bel = ctx->getBelByName(IdStringList::concat(IdString(pad.tile), IdString(pad.bel)));
bel_to_pad.emplace(bel, &pad);
}
for (auto bel : ctx->getBels()) {
auto *ptr = bel_extra_data(bel);
std::map<IdString, const GateMateBelPinConstraintPOD *> pins;
for (const auto &p : ptr->constraints)
pins.emplace(IdString(p.name), &p);
pin_to_constr.emplace(bel, pins);
}
}
delay_t GateMateImpl::estimateDelay(WireId src, WireId dst) const
{
int sx, sy, dx, dy;
tile_xy(ctx->chip_info, src.tile, sx, sy);
tile_xy(ctx->chip_info, dst.tile, dx, dy);
return 100 * (std::abs(dx - sx) / 4 + std::abs(dy - sy) / 4 + 2);
}
bool GateMateImpl::isBelLocationValid(BelId bel, bool explain_invalid) const
{
CellInfo *cell = ctx->getBoundBelCell(bel);
if (cell == nullptr) {
return true;
}
if (ctx->getBelType(bel).in(id_CPE_HALF, id_CPE_HALF_L, id_CPE_HALF_U)) {
Loc loc = ctx->getBelLocation(bel);
const CellInfo *adj_half = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(loc.x, loc.y, loc.z == 1 ? 0 : 1)));
if (adj_half) {
const auto &half_data = fast_cell_info.at(cell->flat_index);
if (half_data.dff_used) {
const auto &adj_data = fast_cell_info.at(adj_half->flat_index);
if (adj_data.dff_used) {
if (adj_data.ff_config != half_data.ff_config)
return false;
if (adj_data.ff_en != half_data.ff_en)
return false;
if (adj_data.ff_clk != half_data.ff_clk)
return false;
if (adj_data.ff_sr != half_data.ff_sr)
return false;
}
}
}
return true;
}
return true;
}
Loc GateMateImpl::getRelativeConstraint(Loc &root_loc, IdString id) const
{
Loc child_loc;
BelId root_bel = ctx->getBelByLocation(root_loc);
if (pin_to_constr.count(root_bel)) {
auto &constr = pin_to_constr.at(root_bel);
if (constr.count(id)) {
auto &p = constr.at(id);
child_loc.x = root_loc.x + p->constr_x;
child_loc.y = root_loc.y + p->constr_y;
child_loc.z = p->constr_z;
} else {
log_error("Constrain info not available for pin.\n");
}
} else {
log_error("Bel info not available for constraints.\n");
}
return child_loc;
}
bool GateMateImpl::getChildPlacement(const BaseClusterInfo *cluster, Loc root_loc,
std::vector<std::pair<CellInfo *, BelId>> &placement) const
{
for (auto child : cluster->constr_children) {
Loc child_loc;
if (child->constr_z >= PLACE_DB_CONSTR) {
child_loc = getRelativeConstraint(root_loc, IdString(child->constr_z - PLACE_DB_CONSTR));
} else {
child_loc.x = root_loc.x + child->constr_x;
child_loc.y = root_loc.y + child->constr_y;
child_loc.z = child->constr_abs_z ? child->constr_z : (root_loc.z + child->constr_z);
}
BelId child_bel = ctx->getBelByLocation(child_loc);
if (child_bel == BelId() || !this->isValidBelForCellType(child->type, child_bel))
return false;
placement.emplace_back(child, child_bel);
if (!getChildPlacement(child, child_loc, placement))
return false;
}
return true;
}
bool GateMateImpl::getClusterPlacement(ClusterId cluster, BelId root_bel,
std::vector<std::pair<CellInfo *, BelId>> &placement) const
{
CellInfo *root_cell = get_cluster_root(ctx, cluster);
placement.clear();
NPNR_ASSERT(root_bel != BelId());
Loc root_loc = ctx->getBelLocation(root_bel);
if (root_cell->constr_abs_z) {
// Coerce root to absolute z constraint
root_loc.z = root_cell->constr_z;
root_bel = ctx->getBelByLocation(root_loc);
if (root_bel == BelId() || !this->isValidBelForCellType(root_cell->type, root_bel))
return false;
}
placement.emplace_back(root_cell, root_bel);
return getChildPlacement(root_cell, root_loc, placement);
}
bool GateMateImpl::need_inversion(CellInfo *cell, IdString port)
{
PortRef sink;
sink.cell = cell;
sink.port = port;
NetInfo *net_info = cell->getPort(port);
if (!net_info)
return false;
WireId src_wire = ctx->getNetinfoSourceWire(net_info);
WireId dst_wire = ctx->getNetinfoSinkWire(net_info, sink, 0);
if (src_wire == WireId())
return false;
WireId cursor = dst_wire;
bool invert = false;
while (cursor != WireId() && cursor != src_wire) {
auto it = net_info->wires.find(cursor);
if (it == net_info->wires.end())
break;
PipId pip = it->second.pip;
if (pip == PipId())
break;
invert ^= ctx->isPipInverting(pip);
cursor = ctx->getPipSrcWire(pip);
}
return invert;
}
void GateMateImpl::update_cpe_lt(CellInfo *cell, IdString port, IdString init)
{
unsigned init_val = int_or_default(cell->params, init);
bool invert = need_inversion(cell, port);
if (invert) {
if (port.in(id_IN1, id_IN3))
init_val = (init_val & 0b1010) >> 1 | (init_val & 0b0101) << 1;
else
init_val = (init_val & 0b0011) << 2 | (init_val & 0b1100) >> 2;
cell->params[init] = Property(init_val, 4);
}
}
void GateMateImpl::update_cpe_inv(CellInfo *cell, IdString port, IdString param)
{
unsigned init_val = int_or_default(cell->params, param);
bool invert = need_inversion(cell, port);
if (invert) {
cell->params[param] = Property(3 - init_val, 2);
}
}
void GateMateImpl::update_cpe_mux(CellInfo *cell, IdString port, IdString param, int bit)
{
// Mux inversion data is contained in other CPE half
Loc l = ctx->getBelLocation(cell->bel);
CellInfo *cell_l = ctx->getBoundBelCell(ctx->getBelByLocation(Loc(l.x, l.y, 1)));
unsigned init_val = int_or_default(cell_l->params, param);
bool invert = need_inversion(cell, port);
if (invert) {
int old = (init_val >> bit) & 1;
int val = (init_val & (~(1 << bit) & 0xf)) | ((!old) << bit);
cell_l->params[param] = Property(val, 4);
}
}
void GateMateImpl::rename_param(CellInfo *cell, IdString name, IdString new_name, int width)
{
if (cell->params.count(name)) {
cell->params[new_name] = Property(int_or_default(cell->params, name, 0), width);
cell->unsetParam(name);
}
}
void GateMateImpl::prePlace() { assign_cell_info(); }
void GateMateImpl::postPlace()
{
ctx->assignArchInfo();
for (auto &cell : ctx->cells) {
if (cell.second->type.in(id_CPE_HALF, id_CPE_HALF_U, id_CPE_HALF_L)) {
Loc l = ctx->getBelLocation(cell.second->bel);
if (l.z == 0) { // CPE_HALF_U
if (cell.second->params.count(id_C_O) && int_or_default(cell.second->params, id_C_O, 0) == 0)
cell.second->params[id_C_2D_IN] = Property(1, 1);
rename_param(cell.second.get(), id_C_O, id_C_O2, 2);
rename_param(cell.second.get(), id_C_RAM_I, id_C_RAM_I2, 1);
rename_param(cell.second.get(), id_C_RAM_O, id_C_RAM_O2, 1);
cell.second->type = id_CPE_HALF_U;
} else { // CPE_HALF_L
if (!cell.second->params.count(id_INIT_L20))
cell.second->params[id_INIT_L20] = Property(0b1100, 4);
rename_param(cell.second.get(), id_C_O, id_C_O1, 2);
rename_param(cell.second.get(), id_INIT_L00, id_INIT_L02, 4);
rename_param(cell.second.get(), id_INIT_L01, id_INIT_L03, 4);
rename_param(cell.second.get(), id_INIT_L10, id_INIT_L11, 4);
rename_param(cell.second.get(), id_C_RAM_I, id_C_RAM_I1, 1);
rename_param(cell.second.get(), id_C_RAM_O, id_C_RAM_O1, 1);
cell.second->type = id_CPE_HALF_L;
}
}
}
}
void GateMateImpl::postRoute()
{
ctx->assignArchInfo();
// Update configuration bits based on signal inversion
for (auto &cell : ctx->cells) {
if (cell.second->type.in(id_CPE_HALF_U)) {
uint8_t func = int_or_default(cell.second->params, id_C_FUNCTION, 0);
if (func != C_MX4) {
update_cpe_lt(cell.second.get(), id_IN1, id_INIT_L00);
update_cpe_lt(cell.second.get(), id_IN2, id_INIT_L00);
update_cpe_lt(cell.second.get(), id_IN3, id_INIT_L01);
update_cpe_lt(cell.second.get(), id_IN4, id_INIT_L01);
} else {
update_cpe_mux(cell.second.get(), id_IN1, id_INIT_L11, 0);
update_cpe_mux(cell.second.get(), id_IN2, id_INIT_L11, 1);
update_cpe_mux(cell.second.get(), id_IN3, id_INIT_L11, 2);
update_cpe_mux(cell.second.get(), id_IN4, id_INIT_L11, 3);
}
}
if (cell.second->type.in(id_CPE_HALF_L)) {
update_cpe_lt(cell.second.get(), id_IN1, id_INIT_L02);
update_cpe_lt(cell.second.get(), id_IN2, id_INIT_L02);
update_cpe_lt(cell.second.get(), id_IN3, id_INIT_L03);
update_cpe_lt(cell.second.get(), id_IN4, id_INIT_L03);
}
if (cell.second->type.in(id_CPE_HALF_U, id_CPE_HALF_L)) {
update_cpe_inv(cell.second.get(), id_CLK, id_C_CPE_CLK);
update_cpe_inv(cell.second.get(), id_EN, id_C_CPE_EN);
bool set = int_or_default(cell.second->params, id_C_EN_SR, 0) == 1;
if (set)
update_cpe_inv(cell.second.get(), id_SR, id_C_CPE_SET);
else
update_cpe_inv(cell.second.get(), id_SR, id_C_CPE_RES);
}
}
// Sanity check
for (auto &c : ctx->cells) {
CellInfo *cell = c.second.get();
if (!cell->type.in(id_CPE_HALF_U, id_CPE_HALF_L)) {
for (auto port : cell->ports) {
if (need_inversion(cell, port.first)) {
log_error("Unhandled cell '%s' of type '%s' port '%s'\n", cell->name.c_str(ctx),
cell->type.c_str(ctx), port.first.c_str(ctx));
}
}
}
}
print_utilisation(ctx);
const ArchArgs &args = ctx->args;
if (args.options.count("out")) {
write_bitstream(args.device, args.options.at("out"));
}
}
void GateMateImpl::configurePlacerHeap(PlacerHeapCfg &cfg)
{
cfg.beta = 0.5;
cfg.placeAllAtOnce = true;
}
void GateMateImpl::assign_cell_info()
{
fast_cell_info.resize(ctx->cells.size());
for (auto &cell : ctx->cells) {
CellInfo *ci = cell.second.get();
auto &fc = fast_cell_info.at(ci->flat_index);
if (ci->type.in(id_CPE_HALF, id_CPE_HALF_U, id_CPE_HALF_L)) {
fc.signal_used = int_or_default(ci->params, id_C_O, -1);
fc.ff_en = ci->getPort(id_EN);
fc.ff_clk = ci->getPort(id_CLK);
fc.ff_sr = ci->getPort(id_SR);
fc.ff_config = 0;
if (fc.signal_used == 0) {
fc.ff_config |= int_or_default(ci->params, id_C_CPE_EN, 0);
fc.ff_config <<= 2;
fc.ff_config |= int_or_default(ci->params, id_C_CPE_CLK, 0);
fc.ff_config <<= 2;
fc.ff_config |= int_or_default(ci->params, id_C_CPE_RES, 0);
fc.ff_config <<= 2;
fc.ff_config |= int_or_default(ci->params, id_C_CPE_SET, 0);
fc.ff_config <<= 2;
fc.ff_config |= int_or_default(ci->params, id_C_EN_SR, 0);
fc.ff_config <<= 1;
fc.ff_config |= int_or_default(ci->params, id_C_L_D, 0);
fc.ff_config <<= 1;
fc.ff_config |= int_or_default(ci->params, id_FF_INIT, 0);
fc.dff_used = true;
}
}
}
}
// Bel bucket functions
IdString GateMateImpl::getBelBucketForCellType(IdString cell_type) const
{
if (cell_type.in(id_CC_IBUF, id_CC_OBUF, id_CC_TOBUF, id_CC_IOBUF, id_CC_LVDS_IBUF, id_CC_LVDS_TOBUF,
id_CC_LVDS_OBUF, id_CC_LVDS_IOBUF))
return id_GPIO;
else if (cell_type.in(id_CPE_HALF_U, id_CPE_HALF_L, id_CPE_HALF))
return id_CPE_HALF;
else
return cell_type;
}
BelBucketId GateMateImpl::getBelBucketForBel(BelId bel) const
{
IdString bel_type = ctx->getBelType(bel);
if (bel_type.in(id_CPE_HALF_U, id_CPE_HALF_L))
return id_CPE_HALF;
return bel_type;
}
bool GateMateImpl::isValidBelForCellType(IdString cell_type, BelId bel) const
{
IdString bel_type = ctx->getBelType(bel);
if (bel_type == id_GPIO)
return cell_type.in(id_CC_IBUF, id_CC_OBUF, id_CC_TOBUF, id_CC_IOBUF, id_CC_LVDS_IBUF, id_CC_LVDS_TOBUF,
id_CC_LVDS_OBUF, id_CC_LVDS_IOBUF);
else if (bel_type == id_CPE_HALF_U)
return cell_type.in(id_CPE_HALF_U, id_CPE_HALF);
else if (bel_type == id_CPE_HALF_L)
return cell_type.in(id_CPE_HALF_L, id_CPE_HALF);
else
return (bel_type == cell_type);
}
bool GateMateImpl::isPipInverting(PipId pip) const
{
const auto &extra_data =
*reinterpret_cast<const GateMatePipExtraDataPOD *>(chip_pip_info(ctx->chip_info, pip).extra_data.get());
return extra_data.type == PipExtra::PIP_EXTRA_MUX && (extra_data.flags & MUX_INVERT);
}
const GateMateBelExtraDataPOD *GateMateImpl::bel_extra_data(BelId bel) const
{
return reinterpret_cast<const GateMateBelExtraDataPOD *>(chip_bel_info(ctx->chip_info, bel).extra_data.get());
}
struct GateMateArch : HimbaechelArch
{
GateMateArch() : HimbaechelArch("gatemate") {};
bool match_device(const std::string &device) override
{
return device.size() > 6 && device.substr(0, 6) == "CCGM1A";
}
std::unique_ptr<HimbaechelAPI> create(const std::string &device, const dict<std::string, std::string> &args)
{
return std::make_unique<GateMateImpl>();
}
} gateMateArch;
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,97 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef HIMBAECHEL_GATEMATE_H
#define HIMBAECHEL_GATEMATE_H
#include "extra_data.h"
#include "himbaechel_api.h"
#include "log.h"
#include "nextpnr.h"
#include "util.h"
#include "himbaechel_helpers.h"
NEXTPNR_NAMESPACE_BEGIN
struct GateMateImpl : HimbaechelAPI
{
~GateMateImpl();
void init_database(Arch *arch) override;
void init(Context *ctx) override;
void pack() override;
void prePlace() override;
void postPlace() override;
void postRoute() override;
bool isBelLocationValid(BelId bel, bool explain_invalid = false) const override;
delay_t estimateDelay(WireId src, WireId dst) const override;
void drawBel(std::vector<GraphicElement> &g, GraphicElement::style_t style, IdString bel_type, Loc loc) override;
bool getClusterPlacement(ClusterId cluster, BelId root_bel,
std::vector<std::pair<CellInfo *, BelId>> &placement) const override;
IdString getBelBucketForCellType(IdString cell_type) const override;
bool isValidBelForCellType(IdString cell_type, BelId bel) const override;
BelBucketId getBelBucketForBel(BelId bel) const override;
Loc getRelativeConstraint(Loc &root_loc, IdString id) const;
void configurePlacerHeap(PlacerHeapCfg &cfg) override;
bool isPipInverting(PipId pip) const override;
std::set<IdString> available_pads;
std::map<BelId, const PadInfoPOD *> bel_to_pad;
private:
bool getChildPlacement(const BaseClusterInfo *cluster, Loc root_loc,
std::vector<std::pair<CellInfo *, BelId>> &placement) const;
void write_bitstream(const std::string &device, const std::string &filename);
void parse_ccf(const std::string &filename);
void assign_cell_info();
bool need_inversion(CellInfo *cell, IdString port);
void update_cpe_lt(CellInfo *cell, IdString port, IdString init);
void update_cpe_inv(CellInfo *cell, IdString port, IdString param);
void update_cpe_mux(CellInfo *cell, IdString port, IdString param, int bit);
void rename_param(CellInfo *cell, IdString name, IdString new_name, int width);
const GateMateBelExtraDataPOD *bel_extra_data(BelId bel) const;
struct GateMateCellInfo
{
// slice info
const NetInfo *ff_en = nullptr, *ff_clk = nullptr, *ff_sr = nullptr;
int ff_config = 0;
int signal_used = -1;
bool dff_used = false;
};
std::vector<GateMateCellInfo> fast_cell_info;
std::map<BelId, std::map<IdString, const GateMateBelPinConstraintPOD *>> pin_to_constr;
};
NEXTPNR_NAMESPACE_END
#endif

View File

@ -0,0 +1,84 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef GATEMATE_UTIL_H
#define GATEMATE_UTIL_H
#include <map>
#include <set>
#include <string>
#include "nextpnr.h"
#include "log.h"
NEXTPNR_NAMESPACE_BEGIN
template <typename KeyType>
double double_or_default(const dict<KeyType, Property> &ct, const KeyType &key, double def = 0)
{
auto found = ct.find(key);
if (found == ct.end())
return def;
else {
if (found->second.is_string) {
try {
return std::stod(found->second.as_string());
} catch (std::invalid_argument &e) {
log_error("Expecting numeric value but got '%s'.\n", found->second.as_string().c_str());
}
} else
return double(found->second.as_int64());
}
};
template <typename KeyType>
int extract_bits(const dict<KeyType, Property> &ct, const KeyType &key, int start, int bits, int def = 0)
{
Property extr = get_or_default(ct, key, Property()).extract(start, bits);
if (extr.is_string) {
try {
return std::stoi(extr.as_string());
} catch (std::invalid_argument &e) {
log_error("Expecting numeric value but got '%s'.\n", extr.as_string().c_str());
}
} else
return extr.as_int64();
}
template <typename T>
std::vector<std::vector<T>> splitNestedVector(const std::vector<std::vector<T>> &input, size_t maxSize = 8)
{
std::vector<std::vector<T>> result;
for (const auto &inner : input) {
size_t i = 0;
while (i < inner.size()) {
size_t end = std::min(i + maxSize, inner.size());
result.emplace_back(inner.begin() + i, inner.begin() + end);
i = end;
}
}
return result;
}
NEXTPNR_NAMESPACE_END
#endif

View File

@ -0,0 +1,200 @@
#
# nextpnr -- Next Generation Place and Route
#
# Copyright (C) 2024 The Project Peppercorn Authors.
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
import os
from os import path
import sys
import argparse
sys.path.append(path.join(path.dirname(__file__), "../../.."))
from himbaechel_dbgen.chip import *
PIP_EXTRA_MUX = 1
MUX_INVERT = 1
MUX_VISIBLE = 2
MUX_CONFIG = 4
parser = argparse.ArgumentParser()
parser.add_argument("--lib", help="Project Peppercorn python database script path", type=str, required=True)
parser.add_argument("--device", help="name of device to export", type=str, required=True)
parser.add_argument("--bba", help="bba file to write", type=str, required=True)
args = parser.parse_args()
sys.path.append(os.path.expanduser(args.lib))
sys.path += args.lib
import chip
import die
@dataclass
class TileExtraData(BBAStruct):
die : int = 0
bit_x: int = 0
bit_y: int = 0
prim_id : int = 0
def serialise_lists(self, context: str, bba: BBAWriter):
pass
def serialise(self, context: str, bba: BBAWriter):
bba.u8(self.die)
bba.u8(self.bit_x)
bba.u8(self.bit_y)
bba.u8(self.prim_id)
@dataclass
class PipExtraData(BBAStruct):
pip_type: int
name: IdString
bits: int = 0
value: int = 0
invert: int = 0
def serialise_lists(self, context: str, bba: BBAWriter):
pass
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.name.index)
bba.u8(self.bits)
bba.u8(self.value)
bba.u8(self.invert)
bba.u8(self.pip_type)
@dataclass
class BelPinConstraint(BBAStruct):
index: int
pin_name: IdString
constr_x: int = 0
constr_y: int = 0
constr_z: int = 0
def serialise_lists(self, context: str, bba: BBAWriter):
pass
def serialise(self, context: str, bba: BBAWriter):
bba.u32(self.pin_name.index)
bba.u16(self.constr_x)
bba.u16(self.constr_y)
bba.u16(self.constr_z)
bba.u16(0)
@dataclass
class BelExtraData(BBAStruct):
constraints: list[BelPinConstraint] = field(default_factory = list)
def add_constraints(self, pin: IdString, x: int, y: int, z:int):
item = BelPinConstraint(len(self.constraints),pin,x,y,z)
self.constraints.append(item)
def serialise_lists(self, context: str, bba: BBAWriter):
self.constraints.sort(key=lambda p: p.pin_name.index)
bba.label(f"{context}_constraints")
for i, t in enumerate(self.constraints):
t.serialise(f"{context}_constraint{i}", bba)
pass
def serialise(self, context: str, bba: BBAWriter):
bba.slice(f"{context}_constraints", len(self.constraints))
def set_timings(ch):
speed = "DEFAULT"
tmg = ch.set_speed_grades([speed])
lut = ch.timing.add_cell_variant(speed, "CPE_HALF_L")
lut.add_comb_arc("IN1", "OUT", TimingValue(416, 418)) # IN5 to OUT1
lut.add_comb_arc("IN2", "OUT", TimingValue(413, 422)) # IN6 to OUT1
lut.add_comb_arc("IN3", "OUT", TimingValue(372, 374)) # IN7 to OUT1
lut.add_comb_arc("IN4", "OUT", TimingValue(275, 385)) # IN8 to OUT1
lut = ch.timing.add_cell_variant(speed, "CPE_HALF_U")
lut.add_comb_arc("IN1", "OUT", TimingValue(479, 484)) # to OUT2
lut.add_comb_arc("IN2", "OUT", TimingValue(471, 488)) # to OUT2
lut.add_comb_arc("IN3", "OUT", TimingValue(446, 449)) # to OUT2
lut.add_comb_arc("IN4", "OUT", TimingValue(443, 453)) # to OUT2
lut = ch.timing.add_cell_variant(speed, "CPE_HALF")
lut.add_comb_arc("IN1", "OUT", TimingValue(479, 484)) # to OUT2
lut.add_comb_arc("IN2", "OUT", TimingValue(471, 488)) # to OUT2
lut.add_comb_arc("IN3", "OUT", TimingValue(446, 449)) # to OUT2
lut.add_comb_arc("IN4", "OUT", TimingValue(443, 453)) # to OUT2
dff = ch.timing.add_cell_variant(speed, "CPE_DFF")
dff.add_setup_hold("CLK", "IN1", ClockEdge.RISING, TimingValue(60), TimingValue(50))
dff.add_setup_hold("CLK", "IN2", ClockEdge.RISING, TimingValue(60), TimingValue(50))
dff.add_setup_hold("CLK", "IN3", ClockEdge.RISING, TimingValue(60), TimingValue(50))
dff.add_setup_hold("CLK", "IN4", ClockEdge.RISING, TimingValue(60), TimingValue(50))
dff.add_clock_out("CLK", "OUT", ClockEdge.RISING, TimingValue(60))
def main():
# Range needs to be +1, but we are adding +2 more to coordinates, since
# they are starting from -2 instead of zero required for nextpnr
dev = chip.get_device(args.device)
ch = Chip("gatemate", args.device, dev.max_col() + 3, dev.max_row() + 3)
# Init constant ids
ch.strs.read_constids(path.join(path.dirname(__file__), "..", "constids.inc"))
ch.read_gfxids(path.join(path.dirname(__file__), "..", "gfxids.inc"))
for type_name in sorted(die.get_tile_type_list()):
tt = ch.create_tile_type(type_name)
for group in sorted(die.get_groups_for_type(type_name)):
tt.create_group(group.name, group.type)
for wire in sorted(die.get_endpoints_for_type(type_name)):
tt.create_wire(wire.name, wire.type)
if "GPIO" in type_name:
tt.create_wire("GPIO.DI", "CPE_VIRTUAL_WIRE")
for prim in sorted(die.get_primitives_for_type(type_name)):
bel = tt.create_bel(prim.name, prim.type, prim.z)
extra = BelExtraData()
for constr in sorted(die.get_pins_constraint(type_name, prim.name, prim.type)):
extra.add_constraints(ch.strs.id(constr.name),constr.rel_x,constr.rel_y,0 if constr.pin_num==2 else 1)
bel.extra_data = extra
if prim.name == "GPIO":
tt.add_bel_pin(bel, "DI", "GPIO.DI", PinType.INPUT)
for pin in sorted(die.get_primitive_pins(prim.type)):
tt.add_bel_pin(bel, pin.name, die.get_pin_connection_name(prim,pin), pin.dir)
for mux in sorted(die.get_mux_connections_for_type(type_name)):
pp = tt.create_pip(mux.src, mux.dst)
if mux.name:
mux_flags = MUX_INVERT if mux.invert else 0
mux_flags |= MUX_VISIBLE if mux.visible else 0
mux_flags |= MUX_CONFIG if mux.config else 0
pp.extra_data = PipExtraData(PIP_EXTRA_MUX, ch.strs.id(mux.name), mux.bits, mux.value, mux_flags)
if "GPIO" in type_name:
tt.create_pip("GPIO.DI", "GPIO.IN1")
tt.create_pip("GPIO.DI", "GPIO.IN2")
# Setup tile grid
for x in range(dev.max_col() + 3):
for y in range(dev.max_row() + 3):
ti = ch.set_tile_type(x, y, dev.get_tile_type(x - 2,y - 2))
tileinfo = dev.get_tile_info(x - 2,y - 2)
ti.extra_data = TileExtraData(tileinfo.die, tileinfo.bit_x, tileinfo.bit_y, tileinfo.prim_index)
# Create nodes between tiles
for _,nodes in dev.get_connections():
node = []
for conn in sorted(nodes):
node.append(NodeWire(conn.x + 2, conn.y + 2, conn.name))
ch.add_node(node)
set_timings(ch)
for package in dev.get_packages():
pkg = ch.create_package(package)
for pad in sorted(dev.get_package_pads(package)):
pkg.create_pad(pad.name, f"X{pad.x+2}Y{pad.y+2}", pad.bel, pad.function, pad.bank, pad.flags)
ch.write_bba(args.bba)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,89 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "gatemate.h"
#define HIMBAECHEL_CONSTIDS "uarch/gatemate/constids.inc"
#define HIMBAECHEL_GFXIDS "uarch/gatemate/gfxids.inc"
#define HIMBAECHEL_UARCH gatemate
#include "himbaechel_constids.h"
#include "himbaechel_gfxids.h"
NEXTPNR_NAMESPACE_BEGIN
void GateMateImpl::drawBel(std::vector<GraphicElement> &g, GraphicElement::style_t style, IdString bel_type, Loc loc)
{
GraphicElement el;
el.type = GraphicElement::TYPE_BOX;
el.style = style;
switch (bel_type.index) {
case id_CPE_HALF_L.index:
el.x1 = loc.x + 0.20;
el.x2 = el.x1 + 0.20;
el.y1 = loc.y + 0.25;
el.y2 = el.y1 + 0.20;
g.push_back(el);
break;
case id_CPE_HALF_U.index:
el.x1 = loc.x + 0.20;
el.x2 = el.x1 + 0.20;
el.y1 = loc.y + 0.55;
el.y2 = el.y1 + 0.20;
g.push_back(el);
break;
case id_RAM.index:
el.x1 = loc.x + 0.70;
el.x2 = el.x1 + 0.20;
el.y1 = loc.y + 0.20;
el.y2 = el.y1 + 15.60;
g.push_back(el);
break;
case id_GPIO.index:
el.x1 = loc.x + 0.20;
el.x2 = el.x1 + 0.60;
el.y1 = loc.y + 0.20;
el.y2 = el.y1 + 0.60;
g.push_back(el);
break;
case id_BUFG.index:
el.x1 = loc.x + 0.15 + loc.z * 0.20;
el.x2 = el.x1 + 0.15;
el.y1 = loc.y + 0.10;
el.y2 = el.y1 + 0.30;
g.push_back(el);
break;
case id_PLL.index:
el.x1 = loc.x + 0.15 + (loc.z - 4) * 0.20;
el.x2 = el.x1 + 0.15;
el.y1 = loc.y + 0.60;
el.y2 = el.y1 + 0.30;
g.push_back(el);
break;
case id_USR_RSTN.index:
el.x1 = loc.x + 0.20;
el.x2 = el.x1 + 0.20;
el.y1 = loc.y + 0.20;
el.y2 = el.y1 + 0.20;
g.push_back(el);
break;
}
}
NEXTPNR_NAMESPACE_END

View File

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,78 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef GATEMATE_PACK_H
#define GATEMATE_PACK_H
#include "gatemate.h"
NEXTPNR_NAMESPACE_BEGIN
struct GateMatePacker
{
GateMatePacker(Context *ctx, GateMateImpl *uarch) : ctx(ctx), uarch(uarch) { h.init(ctx); };
void pack_io();
void pack_io_sel();
void pack_cpe();
void pack_addf();
void pack_bufg();
void sort_bufg();
void insert_pll_bufg();
void pack_pll();
void pack_misc();
void pack_constants();
void pack_ram();
void remove_constants();
void remove_not_used();
private:
void dff_to_cpe(CellInfo *dff, CellInfo *cpe);
void insert_bufg(CellInfo *cell, IdString port);
void disconnect_if_gnd(CellInfo *cell, IdString input);
void pll_out(CellInfo *cell, IdString origPort, Loc fixed);
PllCfgRecord get_pll_settings(double f_ref, double f_core, int mode, int low_jitter, bool pdiv0_mux, bool feedback);
CellInfo *move_ram_i(CellInfo *cell, IdString origPort, bool place = true);
CellInfo *move_ram_o(CellInfo *cell, IdString origPort, bool place = true);
CellInfo *move_ram_i_fixed(CellInfo *cell, IdString origPort, Loc fixed);
CellInfo *move_ram_o_fixed(CellInfo *cell, IdString origPort, Loc fixed);
CellInfo *move_ram_io(CellInfo *cell, IdString iPort, IdString oPort, bool place = true);
uint8_t ram_ctrl_signal(CellInfo *cell, IdString port, bool alt);
uint8_t ram_clk_signal(CellInfo *cell, IdString port);
bool is_gpio_valid_dff(CellInfo *dff);
BelId get_bank_cpe(int bank);
// Cell creating
CellInfo *create_cell_ptr(IdString type, IdString name);
void flush_cells();
void pack_ram_cell(CellInfo &ci, CellInfo *cell, int num, bool is_split);
pool<IdString> packed_cells;
std::map<NetInfo *, int> global_signals;
Context *ctx;
GateMateImpl *uarch;
HimbaechelHelpers h;
};
NEXTPNR_NAMESPACE_END
#endif

View File

@ -0,0 +1,352 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include <boost/algorithm/string.hpp>
#include "pack.h"
#define HIMBAECHEL_CONSTIDS "uarch/gatemate/constids.inc"
#include "himbaechel_constids.h"
namespace {
USING_NEXTPNR_NAMESPACE;
double calculate_delta_stepsize(double num)
{
std::ostringstream out;
out << std::fixed << std::setprecision(4) << num;
std::string str = out.str();
switch (4 - (str.size() - str.find_last_not_of('0') - 1)) {
case 4:
return 0.0001;
case 3:
return 0.001;
case 2:
return 0.01;
case 1:
return 0.1;
default:
return 1.0;
}
}
int getDCO_optimized_value(int f_dco_min, int f_dco_max, double f_dco)
{
/*
optimization parameters
DCO_OPT:
1: Optimized to lower DCO frequency.
2: Optimized to the middle of the DCO range (DEFAULT)
3: Optimized to upper DCO frequency.
*/
const int DCO_OPT = 2; // 1: to lower DCO, 2: to DCO middle range, 3: to upper DCO, default = 2
const int DCO_FREQ_OPT_FACTOR = 24; // proportional factor btw. frequency match and dco optimization
switch (DCO_OPT) {
case 1:
return round(abs(f_dco - f_dco_min)) * DCO_FREQ_OPT_FACTOR;
case 3:
return round(abs(f_dco - f_dco_max)) * DCO_FREQ_OPT_FACTOR;
default: {
int f_dco_middle = (f_dco_max + f_dco_min) / 2; // Middle of DCO Range in MHz
return round(abs(f_dco - f_dco_middle)) * DCO_FREQ_OPT_FACTOR;
}
}
}
void get_M1_M2(double f_core, double f_core_delta, PllCfgRecord &setting, double max_input_freq)
{
// precise output frequency preference, highest priority
// the larger value the higher priority
const int OUT_FREQ_OPT_FACTOR = 4200000;
std::vector<PllCfgRecord> res_arr;
for (int M1 = 1; M1 <= 64; M1++) {
for (int M2 = 1; M2 <= 1024; M2++) {
if (((M1 * M2) % 2) != 0) {
// M1*M2-->even number(except one);
// this is important to ensure 90 degree phase shifting of clock output
if (M1 * M2 != 1)
continue;
}
double f_core_local = setting.f_dco / (2 * setting.PDIV1 * M1 * M2);
if (f_core_local > max_input_freq / 4)
continue; // f_core max limit
if ((f_core_local - f_core) < -f_core_delta)
break; // lower limit jump to parent loop
if (abs(f_core_local - f_core) > f_core_delta)
continue; // reg limit continue
if (setting.f_dco / setting.PDIV1 > max_input_freq)
continue; // M1 input freq limit
if ((setting.f_dco / setting.PDIV1) / M1 > max_input_freq / 2)
continue; // M2 input freq limit
PllCfgRecord tmp;
tmp.f_core = f_core_local;
tmp.M1 = M1;
tmp.M2 = M2;
tmp.weight = abs(f_core_local - f_core) * OUT_FREQ_OPT_FACTOR;
res_arr.push_back(tmp);
// frequency match
if (abs(f_core_local - f_core) <= f_core_delta) {
auto it = std::min_element(
res_arr.begin(), res_arr.end(),
[](const PllCfgRecord &a, const PllCfgRecord &b) { return a.weight < b.weight; });
size_t index = std::distance(res_arr.begin(), it);
setting.M1 = res_arr[index].M1;
setting.M2 = res_arr[index].M2;
setting.f_core = res_arr[index].f_core;
setting.f_core_delta = abs(res_arr[index].f_core - f_core);
setting.core_weight = res_arr[index].weight + setting.M1 + setting.M2 + setting.N1 + setting.N2 +
setting.K + setting.PDIV1;
return;
}
}
}
}
void get_DCO_ext_feedback(double f_core, double f_ref, PllCfgRecord &setting, int f_dco_min, int f_dco_max,
double f_dco, double max_input_freq)
{
std::vector<PllCfgRecord> res_arr;
for (int M1 = 1; M1 <= 64; M1++) {
for (int M2 = 1; M2 <= 1024; M2++) {
if (((M1 * M2) % 2) != 0) {
// M1*M2-->even number(except one);
// this is important to ensure 90 degree phase shifting of clock output
if (M1 * M2 != 1)
continue;
}
double f_dco_local = (f_ref / setting.K) * 2 * setting.PDIV1 * setting.N1 * setting.N2 * M1 * M2;
if (f_dco_local > f_dco_max)
break; // upper limit
if ((f_dco_local < f_dco_min) || (f_dco_local > f_dco_max))
continue; // dco out of range limit
if (f_dco_local / setting.PDIV1 > max_input_freq)
continue; // M1 input freq limit
if ((f_dco_local / setting.PDIV1) / M1 > max_input_freq / 2)
continue; // M2 input freq limit
PllCfgRecord tmp;
tmp.f_dco = f_dco_local;
tmp.M1 = M1;
tmp.M2 = M2;
tmp.weight = f_dco_local + getDCO_optimized_value(f_dco_min, f_dco_max, f_dco_local);
res_arr.push_back(tmp);
}
}
auto it = std::min_element(res_arr.begin(), res_arr.end(),
[](const PllCfgRecord &a, const PllCfgRecord &b) { return a.weight < b.weight; });
size_t index = std::distance(res_arr.begin(), it);
setting.M1 = res_arr[index].M1;
setting.M2 = res_arr[index].M2;
setting.f_dco = res_arr[index].f_dco;
}
}; // namespace
NEXTPNR_NAMESPACE_BEGIN
PllCfgRecord GateMatePacker::get_pll_settings(double f_ref, double f_core, int mode, int low_jitter, bool pdiv0_mux,
bool feedback)
{
const int MATCH_LIMIT = 10; // only for low jitter = false
// frequency tolerance for low jitter = false, the maximum frequency deviation in MHz from f_core
const double DELTA_LIMIT = 0.1;
double max_input_freq;
int f_dco_min, f_dco_max, pdiv1_min;
double f_dco;
std::vector<PllCfgRecord> pll_cfg_arr;
PllCfgRecord pll_cfg_rec;
switch (mode) {
case 1: {
// low power
f_dco_min = 500;
f_dco_max = 1000;
pdiv1_min = 1;
max_input_freq = 1000;
break;
}
case 2: {
// economy
f_dco_min = 1000;
f_dco_max = 2000;
pdiv1_min = 2;
max_input_freq = 1250;
break;
}
default: {
// initialize as speed
f_dco_min = 1250;
f_dco_max = 2500;
pdiv1_min = 2;
max_input_freq = 1666.67;
break;
}
}
double f_core_par = f_core;
if (f_ref > 50)
log_warning("The PLL input frequency is outside the specified frequency (max 50 MHz ) range\n");
if (pdiv0_mux && feedback) {
double res;
if (modf(f_core / f_ref, &res) != 0)
log_warning("In this PLL mode f_core can only be greater and multiple of f_ref\n");
}
if (pdiv0_mux) {
if (f_core > max_input_freq / 4) {
f_core = max_input_freq / 4;
log_warning("Frequency out of range; PLL max output frequency for mode: %d: %.5f MHz\n", mode,
max_input_freq / 4);
}
}
pll_cfg_rec.K = 1;
pll_cfg_rec.N1 = 1;
pll_cfg_rec.N2 = 1;
pll_cfg_rec.M1 = 1;
pll_cfg_rec.M2 = 1;
pll_cfg_rec.PDIV1 = 2;
pll_cfg_rec.f_dco = 0;
pll_cfg_rec.f_core_delta = std::numeric_limits<double>::max();
pll_cfg_rec.f_core = pll_cfg_rec.f_dco / (2 * pll_cfg_rec.PDIV1);
pll_cfg_rec.core_weight = std::numeric_limits<double>::max();
pll_cfg_arr.push_back(pll_cfg_rec);
double f_core_delta_stepsize = calculate_delta_stepsize(f_core);
int K = 1;
int K_max = low_jitter ? 1 : 1024;
int match_cnt = 0;
int match_delta_cnt = 0;
while (K <= K_max) {
for (int N1 = 1; N1 <= 64; N1++) {
for (int N2 = 1; N2 <= 1024; N2++) {
for (int PDIV1 = pdiv1_min; PDIV1 <= 2; PDIV1++) {
if (feedback) {
// extern feedback
int f_core_local = (f_ref / K) * N1 * N2;
if (f_core_local > max_input_freq / 4)
continue;
if (abs(f_core - f_core_local) < pll_cfg_arr[0].f_core_delta) {
// search for best frequency match
pll_cfg_arr[0].f_core = f_core_local;
pll_cfg_arr[0].f_core_delta = abs(f_core - f_core_local);
pll_cfg_arr[0].K = K;
pll_cfg_arr[0].N1 = N1;
pll_cfg_arr[0].N2 = N2;
pll_cfg_arr[0].PDIV1 = PDIV1;
}
if (pll_cfg_arr[0].f_core_delta == 0) {
get_DCO_ext_feedback(f_core, f_ref, pll_cfg_arr[0], f_dco_min, f_dco_max, f_dco,
max_input_freq);
log_info("PLL fout= %.4f MHz (fout error %.5f%% of requested %.4f MHz)\n",
pll_cfg_arr[0].f_core,
100 - (100 * std::min(pll_cfg_arr[0].f_core, f_core_par) /
std::max(pll_cfg_arr[0].f_core, f_core_par)),
f_core);
return pll_cfg_arr[0];
}
} else {
f_dco = (f_ref / K) * PDIV1 * N1 * N2;
if ((f_dco <= f_dco_min) || (f_dco > f_dco_max))
continue; // DCO out of range
if (f_dco == 0)
continue; // DCO = 0
if (f_dco / PDIV1 > max_input_freq)
continue; // N1 input freq limit
if ((f_dco / PDIV1) / N1 > max_input_freq / 2)
continue; // N2 input freq limit
if (int(f_core) > int(f_dco / (2 * PDIV1)))
continue; // > f_core max
pll_cfg_rec.f_core = 0;
pll_cfg_rec.f_dco = f_dco;
pll_cfg_rec.f_core_delta = std::numeric_limits<double>::max();
pll_cfg_rec.K = K;
pll_cfg_rec.N1 = N1;
pll_cfg_rec.N2 = N2;
pll_cfg_rec.PDIV1 = PDIV1;
pll_cfg_rec.M1 = 0;
pll_cfg_rec.M2 = 0;
pll_cfg_rec.core_weight = std::numeric_limits<double>::max();
if (!pdiv0_mux) {
// f_core = f_dco/2
pll_cfg_rec.M1 = 1;
pll_cfg_rec.M2 = 1;
pll_cfg_rec.f_core = pll_cfg_rec.f_dco / 2;
pll_cfg_rec.core_weight = abs(pll_cfg_rec.f_core - f_core);
} else {
// default clock path
// calculate M1, M2 then calculate weight for intern loop feedback
bool found = false;
double f_core_delta = 0;
while (f_core_delta < (round((max_input_freq / 4) - f_ref / K))) {
get_M1_M2(f_core, f_core_delta, pll_cfg_rec, max_input_freq);
if (pll_cfg_rec.f_core != 0 && pll_cfg_rec.f_core_delta <= f_core_delta) {
// best result
pll_cfg_rec.core_weight +=
pll_cfg_rec.f_dco + getDCO_optimized_value(f_dco_min, f_dco_max, f_dco);
found = true;
}
if (found)
break; // best result found
f_core_delta += f_core_delta_stepsize;
}
}
pll_cfg_arr.push_back(pll_cfg_rec);
if (pll_cfg_rec.f_core_delta == 0)
match_cnt++;
if (pll_cfg_rec.f_core_delta < DELTA_LIMIT)
match_delta_cnt++;
}
}
}
}
K++;
// only with low_jitter false
if (match_cnt > MATCH_LIMIT)
break;
if ((match_cnt == 0) && (match_delta_cnt > MATCH_LIMIT))
break;
}
if (feedback) {
// extern feedback if not exact match pick the best here
get_DCO_ext_feedback(f_core, f_ref, pll_cfg_arr[0], f_dco_min, f_dco_max, f_dco, max_input_freq);
}
auto it =
std::min_element(pll_cfg_arr.begin(), pll_cfg_arr.end(), [](const PllCfgRecord &a, const PllCfgRecord &b) {
return a.core_weight < b.core_weight;
});
PllCfgRecord val = pll_cfg_arr.at(std::distance(pll_cfg_arr.begin(), it));
log_info("PLL fout= %.4f MHz (fout error %.5f%% of requested %.4f MHz)\n", val.f_core,
100 - (100 * std::min(val.f_core, f_core_par) / std::max(val.f_core, f_core_par)), f_core);
return val;
}
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,36 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include <vector>
#include "command.h"
#include "testing.h"
#include "uarch/gatemate/pack.h"
#define HIMBAECHEL_CONSTIDS "uarch/gatemate/constids.inc"
#include "himbaechel_constids.h"
USING_NEXTPNR_NAMESPACE
TEST_F(GateMateTest, pack_constants)
{
GateMatePacker packer(ctx, impl);
packer.pack_constants();
ASSERT_EQ(ctx->cells.size(), 2LU);
packer.remove_constants();
ASSERT_EQ(ctx->cells.size(), 0LU);
}

View File

@ -0,0 +1,32 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include <vector>
#include "gtest/gtest.h"
#include "log.h"
USING_NEXTPNR_NAMESPACE
int main(int argc, char **argv)
{
// TODO: Remove for delivery, useful while checking tests
log_streams.push_back(std::make_pair(&std::cerr, LogLevel::LOG_MSG));
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@ -0,0 +1,210 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "testing.h"
#include <vector>
#include "command.h"
#include "uarch/gatemate/pack.h"
#define HIMBAECHEL_CONSTIDS "uarch/gatemate/constids.inc"
#include "himbaechel_constids.h"
NEXTPNR_NAMESPACE_BEGIN
void GateMateTest::SetUp()
{
init_share_dirname();
chipArgs.device = "CCGM1A1";
ctx = new Context(chipArgs);
ctx->uarch->init(ctx);
ctx->late_init();
impl = (GateMateImpl *)(ctx->uarch.get());
}
void GateMateTest::TearDown() { delete ctx; }
CellInfo *GateMateTest::create_cell_ptr(IdString type, std::string name)
{
CellInfo *cell = ctx->createCell(ctx->id(name), type);
auto add_port = [&](const IdString id, PortType dir) {
cell->ports[id].name = id;
cell->ports[id].type = dir;
};
switch (type.index) {
case id_CC_IBUF.index:
add_port(id_I, PORT_IN);
add_port(id_Y, PORT_OUT);
break;
case id_CC_OBUF.index:
add_port(id_A, PORT_IN);
add_port(id_O, PORT_OUT);
break;
case id_CC_TOBUF.index:
add_port(id_A, PORT_IN);
add_port(id_T, PORT_IN);
add_port(id_O, PORT_OUT);
break;
case id_CC_IOBUF.index:
add_port(id_A, PORT_IN);
add_port(id_T, PORT_IN);
add_port(id_Y, PORT_OUT);
add_port(id_IO, PORT_INOUT);
break;
case id_CC_LVDS_IBUF.index:
add_port(id_I_P, PORT_IN);
add_port(id_I_N, PORT_IN);
add_port(id_Y, PORT_OUT);
break;
case id_CC_LVDS_OBUF.index:
add_port(id_A, PORT_IN);
add_port(id_O_P, PORT_OUT);
add_port(id_O_N, PORT_OUT);
break;
case id_CC_LVDS_TOBUF.index:
add_port(id_A, PORT_IN);
add_port(id_T, PORT_IN);
add_port(id_O_P, PORT_OUT);
add_port(id_O_N, PORT_OUT);
break;
case id_CC_LVDS_IOBUF.index:
add_port(id_A, PORT_IN);
add_port(id_T, PORT_IN);
add_port(id_Y, PORT_OUT);
add_port(id_IO_P, PORT_INOUT);
add_port(id_IO_N, PORT_INOUT);
break;
case id_CC_IDDR.index:
add_port(id_D, PORT_IN);
add_port(id_CLK, PORT_IN);
add_port(id_Q0, PORT_OUT);
add_port(id_Q1, PORT_OUT);
break;
case id_CC_ODDR.index:
add_port(id_D0, PORT_IN);
add_port(id_D1, PORT_IN);
add_port(id_CLK, PORT_IN);
add_port(id_DDR, PORT_IN);
add_port(id_Q, PORT_OUT);
break;
case id_CC_DFF.index:
add_port(id_D, PORT_IN);
add_port(id_CLK, PORT_IN);
add_port(id_EN, PORT_IN);
add_port(id_SR, PORT_IN);
add_port(id_Q, PORT_OUT);
break;
case id_CC_DLT.index:
add_port(id_D, PORT_IN);
add_port(id_G, PORT_IN);
add_port(id_SR, PORT_IN);
add_port(id_Q, PORT_OUT);
break;
case id_CC_L2T4.index:
add_port(id_I0, PORT_IN);
add_port(id_I1, PORT_IN);
add_port(id_I2, PORT_IN);
add_port(id_I3, PORT_IN);
add_port(id_O, PORT_OUT);
break;
case id_CC_L2T5.index:
add_port(id_I0, PORT_IN);
add_port(id_I1, PORT_IN);
add_port(id_I2, PORT_IN);
add_port(id_I3, PORT_IN);
add_port(id_I4, PORT_IN);
add_port(id_O, PORT_OUT);
break;
case id_CC_LUT1.index:
add_port(id_I0, PORT_IN);
add_port(id_O, PORT_OUT);
break;
case id_CC_LUT2.index:
add_port(id_I0, PORT_IN);
add_port(id_I1, PORT_IN);
add_port(id_O, PORT_OUT);
break;
case id_CC_MX2.index:
add_port(id_D0, PORT_IN);
add_port(id_D1, PORT_IN);
add_port(id_S0, PORT_IN);
add_port(id_Y, PORT_OUT);
break;
case id_CC_MX4.index:
add_port(id_D0, PORT_IN);
add_port(id_D1, PORT_IN);
add_port(id_D2, PORT_IN);
add_port(id_D3, PORT_IN);
add_port(id_S0, PORT_IN);
add_port(id_S1, PORT_IN);
add_port(id_Y, PORT_OUT);
break;
case id_CC_ADDF.index:
add_port(id_A, PORT_IN);
add_port(id_B, PORT_IN);
add_port(id_CI, PORT_IN);
add_port(id_CO, PORT_OUT);
add_port(id_S, PORT_OUT);
break;
case id_CC_BUFG.index:
add_port(id_I, PORT_IN);
add_port(id_O, PORT_OUT);
break;
case id_CC_USR_RSTN.index:
add_port(id_USR_RSTN, PORT_OUT);
break;
case id_CC_PLL_ADV.index:
add_port(id_USR_SEL_A_B, PORT_IN);
[[fallthrough]];
case id_CC_PLL.index:
add_port(id_CLK_REF, PORT_IN);
add_port(id_USR_CLK_REF, PORT_IN);
add_port(id_CLK_FEEDBACK, PORT_IN);
add_port(id_USR_LOCKED_STDY_RST, PORT_IN);
add_port(id_USR_PLL_LOCKED_STDY, PORT_OUT);
add_port(id_USR_PLL_LOCKED, PORT_OUT);
add_port(id_CLK0, PORT_OUT);
add_port(id_CLK90, PORT_OUT);
add_port(id_CLK180, PORT_OUT);
add_port(id_CLK270, PORT_OUT);
add_port(id_CLK_REF_OUT, PORT_OUT);
break;
case id_CC_CFG_CTRL.index:
for (int i = 0; i < 8; i++)
add_port(ctx->idf("DATA[%d]", i), PORT_IN);
add_port(id_CLK, PORT_IN);
add_port(id_EN, PORT_IN);
add_port(id_RECFG, PORT_IN);
add_port(id_VALID, PORT_IN);
break;
default:
log_error("Trying to create unknown cell type %s\n", type.c_str(ctx));
break;
}
return cell;
}
void GateMateTest::direct_connect(CellInfo *o_cell, IdString o_port, CellInfo *i_cell, IdString i_port)
{
NetInfo *net = ctx->createNet(ctx->idf("%s_%s", o_cell->name.c_str(ctx), o_port.c_str(ctx)));
o_cell->connectPort(o_port, net);
i_cell->connectPort(i_port, net);
}
NEXTPNR_NAMESPACE_END

View File

@ -0,0 +1,45 @@
/*
* nextpnr -- Next Generation Place and Route
*
* Copyright (C) 2024 The Project Peppercorn Authors.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef GATEMATE_TESTING_H
#define GATEMATE_TESTING_H
#include "gtest/gtest.h"
#include "nextpnr.h"
#include "uarch/gatemate/gatemate.h"
NEXTPNR_NAMESPACE_BEGIN
class GateMateTest : public ::testing::Test
{
protected:
virtual void SetUp() override;
virtual void TearDown() override;
CellInfo *create_cell_ptr(IdString type, std::string name);
void direct_connect(CellInfo *o_cell, IdString o_port, CellInfo *i_cell, IdString i_port);
ArchArgs chipArgs;
Context *ctx;
GateMateImpl *impl;
};
NEXTPNR_NAMESPACE_END
#endif