Writing core files

A core is described in a core description file, or core file.

Core files are written in YAML syntax and follow the FuseSoC’s own CAPI (version 2) schema, which describes the structure of core files (e.g. which keys and values are allowed where). Don’t worry: using FuseSoC neither requires a full understanding of YAML, nor an up-front knowledge of CAPI. However, some key facts about YAML are important.

Things one should know about YAML

  • Whitespace matters (as in Python): indentation is used to group settings together to form a hierarchy. The exact amount of whitespace used for indentation does not matter; typically two or four spaces are used.

  • Think of a YAML file as a hierarchical, typed data structure. There are lists, dictionaries (key/value pairs), integers, strings, etc.

  • YAML syntax provides multiple ways to describe the same structure. It does not matter to FuseSoC which syntax variant is used. For example, a list of items can be written in the following two, semantically identical ways.

    [ item1, item2 ]
    

    is semantically identical to

    - item1
    - item2
    

    The same is true for dictionaries (key/value pairs).

    { key1: value1, key2: value2 }
    

    is semantically identical to

    key1: value1
    key2: value2
    

    In most cases, the longer (second) form is preferred, as it is easier to make changes while keeping the diff easy to read.

For a quick introduction into most of YAML’s features have a look at Learn YAML in Y minutes. The full YAML 1.2 specification is available at yaml.org (it’s not an easy read, though).

An example: the blinky core

The following sections explain how to add FuseSoC support to a hardware project. The code is taken from an example design in the FuseSoC source tree in the tests/userguide/blinky directory.

The design consists of two SystemVerilog files, a testbench, a Xilinx constraint file (with pin mappings for a Nexys Video FPGA board), and finally, the FuseSoC core file.

$ tree tests/userguide/blinky/
tests/userguide/blinky/
├── blinky.core
├── data
│   └── nexys_video.xdc
├── rtl
│   ├── blinky.sv
│   └── macros.svh
└── tb
   └── blinky_tb.sv

3 directories, 5 files

To get started, here’s the full blinky.core file. The following sections will refer back to this example to discuss it in detail.

blinky.core, an exemplary core file
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
CAPI=2:
name: fusesoc:examples:blinky:1.0.0
description: Blinky, a FuseSoC example core

filesets:
  rtl:
    files:
      - rtl/blinky.sv
      - rtl/macros.svh:
          is_include_file: true
    file_type: systemVerilogSource

  tb:
    files:
      - tb/blinky_tb.sv
    file_type: systemVerilogSource

  nexys_video:
    files:
      # YAML short form, see rtl/macros.svh above for the longer form.
      - data/nexys_video.xdc: {file_type: xdc}

targets:
  # The "default" target is special in FuseSoC and used in dependencies.
  # The "&default" is a YAML anchor referenced later.
  default: &default
    filesets:
      - rtl
    toplevel: blinky
    parameters:
      - clk_freq_hz

  # The "sim" target simulates the design. (It could have any name.)
  sim:
    # Copy all key/value pairs from the "default" target.
    <<: *default
    description: Simulate the design
    default_tool: icarus
    filesets_append:
      - tb
    toplevel: blinky_tb
    tools:
      icarus:
        iverilog_options:
          - -g2012 # Use SystemVerilog-2012
      modelsim:
        vlog_options:
          - -timescale=1ns/1ns
    parameters:
      - pulses=10

  # The "synth" target synthesizes the design. (It could have any name.)
  synth:
    <<: *default
    description: Synthesize the design for a Nexys Video FPGA board
    default_tool: vivado
    filesets_append:
      - nexys_video
    tools:
      vivado:
        part: xc7a200tsbg484-1
    parameters:
      - clk_freq_hz=100000000

parameters:
  clk_freq_hz:
    datatype    : int
    description : Frequency of the board clock, in Hz
    paramtype   : vlogparam
  pulses:
    datatype    : int
    description : Number of pulses to run in testbench
    paramtype   : vlogparam

Naming the core file

The core file can have any name, but it must end in .core. It is recommended to choose a file name matching the core name, as discussed below.

The first line: CAPI=2

A core file always starts with the line CAPI=2. No other content (including comments) is allowed before this line, as FuseSoC uses this line to differentiate between different versions of the CAPI schema. Only CAPI version 2 is specified at the moment.

The core name, version, and description

Each core has a name, given in the name key. Core names can be freely chosen, but need to follow a common structure called VLNV. VLNV stands the four parts of a core name, which are separated by colon (:): Vendor, Library, Name, and Version.

Version numbers should be three numbers in the form major.minor.patch and follow semantic versioning (SemVer).

Cores can also have a description, given in the description key. A description is optional, but recommended.

name: fusesoc:examples:blinky:1.0.0
description: Blinky, a FuseSoC example core

In this example, the vendor is fusesoc, the library is examples, and the name of the core is blinky. The version is set to 1.0.0.

Specifying source files

A core typically consists of one or multiple source files. Source files are grouped into file sets under the filesets key.

FuseSoC does neither mandate a specific grouping, nor naming of file sets. It is common to use one file set for RTL (design) files, and one for testbench files.

The following example shows a single file set, rtl, with a set of common keys.

filesets:
  rtl:
    files:
      - rtl/blinky.sv
      - rtl/macros.svh:
          is_include_file: true
    file_type: systemVerilogSource

