Getting started

This page installs ohsh, describes a three-module project with manifests, shows how ohsh can generate those manifests, and compiles the output with a simulator. The project is the simple example from the repository.

Install

ohsh needs Python 3.6 or newer and has no other dependencies.

pip install ohsh
ohsh --version

Describe your modules

Give every module a manifest.json next to its sources. A manifest names the module, lists its source files (relative to the manifest), and lists the modules it uses, grouped by library:

project/
├── accumulator/
│   ├── accumulator.vhd
│   └── manifest.json
├── adder/
│   ├── adder.vhd
│   └── manifest.json
└── counter/
    ├── counter.v
    └── manifest.json

accumulator/manifest.json says that the accumulator uses the adder:

{
  "module": "accumulator",
  "sources": ["accumulator.vhd"],
  "dependencies": {
    "work": ["adder"]
  }
}

adder/manifest.json and counter/manifest.json have no dependencies:

{
  "module": "adder",
  "sources": ["adder.vhd"]
}
{
  "module": "counter",
  "sources": ["counter.v"]
}

work means “the same library as the module that uses it”. The manifest format page has every field.

Let ohsh write the manifests

The manifests above can also be generated. ohsh has three options that read the sources and create or update manifests. See Create, fix and check manifests.

Create the manifests

In a project without manifests, run --create:

cd project
ohsh --create

ohsh prints each manifest and asks before it writes the file:

accumulator/manifest.json
  {
    "module": "accumulator",
    "sources": [
      "accumulator.vhd"
    ],
    "dependencies": {
      "work": [
        "adder"
      ]
    }
  }
Create accumulator/manifest.json? [y]es, [n]o, [a]ll, [q]uit:

The dependency on the adder comes from the line add_i : entity work.adder in accumulator.vhd. At the end ohsh prints a summary:

Created 3 of 3 proposed manifests
Nothing depends on these new modules (top-levels or testbenches): accumulator, counter

These are the same three manifests as in the previous section. Check that the modules are split and named the way you want, since ohsh simply makes one module per directory. --create skips directories that already have a manifest, so you can run it again when you add a directory.

Update them when the sources change

Suppose accumulator.vhd now also instantiates the counter:

u_counter : entity work.counter port map (clk => clk, rst => rst, count => open);

The manifest of the accumulator needs a new dependency. Run --fix to add it:

ohsh --fix
Fixed accumulator/manifest.json
  missing dependency: counter (library work)

--fix also puts the sources of a module in compile order, for example a package before the file that uses it. It never removes a dependency. To limit it to one module and its dependencies, give the module name, as in ohsh --fix accumulator.

Check them

--check reports what --fix would change, and writes nothing:

ohsh --check
All 3 manifests match their sources

Had you run it before --fix, the output would have been:

accumulator/manifest.json
  missing dependency: counter (library work)
1 of 3 manifests need fixing, run ohsh --fix

In that case the exit code is 104, which makes --check useful in CI. It accepts a module name in the same way as --fix.

Run ohsh

Run ohsh from the project directory with the name of your top module:

cd project
ohsh accumulator

ohsh is quiet when everything works. It finds every manifest*.json under the current directory, follows the dependencies of accumulator, and writes two files:

work_vhdl.src, the VHDL sources of library work in compile order, as absolute paths:

/path/to/project/adder/adder.vhd
/path/to/project/accumulator/accumulator.vhd

libraries.src, the libraries to compile, in order:

work

The adder comes before the accumulator because the accumulator needs it. The counter is left out because nothing under accumulator uses it. Run ohsh counter to get it, in work_systemverilog.src.

Three options cover most uses:

  • -t DIR searches for manifests under DIR instead of the current directory.

  • -o DIR writes the lists to DIR, creating it if needed.

  • -v prints what ohsh found and wrote. -vv adds debug details.

So ohsh -t project -o build accumulator does the same from anywhere and keeps the output in build/.

Absolute paths only hold on the machine that wrote them. To commit the lists, or to use them from another checkout or inside a container, add --relative-to DIR. ohsh --relative-to . accumulator writes:

adder/adder.vhd
accumulator/accumulator.vhd

A source outside DIR gets a path that starts with ../.

Compile the result

Each line of a .src file is one path, so a shell loop is enough to feed them to a tool. With GHDL:

ohsh -t project -o build accumulator
while IFS= read -r f; do ghdl -a --std=08 "$f"; done < build/work_vhdl.src
ghdl -e --std=08 accumulator

The integrations page has runnable examples for cocotb, VUnit, GHDL, NVC, UVVM, hog, Questa, Vivado and Quartus.

More than one library

Larger designs usually spread modules over several libraries. List each dependency under its library, and use -w to choose the library of the top module. In the advanced example, the accumulator uses an adder in math_lib, which uses a package in util_lib:

ohsh -t advanced -w dsp_lib -o build accumulator

ohsh then writes one list per library, and libraries.src gives the order to compile them in, each library after the ones it uses:

util_lib
math_lib
dsp_lib

Loop over libraries.src and compile each library’s lists in turn. The integrations page shows this for GHDL.

Third-party libraries

Designs often pull in code from elsewhere, such as vendor IP, libraries from other groups or open-source cores. Keep that code unmodified, for example as a git submodule, and put a manifest next to it instead of inside it. The manifest’s paths point into the submodule and list the files you use, in compile order:

project/
├── submodules/
│   ├── ipbus-firmware/          # git submodule, never edited
│   └── manifest_ipbus.json
└── slow_control/
    ├── slow_control.vhd
    └── manifest.json

submodules/manifest_ipbus.json (ohsh picks up any file named manifest*.json):

{
  "module": "ipbus",
  "sources": [
    "ipbus-firmware/components/ipbus_core/firmware/hdl/ipbus_package.vhd",
    "ipbus-firmware/components/ipbus_util/firmware/hdl/ipbus_addr_decode.vhd",
    "ipbus-firmware/components/ipbus_core/firmware/hdl/ipbus_fabric.vhd"
  ]
}

Modules that need it name the library to compile it into, here ipbus:

{
  "module": "slow_control",
  "sources": ["slow_control.vhd"],
  "dependencies": {
    "ipbus": ["ipbus"]
  }
}

ohsh writes the third-party files to ipbus_vhdl.src, and libraries.src puts ipbus before the libraries that use it. Updating the submodule only means updating the one manifest next to it, and only if its file list changed.

One manifest per testbench

A testbench is a module like any other. Give it a manifest with its own sources and the modules it tests, and run ohsh with its name:

{
  "module": "slow_control_tb",
  "sources": ["slow_control_tb.vhd"],
  "dependencies": {
    "work": ["slow_control"]
  }
}
ohsh -t ../.. slow_control_tb

The testbench manifest can sit in the same directory as the module’s own. ohsh reads every file named manifest*.json, so name it manifest_tb.json next to manifest.json. See discovery and validation.

The lists then hold only the files that testbench needs, so the simulator compiles only those files instead of the whole project. The top-level design is a module too, so the synthesis flow uses the same manifests.

A common setup is a small Makefile next to each testbench that runs ohsh before the simulator. See the cocotb Makefile example.

The ohsh dog resting next to an ordered, checked list