In order to create our first core, we will start with some example verilog code. This is a simple SPI slave to GPIO bridge that will be used in the first examples.
Create the following directory structure, and put spi_slave.v
in rtl/verilog
-
spi_slave
-
rtl
-
verilog
-
spi_slave.v
-
-
-
File and directory names for the source code are not important in FuseSoC. The structure described above is just a convention that is quite commonly used
These are all the preparations we need to create our first FuseSoC .core.
In the spi_slave
directory, create a file called spi_slave.core
. On the first line of the file enter CAPI=1
. This will let FuseSoC know that this is a .core file which is using version 1 of the core API (which is currently the only version), which gives information on how to parse the rest of the file. CAPI1 files are standard INI files, which are parsed by Python’s configparser
On the next line, enter [main]
. This starts the main configuration section. Add a single option to the main
section by writing name = ::spi_slave:0
on a new line.
Technically, the name option can be left out in this case, see core naming rules for more information
Create a new fileset section by writing [fileset rtl]
on a new line. The name of the fileset (i.e. rtl
) is not important, but should preferrably describe what kind of files that goes into each fileset. List the source files on a new line by writing files = rtl/verilog/spi_slave.v
. File paths are relative to the core root, which in this case is where the .core
file resides. Also describe the file type of the files in the fileset by adding a new line with file_type = verilogSource
Many older cores have a section called verilog
instead of filesets. This section is deprecated, and information on how to migrate to filesets can be found in the migrations document
The complete .core file should now look like this
CAPI=1 [main]
name = ::spi_slave:0
[fileset rtl] files = rtl/verilog/spi_slave.v file_type=verilogSource
You can now use FuseSoC to get information about your core, and confirm that it has been picked up correctly. Run fusesoc --cores-root=<core path> core-info spi_slave
, where <core path>
is the path to the spi_slave
directory.
FuseSoC should return something like this
CORE INFO Name: ::spi_slave:0 Core root: /home/olof/code/staging/spi_slave == fileset == rtl File sets:
Name : rtl Scope : public Usage : sim/synth Files : rtl/verilog/spi_slave.v verilogSource False
Please verify that Core root
points to the correct directory, in case there is some other core with the same name that is being picked up by the FuseSoC configuration files.
Congratulations, you have now finished writing your first .core file, and anyone who wants to use your code can now add a dependency on your core in their .core file. We will now investigate how dependencies work in FuseSoC and making our core a bit more useful by making the spi_slave core depend on some other cores, and by adding a testbench.
For all options that are introduced in this tutorial, you can see the core API definitions to learn more about them and other options available for core files
In order to convince the world and ourselves that the core actually works, we want to add a testbench to the core and be able to run a simulation. For this we will create two new files. spi_slave_tb is the top-level testbench and spi_bfm is a BFM module to create SPI transactions. These files are put in a new directory called bench
as shown below. Again, the file and directory names are not important. It’s just a convention.
-
spi_slave
-
bench
-
spi_bfm.v
-
spi_slave_tb.v
-
-
rtl
-
verilog
-
spi_slave.v
-
-
-
In order to let FuseSoC know about the two testbench files, they must be added to the .core file. In this case, we will create two new filesets for reasons that will be soon explained. The first one for spi_bfm.v
looks like this:
[fileset bfm] files = bench/spi_bfm.v file_type=verilogSource usage = sim
And the one for spi_slave_tb.v
looks like this:
[fileset tb] files = bench/spi_slave_tb.v file_type=verilogSource scope = private usage = sim
The main difference between these two filesets is the use of the scope
parameter. For the top-level testbench, scope
is defined to private
, instead of its default value public
. Setting it to private
means that if the files in this fileset will only be visible if we run operations (such as simulations) directly on the core. If the core is used as a dependency of another core, the private
filesets will not be seen. As another core might want to use spi_bfm.v
, we make this public, but top-level testbenches are usually only used when directly simulating the core.
The testbench also instantiates a module called vlog_tb_utils
. This isn’t part of the spi_slave
core, but is defined in a core in the FuseSoC base library called vlog_tb_utils
. In order to use this module, we must therefore list vlog_tb_utils
as a dependency of our core. We do this by adding depend = vlog_tb_utils
to our [main]
section.
The next thing to do is to list which simulators that can be used to simulate the core. This is done by setting the simulators
option in the [main]
section to a space-separated list of simulator names. The first one listed, will be used as the default simulator, unless the simulator argument --sim
is added to teh command-line to manually select the simulator
As any value can be specified with the --sim
parameter, regardless if they are in the list of simulators, only the first entry in the list (the default simulator) has any meaning.
The last thing we need to do before running a simulation is to specify the name of the top-level module used for the testbench. Please note that this is not the file name, but the name of the verilog module or VHDL entity to be used. This is done by creating a new section with the following contents:
[simulator] toplevel = spi_slave_tb
The complete .core file should now look like this
CAPI=1 [main]
name = ::spi_slave:0 simulators = icarus depend = vlog_tb_utils
[fileset rtl] files = rtl/verilog/spi_slave.v file_type=verilogSource
[fileset bfm] files = bench/spi_bfm.v file_type=verilogSource usage = sim
[fileset tb] files = bench/spi_slave_tb.v file_type=verilogSource scope = private usage = sim
[simulator] toplevel = spi_slave_tb
The simulation can now be started by executing fusesoc --cores-root=<core path> sim spi_slave
. If everything works as expected, it should print out some information and then finally Test passed!
. Congratulations, you have now run your first simulation with FuseSoC