For each named file set, several keys are supported:

  • files: An ordered list of source files. The list of source files is ordered: the files will be passed to the tool in exactly the given order. This is important, for example, in SystemVerilog, where packages need to be compiled before they can be used by subsequent source files.

  • file_type: The default file type for all files in the files list.

  • depend: Dependencies on other cores. Dependencies are explained in depth at Dependencies: link cores together for re-use.

Source files

Source files are resolved relative to the location of the core file and must be stored in the same directory as the core file, or in a subdirectory of it. Source file names cannot be absolute paths, or start with ../.

Optionally, source files can have attributes; the file macros.vh is an example of that. When specifying attributes, end the file name with a colon (:), and specify attributes as key-value pairs below it. (Alternatively, the equivalent short form syntax can be used, e.g. macros.vh: {is_include_file: true}.)

The most common attributes are:

  • is_include_file: The file is an include file. In Verilog and C/C++, this means the file is not passed to the tool directly, but instead the file is included by another source file. FuseSoC ensures that the tool finds the include file, e.g. by passing an appropriate include path to the tool.

  • file_type: Override the default file type of the fileset for this particular file.

Refer to the CAPI2 reference documentation for more details.

File types

A file type describes the type of source file. FuseSoC does not use this information itself, but passes it on to tool backends which then configure the tool appropriately depending on the file type encountered.

Commonly used file types are:

  • verilogSource: Verilog source code, up to Verilog-2001. Files ending in .v or .vh should use this type.

  • systemVerilogSource: SystemVerilog source code (design and test code). Files ending in .sv or .svh should use this type.

  • vhdlSource: VHDL source code. Files ending in .vhd or .vhdl should use this file type.

Refer to the CAPI2 reference documentation for more details.

Targets

A target can be seen as something you would like to do with the source code in the core: synthesize it, simulate it, lint it. Targets are specified as dictionaries under the targets top-level key.

targets:
  # The "default" target is special in FuseSoC and used in dependencies.
  # The "&default" is a YAML anchor referenced later.
  default: &default
    filesets:
      - rtl
    toplevel: blinky
    parameters:
      - clk_freq_hz

  # The "sim" target simulates the design. (It could have any name.)
  sim:
    # Copy all key/value pairs from the "default" target.
    <<: *default
    description: Simulate the design
    default_tool: icarus
    filesets_append:
      - tb
    toplevel: blinky_tb
    tools:
      icarus:
        iverilog_options:
          - -g2012 # Use SystemVerilog-2012
      modelsim:
        vlog_options:
          - -timescale=1ns/1ns
    parameters:
      - pulses=10

  # The "synth" target synthesizes the design. (It could have any name.)
  synth:
    <<: *default
    description: Synthesize the design for a Nexys Video FPGA board
    default_tool: vivado
    filesets_append:
      - nexys_video
    tools:
      vivado:
        part: xc7a200tsbg484-1
    parameters:
      - clk_freq_hz=100000000

The blinky example shown above defines three targets: the default target, a sim target to simulate the design, and a synth target to synthesize it. Many designs also define a lint target to run static analysis jobs. The sim and synth targets are optional and could have had any name. The default target is special and required.

Within a target

Within each target block multiple keys determine what the target does. The most common keys are:

  • filesets: An ordered list of file sets (source files) included in the target.

  • description (optional): A description of the target.

  • toplevel (optional): The name of the design toplevel. (For advanced scenarios it is possible to specify a list of multiple toplevels instead of just a single one.)

  • default_tool (optional): The default tool to be used to build the target. The tool can also be set or overridden through a FuseSoC command-line argument.

  • tools (optional): Tool-specific settings, grouped by tool name.

  • parameters (optional): Parameters (Verilog parameters and defines, VHDL generics, etc.) to be passed to the design, or forced to a certain value.

The filesets_append key is part of an inheritance schema and explained further in section Inheritance and the default target.

The default target

The default target is the only required target. It serves two purposes:

  • The default target is used if no other target is explicitly selected when running FuseSoC.

  • The contents of the default target are used if the core is used as dependency (described in detail in Dependencies: link cores together for re-use).

All reusable code in the core should go into the default target: RTL files, lint waivers, reusable constraints, etc.

Inheritance and the default target

Importantly, other targets in the same core do not inherit the contents of the default target automatically. To achieve such inheritance behavior, FuseSoC provides a flexible inheritance mechanism, based on YAML anchors/references, YAML << merge operator, and a FuseSoC-specific list append feature.

The blinky.core shows the recommended template to inherit configuration between targets.

  1. Add &default after the default: text. This defines a YAML anchor named default, which can be referenced later in the file.

  2. Add a line <<: *default to the target where you want to inherit from default (the sim and synth targets in the example code). This line will effectively “copy over” all configuration under the default target.

As always with inheritance the interesting questions are around overriding behavior.

  • Settings (keys) given in the target which inherits from default override the keys in default. For example, the toplevel key in the sim target is overridden to be tb. Note that no merging of setting data structure is performed.

  • For settings which are lists, for example the filesets key, FuseSoC provides a way to combine lists by adding _append to the name of the key.

    This behavior is best explained by example. The filesets list in the default target consists of a single item, rtl. The sim target wants to append the item tb (a file set with testbench files) to the list. To do so, it specifies the special filesets_append key with a partial list. When evaluating the core file, FuseSoC appends the contents of sim.fileset_append at the end of default.fileset to form a list with two items: rtl, and tb. The same behavior works for all lists in core files.