# Internals and design
## ECP5 notes
The ECP5 family of Lattice FPGAs have SRAM blocks
called "sysMEM memory" in the manufracturer litterature.
These are "18 Kb" (sic) blocks, more properly this should be written
"18 kib" as they are 1024x18 bits, for a total of 18432 bits per block
or 2304 bytes. The -45 devices have 108 of those (total 248832 B) and
the -85 have 208 (479232 B).
These sysMEM primitives are called DP16KD in Diamond as
well as Yosys.
## Yosys notes
The initialization vectors for the RAM blocks can be found in Yosys' JSON
output in the `INITVAL_xx` parameters of the DP16KD nodes, for
`xx` ranging from `00` to `3F` (64 vectors), example:
```
...
"rom.0.0": {
"hide_name": 0,
"type": "DP16KD",
"parameters": {
"ASYNC_RESET_RELEASE": "ASYNC",
"CLKAMUX": "CLKA",
"CLKBMUX": "CLKB",
"CSDECODE_A": "0b000",
"CSDECODE_B": "0b000",
"DATA_WIDTH_A": "00000000000000000000000000010010",
"DATA_WIDTH_B": "00000000000000000000000000010010",
"GSR": "DISABLED",
"INITVAL_00": "00010011100110000001000111111101001110100001011100010010010100100000101010001001001110010011111010110000011000000111101100011000000110101000000101010001111111110011010000111111110100110110001101101101001000111110000010100000100111001011000100011110100110111101000000011000001000000000010101000010100100001101100011000010",
...
```
Each `INITVAL_xx` is a 320-bit string, giving a total of 20480 bits,
which is larger than the 18432-bit capacity, which would warrant 288-bit
strings for 16 18-bit words. Thus there are two extra bits.
Maybe these are used for the Lattice error detection and correction
functionality? See the ECP5 family datasheet, section 2.18.12 "Single
Event Upset Support".
In any case, in Yosys, the initialization code for the DP16KD blocks
is defined in Verilog in the file `techlibs/ecp5/brams_map.v`
More specifically, the bit layout is defined in the `init_slice`
function. We can see that 2 null bits are inserted after every group
of 18 bits.
## NextPNR notes
In the standard compilation workflow, Yosys synthesizes a JSON design
from Verilog files. This JSON file is ingested by NextPNR which
produces a chip configuration file in PrjTrellis' custom text format.
Finally, ecppack can convert that file to SVF for JTAG programming.
The text PrjTrellis configuration file has BRAM initialization sections that
look like this:
```
.bram_init 3
0c2 06c 029 02a 020 00c 1bd 0f4
0b1 04e 00a 11f 16d 1b1 1fd 1a1
1ff 0a8 1a8 0c0 07b 030 1eb 1c9
...
```
The number after `.bram_init` identifies a BRAM within a tile and so
is not necessarily unique within the file.
The syntax is fairly obvious, and the C++ serialization
code, which is under `nextpnr/ecp5/config.cc` looks like this
```
for (const auto &bram : cc.bram_data) {
out << ".bram_init " << bram.first << std::endl;
std::ios_base::fmtflags f(out.flags());
for (size_t i = 0; i < bram.second.size(); i++) {
out << std::setw(3) << std::setfill('0') << std::hex << bram.second.at(i);
if (i % 8 == 7)
out << std::endl;
else
out << " ";
}
out.flags(f);
out << std::endl;
}
```
In Rust this would be something like
```
for (a,ws) in &cc.bram_data {
writeln!(out,".bram_init {}",a)?;
for (i,&w) in ws.iter().enumerate() {
write!(out,"{:03x}",b);
if i % 8 == 7 { writeln!(out)? } else { write!(out," ")? }
}
writeln!(out)?;
}
```
In `bitstream.cc` the function `write_bram` does this:
```
void write_bram(CellInfo *ci)
{
...
std::vector<uint16_t> init_data;
init_data.resize(2048, 0x0);
// INIT_00 .. INIT_3F
for (int i = 0; i <= 0x3F; 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);
}
```
## Mapping of arrays to BRAMs
Yosys can map register arrays in multiple ways.
First of all, they can be mapped to nothing if your arrays are deleted
during an optimization pass. This can happen, for example, if Yosys
figures out that nothing depends on the RAM contents.
Second, they could be mapped to LUTs. This can happen even if the
array is large, depending on the access patterns and the Yosys
options. For example, Yosys might implement your memory as a
collection of registers instead of RAM blocks if your logic performs
concurrent R/W accesses, unless you use a synthesis flag or directive
such as `no_rw_check`.
Third, the implementation of your memory array using BRAMs will depend
on the size and width of the array. Looking at Yosys' JSON output, we
see that an array `rom` seems to have implementation names of the form
`rom.I.J`; specifically `reg [9:0] rom[0:4095]` can be implemented
using two sectors called `rom.0.0` and `rom.0.1`. The order in which
the memory is split into sectors is not obvious.
Fourth, depending on your design and synthesis options, Yosys can
decide to implement your 16-bit RAM using 16 blocks in 1-bit mode, or
using a single block in 18-bit mode.
Finally, the ordering of the data bits of a RAM block seems to be
variable.