Working BRAM
This commit is contained in:
parent
3a7770dca2
commit
8c38e7ba61
@ -154,6 +154,53 @@ struct MachXO2Bitgen
|
|||||||
return bv;
|
return bv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline int chtohex(char c)
|
||||||
|
{
|
||||||
|
static const std::string hex = "0123456789ABCDEF";
|
||||||
|
return hex.find(std::toupper(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<bool> parse_init_str(const Property &p, int length, const char *cellname)
|
||||||
|
{
|
||||||
|
// Parse a string that may be binary or hex
|
||||||
|
std::vector<bool> result;
|
||||||
|
result.resize(length, false);
|
||||||
|
if (p.is_string) {
|
||||||
|
std::string str = p.as_string();
|
||||||
|
NPNR_ASSERT(str.substr(0, 2) == "0x");
|
||||||
|
// Lattice style hex string
|
||||||
|
if (int(str.length()) > (2 + ((length + 3) / 4)))
|
||||||
|
log_error("hex string value too long, expected up to %d chars and found %d.\n",
|
||||||
|
(2 + ((length + 3) / 4)), int(str.length()));
|
||||||
|
for (int i = 0; i < int(str.length()) - 2; i++) {
|
||||||
|
char c = str.at((str.size() - i) - 1);
|
||||||
|
int nibble = chtohex(c);
|
||||||
|
if (nibble == (int)std::string::npos)
|
||||||
|
log_error("hex string has invalid char '%c' at position %d.\n", c, i);
|
||||||
|
result.at(i * 4) = nibble & 0x1;
|
||||||
|
if (i * 4 + 1 < length)
|
||||||
|
result.at(i * 4 + 1) = nibble & 0x2;
|
||||||
|
if (i * 4 + 2 < length)
|
||||||
|
result.at(i * 4 + 2) = nibble & 0x4;
|
||||||
|
if (i * 4 + 3 < length)
|
||||||
|
result.at(i * 4 + 3) = nibble & 0x8;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = p.as_bits();
|
||||||
|
result.resize(length, false);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint16_t bit_reverse(uint16_t x, int size)
|
||||||
|
{
|
||||||
|
uint16_t y = 0;
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
if (x & (1 << i))
|
||||||
|
y |= (1 << ((size - 1) - i));
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the PIC tile corresponding to a PIO bel
|
// Get the PIC tile corresponding to a PIO bel
|
||||||
std::string get_pic_tile(BelId bel)
|
std::string get_pic_tile(BelId bel)
|
||||||
{
|
{
|
||||||
@ -184,6 +231,21 @@ struct MachXO2Bitgen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the list of tiles corresponding to a blockram
|
||||||
|
std::vector<std::string> get_bram_tiles(BelId bel)
|
||||||
|
{
|
||||||
|
std::vector<std::string> tiles;
|
||||||
|
Loc loc = ctx->getBelLocation(bel);
|
||||||
|
|
||||||
|
static const std::set<std::string> ebr0 = {"EBR0", "EBR0_END", "EBR0_10K", "EBR0_END_10K"};
|
||||||
|
static const std::set<std::string> ebr1 = {"EBR1", "EBR1_10K"};
|
||||||
|
static const std::set<std::string> ebr2 = {"EBR2", "EBR2_END", "EBR2_10K", "EBR2_END_10K"};
|
||||||
|
tiles.push_back(ctx->get_tile_by_type_loc(loc.y, loc.x, ebr0));
|
||||||
|
tiles.push_back(ctx->get_tile_by_type_loc(loc.y, loc.x+1, ebr1));
|
||||||
|
tiles.push_back(ctx->get_tile_by_type_loc(loc.y, loc.x+2, ebr2));
|
||||||
|
return tiles;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the list of tiles corresponding to a PLL
|
// Get the list of tiles corresponding to a PLL
|
||||||
std::vector<std::string> get_pll_tiles(BelId bel)
|
std::vector<std::string> get_pll_tiles(BelId bel)
|
||||||
{
|
{
|
||||||
@ -363,6 +425,78 @@ struct MachXO2Bitgen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void write_bram(CellInfo *ci)
|
||||||
|
{
|
||||||
|
TileGroup tg;
|
||||||
|
tg.tiles = get_bram_tiles(ci->bel);
|
||||||
|
std::string ebr = "EBR";
|
||||||
|
|
||||||
|
if (ci->ramInfo.is_pdp) {
|
||||||
|
tg.config.add_enum(ebr + ".MODE", "PDPW8KC");
|
||||||
|
tg.config.add_enum(ebr + ".PDPW8KC.DATA_WIDTH_R", intstr_or_default(ci->params, id_DATA_WIDTH_B, "18"));
|
||||||
|
tg.config.add_enum(ebr + ".FIFO8KB.DATA_WIDTH_W", "18"); // default for PDPW8KC
|
||||||
|
} else {
|
||||||
|
tg.config.add_enum(ebr + ".MODE", "DP8KC");
|
||||||
|
tg.config.add_enum(ebr + ".DP8KC.DATA_WIDTH_A", intstr_or_default(ci->params, id_DATA_WIDTH_A, "18"));
|
||||||
|
tg.config.add_enum(ebr + ".DP8KC.DATA_WIDTH_B", intstr_or_default(ci->params, id_DATA_WIDTH_B, "18"));
|
||||||
|
tg.config.add_enum(ebr + ".DP8KC.WRITEMODE_A", str_or_default(ci->params, id_WRITEMODE_A, "NORMAL"));
|
||||||
|
tg.config.add_enum(ebr + ".DP8KC.WRITEMODE_B", str_or_default(ci->params, id_WRITEMODE_B, "NORMAL"));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto csd_a = str_to_bitvector(str_or_default(ci->params, id_CSDECODE_A, "0b000"), 3),
|
||||||
|
csd_b = str_to_bitvector(str_or_default(ci->params, id_CSDECODE_B, "0b000"), 3);
|
||||||
|
|
||||||
|
tg.config.add_enum(ebr + ".REGMODE_A", str_or_default(ci->params, id_REGMODE_A, "NOREG"));
|
||||||
|
tg.config.add_enum(ebr + ".REGMODE_B", str_or_default(ci->params, id_REGMODE_B, "NOREG"));
|
||||||
|
|
||||||
|
tg.config.add_enum(ebr + ".RESETMODE", str_or_default(ci->params, id_RESETMODE, "SYNC"));
|
||||||
|
tg.config.add_enum(ebr + ".ASYNC_RESET_RELEASE", str_or_default(ci->params, id_ASYNC_RESET_RELEASE, "SYNC"));
|
||||||
|
tg.config.add_enum(ebr + ".GSR", str_or_default(ci->params, id_GSR, "DISABLED"));
|
||||||
|
|
||||||
|
tg.config.add_word(ebr + ".WID", int_to_bitvector(int_or_default(ci->attrs, id_WID, 0), 9));
|
||||||
|
|
||||||
|
// Invert CSDECODE bits to emulate inversion muxes on CSA/CSB signals
|
||||||
|
for (auto &port : {std::make_pair("CSA", std::ref(csd_a)), std::make_pair("CSB", std::ref(csd_b))}) {
|
||||||
|
for (int bit = 0; bit < 3; bit++) {
|
||||||
|
std::string sig = port.first + std::to_string(bit);
|
||||||
|
if (str_or_default(ci->params, ctx->id(sig + "MUX"), sig) == "INV")
|
||||||
|
port.second.at(bit) = !port.second.at(bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tg.config.add_enum(ebr + ".RSTAMUX", str_or_default(ci->params, id_RSTAMUX, "RSTA"));
|
||||||
|
tg.config.add_enum(ebr + ".RSTBMUX", str_or_default(ci->params, id_RSTBMUX, "RSTB"));
|
||||||
|
if (!ci->ramInfo.is_pdp) {
|
||||||
|
tg.config.add_enum(ebr + ".WEAMUX", str_or_default(ci->params, id_WEAMUX, "WEA"));
|
||||||
|
tg.config.add_enum(ebr + ".WEBMUX", str_or_default(ci->params, id_WEBMUX, "WEB"));
|
||||||
|
|
||||||
|
}
|
||||||
|
std::reverse(csd_a.begin(), csd_a.end());
|
||||||
|
std::reverse(csd_b.begin(), csd_b.end());
|
||||||
|
|
||||||
|
tg.config.add_word(ebr + ".CSDECODE_A", csd_a);
|
||||||
|
tg.config.add_word(ebr + ".CSDECODE_B", csd_b);
|
||||||
|
|
||||||
|
std::vector<uint16_t> init_data;
|
||||||
|
init_data.resize(1024, 0x0);
|
||||||
|
// INIT_00 .. INIT_1F
|
||||||
|
for (int i = 0; i <= 0x1F; i++) {
|
||||||
|
IdString param = ctx->idf("INITVAL_%02X", i);
|
||||||
|
auto value = parse_init_str(get_or_default(ci->params, param, Property(0)), 320, ci->name.c_str(ctx));
|
||||||
|
for (int j = 0; j < 16; j++) {
|
||||||
|
// INIT parameter consists of 16 18-bit words with 2-bit padding
|
||||||
|
int ofs = 20 * j;
|
||||||
|
for (int k = 0; k < 18; k++) {
|
||||||
|
if (value.at(ofs + k))
|
||||||
|
init_data.at(i * 32 + j * 2 + (k / 9)) |= (1 << (k % 9));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int wid = int_or_default(ci->attrs, id_WID, 0);
|
||||||
|
NPNR_ASSERT(!cc.bram_data.count(wid));
|
||||||
|
cc.bram_data[wid] = init_data;
|
||||||
|
cc.tilegroups.push_back(tg);
|
||||||
|
}
|
||||||
|
|
||||||
void write_pll(CellInfo *ci)
|
void write_pll(CellInfo *ci)
|
||||||
{
|
{
|
||||||
TileGroup tg;
|
TileGroup tg;
|
||||||
@ -543,6 +677,8 @@ struct MachXO2Bitgen
|
|||||||
cc.tiles[ctx->get_tile_by_type("CFG1")].add_enum("OSCH.NOM_FREQ", freq);
|
cc.tiles[ctx->get_tile_by_type("CFG1")].add_enum("OSCH.NOM_FREQ", freq);
|
||||||
} else if (ci->type == id_DCCA) {
|
} else if (ci->type == id_DCCA) {
|
||||||
write_dcc(ci);
|
write_dcc(ci);
|
||||||
|
} else if (ci->type == id_DP8KC) {
|
||||||
|
write_bram(ci);
|
||||||
} else if (ci->type == id_EHXPLLJ) {
|
} else if (ci->type == id_EHXPLLJ) {
|
||||||
write_pll(ci);
|
write_pll(ci);
|
||||||
}
|
}
|
||||||
|
114
machxo2/pack.cc
114
machxo2/pack.cc
@ -1012,6 +1012,107 @@ class MachXO2Packer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pack EBR
|
||||||
|
void pack_ebr()
|
||||||
|
{
|
||||||
|
// Autoincrement WID (starting from 3 seems to match vendor behaviour?)
|
||||||
|
int wid = 3;
|
||||||
|
auto rename_bus = [&](CellInfo *c, const std::string &oldname, const std::string &newname, int width,
|
||||||
|
int oldoffset, int newoffset) {
|
||||||
|
for (int i = 0; i < width; i++)
|
||||||
|
c->renamePort(ctx->id(oldname + std::to_string(i + oldoffset)),
|
||||||
|
ctx->id(newname + std::to_string(i + newoffset)));
|
||||||
|
};
|
||||||
|
auto rename_param = [&](CellInfo *c, const std::string &oldname, const std::string &newname) {
|
||||||
|
IdString o = ctx->id(oldname), n = ctx->id(newname);
|
||||||
|
if (!c->params.count(o))
|
||||||
|
return;
|
||||||
|
c->params[n] = c->params[o];
|
||||||
|
c->params.erase(o);
|
||||||
|
};
|
||||||
|
/*for (auto &cell : ctx->cells) {
|
||||||
|
CellInfo *ci = cell.second.get();
|
||||||
|
// Convert 36-bit PDP RAMs to regular 18-bit DP ones that match the Bel
|
||||||
|
if (ci->type == id_PDPW8KC) {
|
||||||
|
ci->params[id_DATA_WIDTH_A] = 18; // force PDP mode
|
||||||
|
ci->params.erase(id_DATA_WIDTH_W);
|
||||||
|
rename_bus(ci, "BE", "ADA", 4, 0, 0);
|
||||||
|
rename_bus(ci, "ADW", "ADA", 9, 0, 5);
|
||||||
|
rename_bus(ci, "ADR", "ADB", 14, 0, 0);
|
||||||
|
rename_bus(ci, "CSW", "CSA", 3, 0, 0);
|
||||||
|
rename_bus(ci, "CSR", "CSB", 3, 0, 0);
|
||||||
|
rename_bus(ci, "DI", "DIA", 18, 0, 0);
|
||||||
|
rename_bus(ci, "DI", "DIB", 18, 18, 0);
|
||||||
|
rename_bus(ci, "DO", "DOA", 18, 18, 0);
|
||||||
|
rename_bus(ci, "DO", "DOB", 18, 0, 0);
|
||||||
|
ci->renamePort(id_CLKW, id_CLKA);
|
||||||
|
ci->renamePort(id_CLKR, id_CLKB);
|
||||||
|
ci->renamePort(id_CEW, id_CEA);
|
||||||
|
ci->renamePort(id_CER, id_CEB);
|
||||||
|
ci->renamePort(id_OCER, id_OCEB);
|
||||||
|
rename_param(ci, "CLKWMUX", "CLKAMUX");
|
||||||
|
if (str_or_default(ci->params, id_CLKAMUX) == "CLKW")
|
||||||
|
ci->params[id_CLKAMUX] = std::string("CLKA");
|
||||||
|
rename_param(ci, "CLKRMUX", "CLKBMUX");
|
||||||
|
if (str_or_default(ci->params, id_CLKBMUX) == "CLKR")
|
||||||
|
ci->params[id_CLKBMUX] = std::string("CLKB");
|
||||||
|
rename_param(ci, "CSDECODE_W", "CSDECODE_A");
|
||||||
|
rename_param(ci, "CSDECODE_R", "CSDECODE_B");
|
||||||
|
std::string outreg = str_or_default(ci->params, id_REGMODE, "NOREG");
|
||||||
|
ci->params[id_REGMODE_A] = outreg;
|
||||||
|
ci->params[id_REGMODE_B] = outreg;
|
||||||
|
ci->params.erase(id_REGMODE);
|
||||||
|
rename_param(ci, "DATA_WIDTH_R", "DATA_WIDTH_B");
|
||||||
|
if (ci->ports.count(id_RST)) {
|
||||||
|
autocreate_empty_port(ci, id_RSTA);
|
||||||
|
autocreate_empty_port(ci, id_RSTB);
|
||||||
|
NetInfo *rst = ci->ports.at(id_RST).net;
|
||||||
|
ci->connectPort(id_RSTA, rst);
|
||||||
|
ci->connectPort(id_RSTB, rst);
|
||||||
|
ci->disconnectPort(id_RST);
|
||||||
|
ci->ports.erase(id_RST);
|
||||||
|
}
|
||||||
|
ci->type = id_DP8KC;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
for (auto &cell : ctx->cells) {
|
||||||
|
CellInfo *ci = cell.second.get();
|
||||||
|
if (ci->type == id_DP8KC) {
|
||||||
|
// Add ports, even if disconnected, to ensure correct tie-offs
|
||||||
|
for (int i = 0; i < 13; i++) {
|
||||||
|
autocreate_empty_port(ci, ctx->id("ADA" + std::to_string(i)));
|
||||||
|
autocreate_empty_port(ci, ctx->id("ADB" + std::to_string(i)));
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
autocreate_empty_port(ci, ctx->id("DIA" + std::to_string(i)));
|
||||||
|
autocreate_empty_port(ci, ctx->id("DIB" + std::to_string(i)));
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
autocreate_empty_port(ci, ctx->id("CSA" + std::to_string(i)));
|
||||||
|
autocreate_empty_port(ci, ctx->id("CSB" + std::to_string(i)));
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
autocreate_empty_port(ci, ctx->id("CSA" + std::to_string(i)));
|
||||||
|
autocreate_empty_port(ci, ctx->id("CSB" + std::to_string(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
autocreate_empty_port(ci, id_CLKA);
|
||||||
|
autocreate_empty_port(ci, id_CEA);
|
||||||
|
autocreate_empty_port(ci, id_OCEA);
|
||||||
|
autocreate_empty_port(ci, id_WEA);
|
||||||
|
autocreate_empty_port(ci, id_RSTA);
|
||||||
|
|
||||||
|
autocreate_empty_port(ci, id_CLKB);
|
||||||
|
autocreate_empty_port(ci, id_CEB);
|
||||||
|
autocreate_empty_port(ci, id_OCEB);
|
||||||
|
autocreate_empty_port(ci, id_WEB);
|
||||||
|
autocreate_empty_port(ci, id_RSTB);
|
||||||
|
|
||||||
|
ci->attrs[id_WID] = wid++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Preplace PLL
|
// Preplace PLL
|
||||||
void preplace_plls()
|
void preplace_plls()
|
||||||
{
|
{
|
||||||
@ -1408,6 +1509,7 @@ class MachXO2Packer
|
|||||||
print_logic_usage();
|
print_logic_usage();
|
||||||
pack_io();
|
pack_io();
|
||||||
preplace_plls();
|
preplace_plls();
|
||||||
|
pack_ebr();
|
||||||
pack_constants();
|
pack_constants();
|
||||||
pack_dram();
|
pack_dram();
|
||||||
pack_carries();
|
pack_carries();
|
||||||
@ -1510,6 +1612,18 @@ void Arch::assign_arch_info_for_cell(CellInfo *ci)
|
|||||||
ci->ffInfo.clk_sig = get_port_net(ci, id_CLK);
|
ci->ffInfo.clk_sig = get_port_net(ci, id_CLK);
|
||||||
ci->ffInfo.ce_sig = get_port_net(ci, id_CE);
|
ci->ffInfo.ce_sig = get_port_net(ci, id_CE);
|
||||||
ci->ffInfo.lsr_sig = get_port_net(ci, id_LSR);
|
ci->ffInfo.lsr_sig = get_port_net(ci, id_LSR);
|
||||||
|
} else if (ci->type == id_DP8KC) {
|
||||||
|
ci->ramInfo.is_pdp = (int_or_default(ci->params, id_DATA_WIDTH_A, 0) == 18);
|
||||||
|
|
||||||
|
// Output register mode (REGMODE_{A,B}). Valid options are 'NOREG' and 'OUTREG'.
|
||||||
|
std::string regmode_a = str_or_default(ci->params, id_REGMODE_A, "NOREG");
|
||||||
|
if (regmode_a != "NOREG" && regmode_a != "OUTREG")
|
||||||
|
log_error("DP8KC %s has invalid REGMODE_A configuration '%s'\n", ci->name.c_str(this), regmode_a.c_str());
|
||||||
|
std::string regmode_b = str_or_default(ci->params, id_REGMODE_B, "NOREG");
|
||||||
|
if (regmode_b != "NOREG" && regmode_b != "OUTREG")
|
||||||
|
log_error("DP8KC %s has invalid REGMODE_B configuration '%s'\n", ci->name.c_str(this), regmode_b.c_str());
|
||||||
|
ci->ramInfo.is_output_a_registered = regmode_a == "OUTREG";
|
||||||
|
ci->ramInfo.is_output_b_registered = regmode_b == "OUTREG";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user