Build System
Blimp’s build system is written in CMake. This specifically involves the following files:
CMakeLists.txt: The main build system, defining the majority of targets
app/:
CMakeLists.txt: Add RISCV targets for all programs (native targets are added at the top-level)
cmake/rv32.cmake: Discover and set up the RISCV compiler
<program>/CMakeLists.txt: Define the files used in the program
cmake/:
vlint.cmake: Defines a functionvlintfor creating a target that lints Verilog source(s)
vdeps.cmake: Defines a functionvdepsfor finding all dependencies of a Verilog file based on'includestatements, so that the CMake target can be rebuilt when needed. Dependencies are memoized to expedite build time (something like a 20x speedup during configuration)
CMake*.cmake: Files to support Verilog compilation as a first-class target in CMake
hw/top/:
test/tests.cmake: Define the tests associated with each Blimp version (including the FL processor)
sim/sims.cmake: Define the possible simulators to make
tools/CMakeLists.txt: Define our tool binaries
The build system is usually built in the following fashion from the top-level directory:
mkdir build
cd build
cmake ..
In addition, user-defines can be specified on the command line with the
cmake command; the two specific for Blimp are:
-DTRACE=1(to add tracing support for Verilator simulators)
-DCMAKE_Verilog_COMPILER_ID=(Verilator|VCS)(to select a Verilog compiler)
Overall Build System
The top-level CMake file is what’s invoked to create the build system. It handles the creation of all targets (with the exception of cross-compiled programs), using variables defined deeper in the hierarchy when necessary.
Below is a summary of all the targets that the build system currently supports:
For each hardware test file
<test>.v, there exists a target<test>to build the binary to run that test
This includes those defined in
tests.cmakeThe
listcommand lists all of these testsThe
checkcommand runs all of the hardware tests (this takes a while!)
check-flruns all of the FL processor model tests
check-v<version>runs all of the tests for theBlimpV<version>processor modelFor each program in the
app/subdirectory (named<program>), there exists
A target
app-<program>-nativeto build the native binary<program>-nativein anapp/directory in the build directoryA target
app-<program>to build the RISCV binary<program>in anapp/directory in the build directoryThe
fl-simtarget to build the FL processor simulatorFor each simulator
BlimpV<N>_sim.vinsims.cmake, there exists a targetv<N>-simto build the simulator for that version of BlimpFor each tool
<tool>.cpp, there exists a target<tool>to make the binary for that tool. Currently, this only includesrvelfdump
Verilog Language Support
One of the main goals of the build system portion of Blimp was to
elegantly support compilation with C/C++ and Verilog; this meant having
support in CMake (chosen for its flexibility and support for C/C++) for
compiling Verilog; specifically, having support for Verilog as a language
(i.e. no hacky add_custom_command to compile a file, but rather
just specifying the file to add_executable, with support for other
related commands like target_compile_options).
A good description of how to support this was found on StackOverflow, which prescribed the following four files:
CMakeDetermineVerilogCompiler.cmake: Commands to identify the desired compiler and set associated variables accordingly. This process currently supports Verilator, VCS, and Iverilog (with the latter not able to be used by Blimp due to advanced SystemVerilog usage), with the compiler selection defaulting to Verilator and switched by definingCMAKE_Verilog_COMPILER_IDat configuration time (i.e.cmake <path-to-root> -DCMAKE_Verilog_COMPILER_ID=VCS). Later optimizations might instead detect a compiler based on availability when not specified
CMakeVerilogCompiler.cmake.in: A template for setting CMake variables to identify our compiler. This is templated for a given configuration byCMakeDetermineVerilogCompiler.cmake, such that the compiler doesn’t have to be re-discovered if the build system needs to be reconfigured
CMakeTestVerilogCompiler.cmake: Any testing that needs to be done for the compiler to determine adequate support. Right now, we assume that if we can find it, it works
CMakeVerilogInformation.cmake: The meat of the setup. This is where (based on our compiler) we set two key variables:
CMAKE_Verilog_COMPILE_OBJECT: The template for a command to turn a Verilog file into an object file
CMAKE_Verilog_LINK_EXECUTABLE: The template for a command to link multiple Verilog object files into an executable. In the case that multiple languages are used,CMAKE_Verilog_LINKER_PREFERENCEis used to determine the linker usedEach compiler has some nuance with their setup:
Verilator both “verilates” the Verilog file into C++, compiles the many resulting files, then uses the default CMake linker to link the resulting object files into one, so that it looks as though one Verilog file turned into one object file
Both VCS and Iverilog don’t produce intermediate binaries. To support the pattern that CMake has given for compilation, their
COMPILE_OBJECTcommands simply preprocess the Verilog into an “object”, then actually compile the binary (along with any other objects, such as C++ libraries) during theLINK_EXECUTABLEstep. To ensure that our “linker” is run, the linker preference is set very high inCMakeDetermineVerilogCompiler.cmake
With all of this, we can add the cmake/ directory (where these files
are stored) to our CMake module path for language discovery, and then
call project as normal with our new language, able to handle .v
and .sv files:
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
project(
BLIMP
VERSION 1.0
DESCRIPTION "BLIMP: BRG's Luculently-Interfaced Modular Processor"
LANGUAGES Verilog C CXX ASM
)
RISCV Cross-Compilation
Most build systems require knowledge of the target architecture at configuration time, and are unable to switch once configured; if you want a different architecture, you have to re-configure the build system. However, Blimp’s build system allows programs to be compiled for both the native architecture and RISCV from one configuration.
CMake doesn’t support this behaviour by default; the CMAKE_C_COMPILER
and CMAKE_CXX_COMPILER hold the compilers for compiling C and C++,
respectively, and are stored per project. However, CMake does allow
you to include other CMake files, which may have their own targets
with their own compilers. This allowed the build system to support
multiple compilers by defining targets in multiple places:
In
app/CMakeLists.txt, therv32.cmakefile is included to use the RISCV compiler. Here, we define the cross-compile targets for RISCV (i.e.app-<program>). However, we also set the variablePROGSto a list of the programs, as well as settingapp-<program>-filesto the files needed to compile<program>. All of these are exported to the parent scopeIn the top-level
CMakeLists.txtfile (which uses the native compilers), we get access to the exported files, and can appropriately defineapp-<program>-nativetargets for each of them to get a native program target as well
Adding New Targets
For users looking to extend Blimp, the following sections detail the places in the build system that you need to modify in order to make additions:
Adding a Hardware Unit Test
After adding the file in the appropriate location, add the path relative to the top of the repo to the
V_TEST_FILESdefinition inCMakeLists.txt
Adding a Hardware Top-Level Test
(This should likely be a suite of test cases in hw/top/test/test_cases)
If the test cases is semantically grouped with others, include it in the corresponding test file for the relevant processor versions (ex.
hw/top/test/BlimpVN_test/BlimpVN_<test_type>_test.v)If not, create a new test file for it for every relevant processor version as
hw/top/test/BlimpVN_test/BlimpVN_<test_type>_test.v. Modeling off those that exist already, create a test suite module that instantiates the test harness for a particular parametrization, then includes the tests. Create a top-level module that instantiates and runs multiple parametrizations of the test suite. Finally, after doing the above for each relevant version, add it to those versions’ tests inhw/top/test/tests.cmake
Adding a New Blimp Version
This is a relatively large ordeal, but is likely uncommon and still not too bad:
Define the new processor version in
hw/top, similar to those that exist (and with the same naming,BlimpV<version>.v)In
hw/top/test, define a new test harnessBlimpV<version>TestHarness.vsimilar to those that exist. It should instantiate the processor with connected memory, and provide the following functions:
asmto store an assembly instruction at a given address
check_traceto use anInstTraceSubto check the result of a specific instruction
check_tracesto continuously check the results from the processor against the FL modelIn
hw/top/test, define a new subdirectoryBlimpV<version>_testthat contains main test files, each of which instantiation the harness and include tests to create a test suite, then instantiate that test suite multiple times with different processor parametrizations.In
hw/top/test/tests.cmake, include the new processor’s name in the definition ofBLIMP_VERSIONS. Additionally, define a new variableBlimpV<version>_TESTSthat lists the tests inhw/top/test/BlimpV<version>_testto run in order to test that processorIf relevant (i.e. the processor can simulate code), go to
hw/top/simand define a new fileBlimpV<version>_sim.v. This should instantiate the processor, connect the memory, and provide a functioninit_memto initialize the memory at a given address with data. Modifysims.cmaketo include the name of your new simulation file
Adding a Program
In the app/ subdirectory:
Create a new subdirectory with the name of your program. In this directory, create all of the files for your program, then create a
CMakeLists.txtfile with the following variable definitions in the parent scope:
APP_FILESare all the programs in your directory (i.e. all that include amainfunction). Each<program>.cppwill result in a target<program>, so make sure it’s globally unique.
SRC_FILESare all of the supporting source codeAll of the files in
utilsare implicitly linked, and should be included relative toapp/In
app/CMakeLists.txt, add the name of your new subdirectory in the definition forAPP_SUBDIRS
Adding a Tool
This should go in the tools/ subdirectory, and simply requires the
modification of the TOOL_FILES definition in tools/CMakeLists.txt.
The code in both the asm/ and fl/ folders are implicitly linked
(see the definitions of ASM_FILES and FL_PROC_FILES in the top-level
CMakeLists.txt), and necessary headers should be included relative to
the top of the repository