Working BRAM

This commit is contained in:
Miodrag Milanovic 2023-04-10 14:27:36 +02:00 committed by myrtle
parent 3a7770dca2
commit 8c38e7ba61
2 changed files with 250 additions and 0 deletions

View File

@ -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);
} }

View File

@ -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";
} }
} }