This file describes the contents of the memory intialization file and how to
use it for various vendor-specific tools.
Format
Each line of the file consists of a hex address into which the values are to
be stored and the corresponding value. For SSBCC.9x8 these are 9-bit
values.
The format of each line is "@%04X %03X" where the 4-digit value is
the hex memory address and the 3-digit hex value is the 9-bit memory
value.
Xilinx data2mem
data2mem is a tool for modifying the block ram initialization
contents of bitstreams. Using this tool allows the micro controller assembly
code to be modified in the bitstream without rerunning the entire build
process.
The following illustrates how to use data2mem using
ISE 14.5:
- Create a BMM file named "uc.bmm" for inclusion in the
build process:
The file should look like the following. The text
"top_inst/uc_inst" needs to be modified to point to your
instantiation of the micro controller. Sometimes
"uc_inst/Mram_s_opcodeMemory" becomes
"uc_inst_Mram_s_opcodeMemory"
ADDRESS_SPACE uc RAMB18 WORD_ADDRESSING [0x0:0x7FF]
BUS_BLOCK
top_inst/uc_inst/Mram_s_opcodeMemory [0:8];
END_BUS_BLOCK;
END_ADDRESS_SPACE;
The following command can be used to verify the syntax of this BMM
file:
data2mem -bm uc_bmm
WARNING: Using "ARCHITECTURE 4096" on a Spartan-6 build
produced two RAMB16's and one RAMB8, not the expected two RAMB18's.
Changing the configuration command to "ARCHITECTURE 2048*2"
produced the desired results. The following BMM extracted the two
RAMB18 locations with the desired memory mapping:
ADDRESS_SPACE uc RAMB18 WORD_ADDRESSING [0x0:0xFFF]
BUS_BLOCK
top_inst/uc_inst_Mram_s_opcodeMemory_0 [0:8];
END_BUS_BLOCK;
BUS_BLOCK
top_inst/uc_inst_Mram_s_opcodeMemory_1 [0:8];
END_BUS_BLOCK;
END_ADDRESS_SPACE;
Note: For a Spartan-3A the bit indices [0:8] may need to be reversed.
- Add this file to the build process.
For a command-line build this is done by adding "-bm uc.bmm"
to the argument list for ngdbuild.
When bitgen is run it will create a file named "uc_bd.bmm
which will include the memory block address required to run
data2mem.
- Perform the build and ensure that uc_bd.bmm has the address for
the memory block.
- Run data2mem as follows, where "orig.bit" is the
assumed name for the original bitstream generated by Xilinx' tools:
data2mem -bm uc_bd.bmm -bt orig.bit -bd uc.mem -o b new.bit;
- Compare the original bitstream to the modified bitstream as follows to
ensure this process worked.
data2mem -bm uc.bmm -bt orig.bit -d > orig.dump;
data2mem -bm uc.bmm -bt new.bit -d > new.dump;
diff orig.dump new.dump | less;
The only differences other than file names and dates and such should be the
initialization values for the memory block.
You can validate this process by using the original memory initialization
file, in which case the above differences should be limited to the file
name, data, etc., but not the memory contents.
If you didn't include the BMM file in the build process you can use
fpga_editor to get the memory names and memory locations. The
command to invoke it is:
fpga_editor -r file.ncd file.pcf
Then, under "Name Filter" type "*opcodeMemory*" and hit
the ENTER key.
Xilinx Vivado
As of this writing, Xilinx' Vivado does not have clean non-SDK support for
generating the files required to modify the processor instruction memory.
However, the TCL scripting language can be used for the following work-around
to this problem:
- Determine the name of the memory.
The following commands lists the names of all the block rams. This
obviously needs to be done after place and route.
join [get_cells -hierarchical -filter { LOC =~ "RAMB*" }] "\n";
or
join [filter [get_cells -hierarchical] { BEL =~ "RAMB*" }] "\n"
Alternatively, the following command lists the names of the block rams, the
type of the block ram, and their locations:
foreach a [filter [get_cells -hierarchical] { BEL =~ "RAMB*" }] {
puts "$a [lindex [report_property -return_string $a BEL] 7] [lindex
[report_property -return_string $a LOC] 7]"; }
Any of these can be included in the build script or can be cut and pasted
into the TCL console in the GUI after place and route.
Note: The "list_property_value" seems to be more natural to use
than the "[lindex ..." commands, but can only be used for
enumerated types, i.e., not for BEL and LOC
properties.
- Once you've identified the name(s) of the memories, add the following
command to the build script or use it on a checkpoint. Here,
uc/inst/s_PC_reg_rep was the single memory in the micro controller.
foreach memName [list "uc/inst/s_PC_reg_rep"] {
set memBel [lindex [report_property -return_string [get_cells $memName] BEL] 7];
set memLoc [lindex [report_property -return_string [get_cells $memName] LOC] 7];
puts "MYBMMINFO: $memName $memBel $memLoc]";
}
This should add lines starting with "MYBMMINFO:" to the Vivado log
file with each memory name, type, and location.
Note: If the processor uses more than one block ram, simply append the name
to the "list" in this TCL script.
- Use the following gawk script or similar to generate a
BMM file from the "MYBMMINFO:" lines. Here, the
"vivado.log" is the Vivado log file and "build-bmm is the
name of this file.
#!/bin/bash
#
# Generate a BMM file for the micro controller from the MYBMMINFO lines in the
# Vivado log file.
#
# Usage: ./build-bmm
gawk -- '
BEGIN {
nMemories = 0;
memName[nMemories++] = "uc/inst/s_PC_reg_rep";
}
/^MYBMMINFO:/ { bel[$2] = $3; loc[$2] = $4; }
END {
for (ix=0; ix<nMemories; ++ix)
if (!(memName[ix] in bel)) {
printf("FATAL ERROR: MYBMMINFO record not found for \"%s\"\n", memName) > "/dev/stderr";
exit 1;
}
memType = "";
for (ix=0; ix<nMemories; ++ix) {
split(loc[memName[ix]],splitLoc,"_");
if (memType == "") {
memType = splitLoc[1];
if (memType = "RAMB18") L = 2048;
if (memType = "RAMB36") L = 4096;
L *= nMemories;
printf("ADDRESS_SPACE uc %s WORD_ADDRESSING [0x0:0x%x]\n",memType,L-1);
}
else if (splitLoc[1] != memType) {
printf("Inconsistent memory types: %s is %s instead of %s\n",memName[ix],splitLoc[1],memType) > "/dev/stderr";
exit 1;
}
printf(" BUS_BLOCK\n");
printf(" %s [8:0] PLACED = %s;\n",memName[ix],splitLoc[2]);
printf(" END_BUS_BLOCK;\n");
}
printf("END_ADDRESS_SPACE;\n");
}
' vivado.log > build_uc.bmm
- Update the contents of the bitstream file as follows. Here,
build.bit is the original bitstream and build_uc.bit is
the bitstream updated with the new micro controller instructions.
data2mem -bm build_uc.bmm -bt build.bit -bd uc/uc.mem -o b
build_uc.bit
Alternatively, use something like the following as an "update_uc"
script file:
#!/bin/bash
#
# Update the micro controller instruction memory in the BIT file.
#
# Usage: ./update-uc
if test ! -f build_uc.bmm -o vivado.log -nt build_uc.bmm; then
./build-bmm || { echo "build-bmm failed" > /dev/stderr; exit 1; }
fi
( cd uc; ssbcc -q --define-clog2 uc.9x8 ) \
|| { echo "ssbcc failed" > /dev/stderr; exit 1; }
data2mem -bm build_uc.bmm -bt build.bit -bd uc/uc.mem -o b build_uc.bit \
|| { echo "data2mem failed" > /dev/stderr; exit 1; }
Note: If you want to use these procedures to identify the memories after
you've run Vivado, you must include a "write_checkpoint" command in
your TCL script. For example, include the following command after
"write_bitstream -force build.bit"
write_checkpoint -force build;
and then use the following command in a subsequent Vivado session to open the
checkpoint:
read_checkpoint build;
Once these are done you can examine the memory names and so forth.