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 DIRsearches for manifests underDIRinstead of the current directory.-o DIRwrites the lists toDIR, creating it if needed.-vprints what ohsh found and wrote.-vvadds 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.