Programming Guide

So, you've downloaded the supersmall soft processor, you've tested it to make sure it works, you've connected it onto your hardware, and now you want to run your own code on it. Fortunately, using the same framework that was used to compile and run the tests, it's not that hard.


You're going to need a MIPS compilation toolchain (compiler, assembler, linker). One is available for download from the download page, but feel free to provide your own.

The easy way

If you're using the GNU toolchain, you can actually simply piggyback on the scripts already used to compile the test cases, and use those to jump-start your programming. Here's probably the easiest way to handle it:

  1. Make a directory in the applications directory to store your program in.

    mkdir applications/mycode

  2. Put the code you plan on putting onto the supersmall into it, and in addition, copy the Makefile from one of the other applications into your new directory.

    cp applications/des/Makefile applications/mycode

  3. Modify the Makefile in your mycode directory, changing the TARGET, SRC, and (if you're not compiling C code) OBJS constants as needed.

  4. Run make in your mycode directory.


  5. If everything worked as planned, you'll now have two files, instr.mif and data.mif. Copy these to the supersmall directory. I've provided a small script in the testing directory for this purpose, which also converts the .mif files to the .hex files needed by SOPC builder using Quartus II's mif2hex utility.

    testing/ mycode

  6. Recompile your project, and watch your code execute on the supersmall processor.

    cd debugquartus; make mif; make program

    The other advantage of this approach is that you can use the same simulate and test scripts you're already used to from testing the supersmall to test your new code.

The hard way

The advantage of the easy way is that it's easy, and that it integrates well with the currently existing supersmall test framework. Unfortunately, if you're doing something substantially different, such as using a different toolchain, you're going to have to know what's actually going on under the hood, since the scripts won't work properly for you, and you're going to have to make custom modifications. So, the hard way, rather than being a "copy-and-paste this code and it'll work" tutorial, will instead take you step by step through the process the scripts I ran above would have automated.

  1. Compile your code to MIPS-I object code, making sure that you don't include any of the unimplemented instructions. In particular, you're probably going to have to link in a floating-point emulation library (such as SoftFloat) if you do any floating point operations. As well, since using SoftFloat will change the Application Binary Interface (ABI) of all your libraries, you're going to have to also ensure that any libraries you link in are also compiled using the same floating-point emulation library.

    Fortunately, if you're using gcc, it does become slightly easier to compile code with floating-point emulation, since all you have to do is use the -msoft-float flag. Unfortunately, you still need to ensure any libraries you link in are compiled using soft-float.

    To compile the final program, there are additional requirements: if you've looked at the test file scripts, you'll have noticed that they link in one file every time: start.o, whose source file (start.S) is found in the testing/tools directory. This file provides some basic bootstrapping code, and you'll either need to link it in with your program, or provide your own.

    As well, you may have noticed that the Makefile for exceptiontest contains an additional line: "EXCEPTION= ../../tools/exception.o", which links in a custom exception handler (also found in the testing/tools directory). Since exception handlers tend to be very application-specific, you'll probably want to write your own, but feel free to take a look at the code in testing/tools/exception.S as an example. If you do copy chunks from it though, make sure to remove the parts that write to memory. These are used as signals for exceptiontest, but could cause subtle bugs in your own programs.

    The linker script provided to link these files in is contained in testing/tools/lsi.ld. The file is only a very basically modified version of the GNU ld default (don't be scared by all the extra directives: they're usually irrelevant), and if you need to modify it, or are using your own linker, there's only two important rules to remember:

    • Program execution starts at the address 0x0400_0000
    • The exception handler starts at the address 0x0400_0180

    Yes, this deviates slightly from the MIPS spec, but it makes the processor smaller, so it's justifiable. Remember, unless you're using SOPC builder, the processor by default doesn't even look at the top 16 bits of the address anyway, so if you're already configuring things to run from 0xbfc0_0000 (the MIPS default), it shouldn't cause a problem. If it does, simply change the START_ADDR and EXCEPTION_ADDR constants inside supersmall/constants.v to whatever you like, although be aware that it might cost you a few ALUTs. If you're using SOPC builder, the START_ADDR and EXCEPTION_ADDR constants are also modifiable using the GUI interface.

    One issue to watch out for, however, is if your startup code overlaps with your exceptions code. Make sure to put in some sort of branch to jump past the 0x0400_0180 barrier where exception code starts, or your exception handler won't work properly. Even if you're planning on writing your own startup code and exception handler, you might want to take a look at start.S and exception.S. In particular, notice how they use the special ".section .start" and ".section .exceptions" directives to be linked into the proper locations. The lsi.ld linker script makes sure that any normal .text code will be linked _after_ the .exceptions section.

  2. Now that you actually have the object code you want to put on the soft processor, it has to get separated into the instr.mif and data.mif files, which Quartus uses to preload memory values. This process is done in two steps by the Makefile: dumping the object code to ascii in what it calls a ".raw" file, and then using raw2mif, a custom C program contained in the testing/tools directory, to parse that raw output into a mif file. Even if you're not using the GNU toolchain, I recommend installing the GCC objdump program, simply so that you can produce the same output and won't have to change raw2mif. You can feel free to roll your own solution, but it's probably easier and faster not to.

    mips-mips-objdump --full-content myprog > myprog.raw
    testing/tools/raw2mif -width=32 -depth=16384 myprog.raw

  3. Now, instr.mif and data.mif will work fine for a vanilla supersmall installation, but if you're doing anything serious with the supersmall, you're probably using SOPC builder to connect it to your design, and SOPC Builder currently only supports using .hex files for memory initialization. Fortunately, Quartus II includes a handy mif2hex program that deals with that problem for us.

    mif2hex instr.mif instr.hex
    mif2hex data.mif data.hex

  4. That's it! Now that the memory initialization files are made, all you have to do is re-integrate them into your project (probably using Quartus' "update memory initialization files" command), and reprogram the FPGA with your new application. The supersmall will begin executing the commands in instr.mif as soon as it comes online.