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:

  1. 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.

  2. 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.

  3. Perform the build and ensure that uc_bd.bmm has the address for the memory block.

  4. 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;

  5. 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:
  1. 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.

  2. 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.

  3. 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

  4. 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.