Running simulations from the command line
jhdl is the command line of JanusHDL. One command analyzes your source files, elaborates the top level and runs the simulation, printing what the design reports as it goes. It is made for scripts and for continuous integration: its output is plain text, and its exit code says whether the run passed.
jhdl [options] FILE.vhd... [-f FILES.f]...
jhdl run PROJECT.jhdlproj [options]
The second form runs a project file; see Projects. Everything in this chapter applies to both: options given after a project take precedence over the project's own settings. Command reference lists every option in one table.
The basic run
jhdl counter.vhd counter_tb.vhd
does three things:
- Analysis. The files are analyzed in the order given, into library
work. The standard libraries (stdandieee) are built in and need no analysis. - Elaboration. The top level is elaborated: by default the entity analyzed last, here
counter_tb. - Simulation. The design runs until nothing is left to do, until the stop time, or until the testbench stops it (with
std.env.stoporstd.env.finish, or a failed assertion of severityfailure).
At the end, jhdl prints a summary line:
simulation of work.counter_tb ended at 115 ns (stopped); analysis 210 ms, elaboration 6 ms, run 2 ms
The reason in parentheses is one of:
| Reason | Meaning |
|---|---|
nothing left to simulate |
No process will resume and no signal will change. The time is that of the last thing that happened. |
stop time reached |
The run reached the time given with --stop-time; the time shown is the stop time, even when the last change came earlier. |
stopped |
The design stopped the run: std.env.stop or finish was called, a report reached the stop level (see Severity levels), or a run-time error occurred. |
Options and file names can come in any order on the command line, with one exception: --work applies to the files that follow it.
Analysis order
Files are analyzed in the order you give them, and a design unit must be analyzed before the units that use it: a package before the files that use it, an entity before its architecture. When the order is wrong, the error says what to analyze first:
error[undeclared]: `my_pkg` is not declared in `work`
--> tb.vhd:4:5
|
4 | use work.my_pkg.all;
| ^^^^^^^^^^^
= help: library `work` has no design units yet: analyze its sources into it first, before the files that use it
= LRM 8.3
If you have a list of files in no particular order, the language server can sort it for you. Write the files into a file list (see File lists) and run:
jhdl-lsp --order unsorted.f > sorted.f
It prints the same list with the files of each library in an order their dependencies allow.
Libraries
By default every file goes into library work. There are two ways to put files into other libraries.
--work NAME sets the library for the files that come after it on the command line:
jhdl --work util src/fifo_pkg.vhd src/fifo.vhd --work work tb/fifo_tb.vhd
Here fifo_pkg.vhd and fifo.vhd go into library util, and fifo_tb.vhd into work. The testbench then uses them with library util; use util.fifo_pkg.all;.
A file list (-f) names the files with their libraries; for anything with more than a couple of libraries it is the better choice.
File lists
A file list is a text file, usually named with the extension .f, that names the files to analyze in order:
# The FIFO and its testbench
library util
src/fifo_pkg.vhd
src/fifo.vhd
library work
tb/fifo_tb.vhd
- A line
library NAMEchooses the library for the files after it, up to the nextlibraryline. Files before anylibraryline go intowork. - Every other line is the path of a file, relative to the folder of the file list (or absolute). The whole line is the path, spaces included; do not put quotes around it.
- Blank lines, and lines that start with
#, are ignored.
Run it with:
jhdl -f fifo.f --top work.fifo_tb
-f can be given several times, and mixed with file names; everything is analyzed in the order given. This makes it easy to keep a verification library's files in one list and your design's in another:
jhdl -f uvvm_util.f -f my_design.f --top work.my_tb
The same file lists work in the window (Simulate → Run simulation…), with the language server, and as the starting point of a project (File → Import a file list (.f) as a project…).
Choosing the top level
--top NAME names the design unit to elaborate:
| You write | JanusHDL elaborates |
|---|---|
--top fifo_tb |
Entity fifo_tb, looked up in work first, then in the other libraries |
--top verif.fifo_tb |
Entity fifo_tb of library verif |
--top work.fifo_tb_cfg |
The configuration fifo_tb_cfg, with the architecture and bindings it gives |
Without --top, the top is the entity analyzed last. An entity is elaborated with the architecture analyzed last for it; to choose another architecture, write a configuration and give its name.
Generics
-gNAME=VALUE sets a generic of the top level:
jhdl -f fifo.f --top fifo_tb -gDEPTH=16 -gNAME=smoke -gVERBOSE=true
With a path of instance labels below the top, it sets a generic of that instance, replacing the actual its generic map gives:
jhdl -f fifo.f --top fifo_tb -gdut.DEPTH=32 -gdut.mem.WIDTH=8
Generic names are not case-sensitive. The value is written as VHDL writes a literal of the generic's type:
| Type | Forms |
|---|---|
Integer (integer, natural, ...) |
42, -7, 1_000, 1e3, 16#FF#, 2#1010# |
| Real | 1.5, -0.25, 2.0e-3, 16#F.8#; a whole number (2), or a number with an exponent and no point (1e-05) too. A point needs a digit on each side |
| Time and other physical types | A number and a unit, with or without a space: 20 ns, 20ns, 1.5 us, 16#A# ns; for time also s for seconds. A number without a unit is refused |
Enumeration (boolean, std_logic, bit, your own) |
A literal: true, idle (in any case), '1', 'X'. A character literal may also be written without its quotes: -gRESET_LEVEL=1 for a std_logic or bit generic. Character literals keep their case: x is not 'X' |
string, std_logic_vector, bit_vector |
The characters as they are (smoke, 0101), in double quotes ("0101"), or a bit string literal: x"A5", b"1111_0000", o"17", 8x"F" |
| Another one-dimensional array, a record | A positional aggregate: (1, 2, 3) |
The value then takes the generic's subtype, as an actual would: its range is checked, and an array value must have the generic's length (an unconstrained array is indexed from its index subtype's left bound, as a string literal is). On a command line, put a value with spaces or quotes in quotes for your shell: "-gPERIOD=1.5 us", '-gINIT=x"A5"'.
A value that is not one of the type's forms is an error that names the generic and says which forms its type takes:
error: generic period: `20` is not a value of type TIME: give a number and a unit, such as 20 ns, 20ns or 1.5 us (the units: fs, ps, ns, us, ms, sec, min, hr)
elaboration of work.fifo_tb failed
Stop time
--stop-time TIME ends the run at that time, whatever is still scheduled:
jhdl -f fifo.f --stop-time 10us
jhdl -f fifo.f --stop-time "2.5 ms"
A time is a number and a unit: fs, ps, ns, us, ms or s (also sec), with or without a space between them; the number may have decimals. Without --stop-time, the run goes on until nothing is left to do or the design stops it. A free-running clock never runs out of things to do, so a testbench with one should end itself with std.env.finish (or stop), or be given a stop time.
VHDL versions
--std VERSION chooses the VHDL version: 93, 02, 08 or 19 (or 1993, 2002, 2008, 2019). The default is VHDL-2008.
jhdl --std 19 -f design.f
The version decides:
- The language. Each file is read with that version's reserved words, syntax and rules. A word reserved only in a later version can be a name in an earlier one (with a warning,
future-reserved-word, that it will not be portable). - The standard packages. VHDL-1993 and VHDL-2002 designs get the VHDL-93 forms of
std_logic_1164,numeric_stdandnumeric_bit, in whichstd_logic_vector,unsignedandsignedare types of their own (designs that overload on them depend on that). VHDL-2008 and VHDL-2019 designs get the current packages. - The width of
INTEGERandTIME. VHDL-2019 makes both 64 bits wide. - What is allowed. For example, a shared variable of a type that is not protected is legal in VHDL-1993 and an error from VHDL-2002 on (a warning with
--compat; see Diagnostics).
On the command line, --std applies to every file. To analyze some libraries or files as another version, for example an old VHDL-1993 IP block in a VHDL-2008 design, use a project file, which gives a version per library or per file (see Projects).
Conditional analysis
VHDL-2019 has conditional analysis: tool directives that choose which text is analyzed.
`if RUN_MODE = "fast" then
constant C_CYCLES : natural := 100;
`else
constant C_CYCLES : natural := 100_000;
`end if
-DNAME=VALUE defines an identifier for these directives; one that is not defined is the empty string:
jhdl --std 19 -f design.f -DRUN_MODE=fast
The standard identifiers are predefined: VHDL_VERSION (such as 2019), TOOL_TYPE (SIMULATION), TOOL_VENDOR and TOOL_NAME (JanusHDL), TOOL_EDITION and TOOL_VERSION. Directives are VHDL-2019: in a file analyzed as an earlier version, they are an error that says to analyze the file with --std 19.
PSL
PSL directives (assert, assume, restrict, cover) and declarations written in VHDL-2008 or VHDL-2019 code are simulated as part of the design. To read PSL written in comments as well, the -- psl form, add --psl:
-- psl default clock is rising_edge(clk);
-- psl assert always (req -> next ack);
jhdl --psl -f design.f
A PSL assertion that fails reports like a VHDL assertion, with its place in the source. A strong property still waiting when the simulation ends fails then.
Messages
Reports and assertions
Each report, and each assertion that fails, prints one line as it happens:
115 ns (delta 2) counter_tb.vhd:25:12: note: count reached 10
| Part | Meaning |
|---|---|
115 ns |
The simulation time |
(delta 2) |
The delta cycle within that time, from 0 |
counter_tb.vhd:25:12 |
The file, line and column of the statement. The file is named by its path as you gave it, tidied: a file list's lib/script/../src/a.vhd shows as lib/src/a.vhd, with your system's separator (\ on Windows) |
note |
The severity: note, warning, error or failure |
count reached 10 |
The message |
Text that the design writes to the standard output, with writeline(OUTPUT, ...) (the way UVVM and OSVVM log), prints as it is, without time or place.
Run-time errors
An error found while the simulation runs, such as an index out of range, stops the run and prints the time, the place and the process:
3 ns (delta 0): failure: [rte.vhd:11:5] index 5 is out of the range 0 to 3 (in process /rte/check)
simulation of work.rte ended at 3 ns (stopped); analysis 15 ms, elaboration 3 ms, run 1 ms
The process is named by its path in the design: here the process labelled check in entity rte.
Analysis and elaboration errors
Analysis errors and warnings print with their rule, the source line and a pointer to the place, how to fix it, and the section of the LRM:
error[undeclared]: `x` is not declared
--> bad.vhd:8:10
|
8 | s <= x;
| ^
= help: check the spelling, or add the use clause that makes it visible
= LRM 12.3
analysis failed
A design unit whose analysis failed is not put into its library. A unit that needs it says so, and where to look, instead of a confusing "not declared":
error[undeclared]: package `vvc_sb_support_pkg` is not in library `bitvis_vip_sbi`: its analysis failed
Diagnostics, rules and compatibility mode explains the messages, and how to change how a rule is reported. Elaboration errors print as error: ... lines, followed by elaboration of TOP failed.
Where the output goes
Reports, assertions, the design's text output and the summary line go to the standard output. Analysis diagnostics and jhdl's own errors go to the standard error. To keep everything in one log file:
jhdl -f design.f > run.log 2>&1
Fewer messages
--quietprints only reports and assertions of severityerrorandfailure, and run-time errors. Notes, warnings, the design's text output and the summary line are left out. Analysis diagnostics still print.--ieee-warnings offsilences the assertions inside the IEEE packages, such as the warningsnumeric_stdgives for metavalues ("NUMERIC_STD.TO_INTEGER: metavalue detected, returning 0"). They are on by default, as the standard's package bodies have them.
Severity levels: stopping and failing
Two levels decide what a report of each severity does:
| Option | What it sets | Default |
|---|---|---|
--stop-level LEVEL |
The least severity of a report or assertion that stops the simulation | failure |
--exit-level LEVEL |
The least severity of a report or assertion that makes the exit code non-zero | error |
LEVEL is note, warning, error or failure. With the defaults, an assertion of severity error lets the run go on but makes it fail, and one of severity failure stops it.
# Stop at the first error, and also fail the run on warnings:
jhdl -f design.f --stop-level error --exit-level warning
A testbench that makes errors on purpose (to check that a checker catches them) can raise the exit level: --exit-level failure.
Exit codes
| Code | Meaning |
|---|---|
0 |
The run passed: analysis and elaboration succeeded, and no report reached the exit level. |
1 |
Analysis or elaboration failed (also with --keep-going, even when the design ran), a run-time error occurred, a report reached the exit level (by default: any error or failure), or the VCD file or waveform database could not be written. |
2 |
The command line or a file it names is wrong: an unknown option, a bad value, a file, file list or project file that cannot be read. |
3 |
The simulation used more memory than --max-memory allows. |
jhdl --help lists the codes too.
Recording waveforms
jhdl can record every signal of the design in two formats.
The waveform database
jhdl -f design.f --wave run.jhw
jhdlw run.jhw
--wave FILE records every signal into a JanusHDL waveform database (.jhw), written while the simulation runs. Besides the values, it holds the design's structure: every process with its kind and place in the source, the signals it drives (with the lines that assign them) and the signals it reads. It also holds the delta cycles of each time step: which signals changed in each delta cycle, from which value to which, and which processes ran. The insight panels of the window use them (Insight panels).
The file is written in blocks, compressed, from a thread of its own, so memory does not grow with the length of the run. You can open it in jhdlw while the simulation is still running: the status bar says recording, and the view follows the file as it grows.
--wave-deltas off leaves the delta cycles out, for a smaller file; the waveforms are the same, and the Delta cycles panel then says there are none.
VCD files
jhdl -f design.f --vcd run.vcd
--vcd FILE writes a Value Change Dump file, for tools that read VCD. It is written when the run ends. VCD has no place for delta cycles or for the design's structure, so those are not in it. std_logic and bit signals and vectors are written as the format defines them, with the nine std_ulogic values; records appear as scopes of their fields; enumerations, integers, times and strings are written as text values, and reals as reals. The time scale is 1 fs.
Which to use
Waveform database (--wave) |
VCD (--vcd) |
|
|---|---|---|
| Written | While the run goes on | When the run ends |
| Can be viewed during the run | Yes | No |
| Delta cycles | Yes (unless --wave-deltas off) |
No |
| Design structure, for the insight panels | Yes | No |
| Size | Compressed | Plain text |
| Read by | jhdlw |
jhdlw and other tools that read VCD |
Give both to have both: --wave run.jhw --vcd run.vcd records the database as the run goes, and writes the VCD file from it at the end, the same file --vcd alone gives. A file that cannot be written is an error that says which, and the exit code is 1.
Files the design reads and writes
A design reads and writes files with std.textio and with file types of its own. Relative file names are relative to the folder jhdl runs in.
- Text files (
file ... : text) are plain text, astextiowrites them. - Files of
characterhold one byte per character and nothing else: the way to read and write any binary file from VHDL. - Other typed files (
type int_file is file of integer;, files of records, of vectors) have a layout of JanusHDL's own: a short header that says what the file holds, then the values, in a byte order that is the same on every system. A file of another element type, or one written by another program, is not opened; the message names the file and says what it holds:
text
0 (delta 0): failure: [tf.vhd:14:5] cannot open file "data.bin": it holds values of type INTEGER, not REAL (the element type of REAL_FILE). Read it with a file type of the element type it was written with, or write it again with this one (in process /tf/_p0)
file_open with a status parameter gets NAME_ERROR instead. The header records the type's structure, not its name: a file written as a file of integer reads as a file of natural, and a record declared the same way in two packages reads the other's files.
Typed files written by versions of JanusHDL before this layout are not read: write them again. Text files and files of character are not affected.
READ comes in the form its file type declares (LRM 5.5.2): READ(F, VALUE, LENGTH) for a file of an array type that is not fully constrained, such as file of string, which gives the value read and its length; READ(F, VALUE) for the others. The other form is an error (file-read-form), which --compat accepts.
Native code
By default, jhdl compiles the parts of the design that qualify (process bodies, subprograms, methods of protected types and more) to native machine code as the simulation runs, and runs the rest in its interpreter. The results are the same either way, messages included: the interpreter is the reference, and compiled code reports its errors with the same text and places.
--jit off runs everything in the interpreter; --jit on is the default. Setting the environment variable JHDL_JIT=off does the same as --jit off for every run, including the window's.
You do not normally need to touch this. If a run with native code ever behaves differently from one with --jit off, that is a defect: please report it with the design that shows it.
Caching the analysis
A large design, or a verification library such as UVVM, takes a noticeable time to analyze before every run. --cache FILE keeps the analysis in a file and loads it on the next run, instead of analyzing again:
jhdl -f uvvm_uart_demo.f --top bitvis_uart.uart_vvc_demo_tb --cache uart.cache
The cache is used when the same build of JanusHDL is given the same files in the same order, with the same contents, each with the same VHDL version, and with the same rule levels; otherwise the files are analyzed again and the cache is written anew. Warnings are kept with the cached analysis and printed again; an analysis with errors is not saved. Give each list of files its own cache file.
--analyze-only analyzes (and with --cache saves the analysis), then exits without elaborating. This prepares a cache once, for many runs that then each load it, for example one run per test with different generics:
jhdl -f tests.f --cache tests.cache --analyze-only
jhdl -f tests.f --cache tests.cache --top work.tb -gTEST=reset
jhdl -f tests.f --cache tests.cache --top work.tb -gTEST=overflow
A project whose files have different VHDL versions is cached too: the saved analysis records each file's version.
Running despite analysis errors
--keep-going reports the analysis errors and then elaborates anyway. A top level that does not depend on the design units in error runs; one that does stops with a message that says so:
error: work.bad cannot be elaborated: its analysis failed (the errors are above)
This lets you run one testbench of a library while another part of it does not analyze yet. The exit code is still 1, since the analysis failed, even when the design ran.
Memory limit
Each jhdl run stops with an error if it uses more than 4 GiB of memory, so that one runaway test cannot exhaust a build machine:
error: the simulation uses 4100 MiB, over the --max-memory limit of 4096 MiB; stopped (--max-memory 0 turns the limit off)
--max-memory SIZE changes the limit: a number of bytes, or a number with K, M, G or T (binary units), such as 512M or 16G. --max-memory 0 turns it off. The exit code of a run stopped this way is 3.
Timing the phases
The summary line gives the time of analysis, elaboration and the run. To see each phase as it finishes, set JHDL_PHASES=1:
[analysis loaded in 22 ms]
[elaboration done in 2 ms]
[run done in 1 ms]
loaded means the analysis came from the cache. These lines go to the standard error. JHDL_PEAK_MEMORY=1 prints the run's peak memory use at the end, in the same way.
In continuous integration
jhdl needs no license server, no network and nothing installed beyond its folder. A CI job can unpack the download (or keep it in the build image), put bin on the PATH, and run each testbench:
#!/usr/bin/env bash
set -e
export PATH=/opt/janushdl/bin:$PATH
jhdl -f sim/design.f --cache build/design.cache --analyze-only
for test in reset burst overflow; do
jhdl -f sim/design.f --cache build/design.cache --top work.tb -gTEST=$test \
--wave build/$test.jhw > build/$test.log 2>&1
done
With set -e, the job fails at the first test whose exit code is not 0; the log and waveform of each test stay in build/. The same design gives the same result on every run and every machine (Determinism and races), so a failure in CI reproduces on your desk.