Tools
Documentation for ancillary tools included with slang
slang includes several tools besides the main command line driver tool (which is documented in Command Line Reference ). Many of these tools are contributions that may not be as complete or well supported as the main slang library.
slang-hier
A tool that can display information about a Verilog hierarchy.
This tool accepts the standard set of slang driver command line options, which lets you configure your design. Then the tool will display information like module instance names and resolved parameter values.
Additional options to control output:
--params
Include instance parameter values in the output.
--max-depth <depth>
The maximum instance depth of the hierarchy to be printed. Everything deeper than that will be ignored.
--inst-prefix <prefix>
A hierarchical path indicating which hierarchy to display. All parts of the design not under this prefix will be ignored.
--inst-regex <regex>
Only instances that match the given regex (anywhere in the design tree) will be shown. All others will be skipped.
--custom-format <fmt>
A libfmt style format string that controls how the output is printed. Use {inst}, {module}, {file} as argument names in the string.
slang-reflect
slang-reflect is a tool that aims to help developers to remove the burden of creating C/C++ representations of SystemVerilog structs, enums and parameters. Its behavior is inspired from the verilator public option provided by verilator, and it is of a great use when building testbenches using C++/SystemC.
To indicate which SystemVerilog types we want to make public we need to annotate its declaration with // public or // verilator public. For example:
typedef struct { logic k; } pkg_struct_comment /* public */; typedef enum {ONE = 2, TWO, THREE} my_enum /* public */; localparam my_local /* public */ = 8'h42;
For all types that have been declared public inside the same package or module, the tool will create a header file with a namespace inside it, both of them with the name of the SystemVerilog container. Inside the namespace, there will be the declaration of the public types. For example, a SystemVerilog file that looks like this:
package foo; localparam foo_local /* public */ = 12; endpackage package bar; localparam bar_local /* public */ = 12; endpackage module top; localparam top_local /* verilator public */ = 12; endmodule
The auto-generated files and code will look like this:
// bar.h #pragma once #include ... namespace bar { static constexpr uint32_t bar_local = 12; } // foo.h #pragma once #include ... namespace foo { static constexpr uint32_t foo_local = 12; } // top.h #pragma once #include ... namespace top { static constexpr uint32_t top_local = 12; }
CPP Reserved Keywords
Since not all CPP kewords are SystemVerilog keywords, if any name of a member, enum, struct or parameter collides with a reserved C++ keyword, it will be renamed with a _ in front. For example if a member of a struct is named operator it will be emitted as _operator.
SystemVerilog Namspace References
In order to handle references from other packages that are done using the import foo::* or foo::my_local, the foo header will be included into the headers where their types or parameters are being used.
package bar; typedef struct packed { logic k; } bar_struct /* public */; endpackage package foo; typedef struct { bar::bar_struct foo_struct; } foo_struct /* public */; endpackage
will transpile into
// bar.h #pragma once #include ... namespace bar { struct bar_struct { bool k; ... }; } // foo.h #pragma once #include ... #include "bar.h" namespace foo { struct foo_struct { bar::bar_struct foo_struct; ... }; }
SystemVerilog Typedefs
When the type of a member of a struct comes from a typedef, the tool will resolve the typedef and emit the base type. For example:
package bar; typedef enum {ONE = 5, TWO, THREE} my_enum /* public */; typedef my_enum t_enum; typedef struct packed { t_enum e; } bar_struct /* public */; endpackage
// bar.h #pragma once #include ... namespace bar { struct my_enum { enum Type : uint32_t { ONE = 5, TWO = 6, THREE = 7 }; ... }; struct bar_struct { my_enum e; ... }; }
Emitted Headers
The includes that are emitted along with each file are:
#include <ostream> #include <cstddef> #include <cstdint> #include <string> #include <sstream> #include <systemc.h>
The #include <systemc.h> can be removed if the --no-sc argument is provided to the tool.
SystemVerilog Parameters
SystemVerilog parameters are transpiled as a static constexpr with the type being uint32_t or uint64_t. For example
package foo; localparam bar /* public */ = 42; endpackage
namespace foo { static constexpr uint32_t bar = 42; }
SystemVerilog Enums
SystemVerilog enums are transpiled as a struct in order to provide convenient methods to serialize and deserialize the enum into the underlying type. For example:
package bar; typedef enum {ONE = 5, TWO, THREE} my_enum /* public */; endpackage
// bar.h #pragma once #include ... namespace bar { struct my_enum { enum Type : uint32_t { ONE = 5, TWO = 6, THREE = 7 }; static constexpr size_t _size = 32; Type type; my_enum() = default; my_enum (uint32_t data) { switch (data) { case 5: type = Type::ONE; break; case 6: type = Type::TWO; break; case 7: type = Type::THREE; break; default: throw std::runtime_error("Can not create my_enum from provided value"); } } my_enum (Type& type) { this->type = type; } friend std::ostream& operator<<(std::ostream& os, const my_enum& data) { switch (data.type) { case Type::ONE: os << "ONE"; break; case Type::TWO: os << "TWO"; break; case Type::THREE: os << "THREE"; break; } return os; } my_enum& operator=(const Type t) { this->type = t; return *this; } operator uint64_t() const { return static_cast<uint64_t>(type); } Type operator() () const { return type; } }; }
SystemVerilog Structs
SystemVerilog structs are transpiled as a struct that provide convenient methods to serialize, deserialize and print the struct.
Members of the struct that are not another struct or enum, will be transpiled as uint32_t, uint64_t or sc_bv<N>. In the case the option --no-sc has been provided to the tool and there is a member bigger than 64 bits, or the struct itself has a size bigger than 64 bits, the tool will report an error asking for enabling the SystemC support.
For example:
package bar; typedef struct packed { logic [7:0] b; logic [15:0] h; logic [31:0] w; } bar_struct /* public */; endpackage
// bar.h #pragma once #include ... namespace bar { struct bar_struct { uint32_t w; uint32_t h; uint32_t b; static constexpr size_t w_s = 0; static constexpr size_t w_w = 32; static constexpr size_t h_s = 32; static constexpr size_t h_w = 16; static constexpr size_t b_s = 48; static constexpr size_t b_w = 8; static constexpr size_t _size = 56; bar_struct() = default; bar_struct(const uint64_t& data) { w = (data >> w_s) & (~0ULL >> (64 - 32)); h = (data >> h_s) & (~0ULL >> (64 - 16)); b = (data >> b_s) & (~0ULL >> (64 - 8)); } bar_struct(const sc_bv<56>& data) { w = data.range(w_s + w_w - 1, w_s).to_uint64(); h = data.range(h_s + h_w - 1, h_s).to_uint64(); b = data.range(b_s + b_w - 1, b_s).to_uint64(); } operator uint64_t() const { uint64_t ret = 0; ret |= static_cast<uint64_t>(w) << w_s; ret |= static_cast<uint64_t>(h) << h_s; ret |= static_cast<uint64_t>(b) << b_s; return ret; } operator sc_bv<56>() const { auto ret = sc_bv<56>(); ret.range(w_s + w_w - 1, w_s) = w; ret.range(h_s + h_w - 1, h_s) = h; ret.range(b_s + b_w - 1, b_s) = b; return ret; } std::string to_string() const { std::stringstream ss; ss << "w" << " = " << w; ss << " h" << " = " << h; ss << " b" << " = " << b; return ss.str(); } friend std::ostream& operator<<(std::ostream& os, const bar_struct& data) { os << data.to_string(); return os; } static uint32_t get_w (const uint64_t& data) { return (data >> w_s) & (~0ULL >> (64 - 32)); } static uint32_t get_h (const uint64_t& data) { return (data >> h_s) & (~0ULL >> (64 - 16)); } static uint32_t get_b (const uint64_t& data) { return (data >> b_s) & (~0ULL >> (64 - 8)); } }; }
slang-unifdef
slang-unifdef simplifies SystemVerilog preprocessor conditionals by forcing selected macros to be defined or undefined, then rewriting the source with inactive branches removed.
This is useful when producing a source tree for one configuration, for example permanently enabling a feature guarded by a macro or removing code for a disabled feature.
Usage
slang-unifdef [options] files-or-dirs...
Options
-D,--define <macro>- simplify conditionals as if<macro>is defined-U,--undefine <macro>- simplify conditionals as if<macro>is undefined-i,--inplace- rewrite input files in place-h,--help- print command-line help
--define and --undefine accept comma-separated macro lists and can be repeated. The short forms accept attached or separate names, such as -DENABLE_CACHE or -D ENABLE_CACHE.
Examples
Print the simplified output for one file:
slang-unifdef foo.sv -UENABLE_LOGGING
Rewrite a tree in place:
slang-unifdef rtl/ -DENABLE_CACHE -UENABLE_LOGGING -i
Force more than one macro value:
slang-unifdef rtl/ --define ENABLE_CACHE,ENABLE_PREFETCH --undefine ENABLE_LOGGING -i
Behavior
For file inputs, slang-unifdef processes the exact paths provided. For directory inputs, it recursively processes files ending in .sv, .svh, .v, or .vh.
Without --inplace, all rewritten output is written to stdout. With --inplace, each input file is overwritten with its simplified contents.
The tool understands ifdef, ifndef, elsif, else, and endif directive structure. When a forced macro selects a branch, the surrounding directive scaffolding is removed and the selected branch is inlined:
`ifdef ENABLE_CACHE logic cache_hit; `else logic direct_access; `endif
With --define ENABLE_CACHE, this becomes:
logic cache_hit;
If part of an elsif chain is still unknown, the unresolved prefix is preserved. For example, if MACRO_A is unknown and MACRO_B is forced defined:
`ifdef MACRO_A logic a; `elsif MACRO_B logic b; `else logic fallback; `endif
becomes:
`ifdef MACRO_A logic a; `else logic b; `endif
Notes
The implementation works directly over source text using slang's lexer; it does not run the full preprocessor or parse a syntax tree. Conditions are simplified only when the conditional directive uses a simple macro name that was supplied with --define or --undefine.
Rewriter
A simple tool that prints a given input file, showcasing the round-trip ability of the library.
Description
By default, rewriter prints the input file exactly as is. This tool can be used to:
- Modify files before use in other tools
- Better understand what the preprocessor is doing
- Test the round-trip capability of the slang library
The tool accepts standard slang arguments like include directories. When a macro or include is not found, it will be skipped.
Usage
rewriter [options] <file-name>
Options
Expansion Options
--expand-includes- Expand include directives to show included content--expand-macros- Expand macro usages to show expanded content
Filtering Options
--exclude-directives- Exclude other directives in output (doesn't control include and macro directives)--exclude-comments- Exclude comments in output--exclude-skipped- Exclude skipped (error) nodes in output
Formatting Options
--squash-blanklines- Squash adjacent blank lines into one--include-missing- Include missing (auto-inserted) nodes in output
slang-threadtest
A simple tool that allows testing the thread safety invariants of the AST. The AST, once an initial visitation has occurred and all diagnostics are collected, should then be able to be visited from multiple threads without issue. This tool runs many such visitations across a design to see if any asserts fire.
Usage:
slang-threadtest [-n num_iterations] <all-other-slang-args>
slang-tidy
A SystemVerilog linter
Configuration File
Slang Tidy can be configured using a .slang-tidy. This file can be provided using the --config-file argument or the slang-tidy tool will search for it from the path where it has been called until the root directory.
Configuration file grammar
The grammar of the config file is the following
config ::= section+ section ::= checks_config_section | checks_section checks_section ::= "Checks:" [EOL] rule+ rule ::= ["-"] ((check_or_all ",")* | check_or_all) END check_or_all ::= "*" | check_group "-" check_name_or_all ["=" severity_name] check_group ::= identifier check_name_or_all ::= "*" | identifier checks_config_section ::= "CheckConfigs:" [EOL] config+ config ::= ((config_tuple ",")* | config_tuple) END config_tuple ::= config_name ":" config_value config_name ::= identifier config_value ::= expression | "[" ((expression ",")* expression)? "]" severity_name ::= "ignore" | "info" | "warning" | "error" | "fatal" identifier ::= { A-Z | a-z }+ expression ::= { A-Z | a-z | 0-9 | "_" }+ EOL ::= '\n' | '\r' | '\r\n' END ::= EOL* | EOF
Configuration file
The syntax of the .slang-tidy file is similar to the syntax of the .clang-tidy. There are two main sections: Checks and CheckConfigs, that can appear as many times as needed in the config file.
The Checks section is a comma separated list of globs with an optional - prefix, that will enable or disable the specified check or group of checks.
Enable a check: synthesis-only-assigned-on-reset Disable a check: -synthesis-only-assigned-on-reset Enable a group: synthesis-* Disable a group: -synthesis-* Enable all: * Disable all: -*
Each check identifier is composed of a group name, eg style, followed by a hyphen - and the check name in lower case with hyphens. See the output of --print-descriptions to see the available checks and their config keys.
Additionally, each check or group of checks can specify a severity level that diagnostics are reported with. These levels are ignore, info, warning, error and fatal.
The CheckConfigs is a dictionary like, config: value, comma separated list of options for the different checks. The available options are:
| Config | Type | Default |
|---|---|---|
| clkName | string | clk_i |
| clkNameRegexString | string | "clk\S*|clock\S*" |
| resetName | string | rst_ni |
| resetIsActiveHigh | bool | false |
| inputPortSuffix | [string] | [_i] |
| outputPortSuffix | [string] | [_o] |
| inoutPortSuffix | [string] | [_io] |
| moduleInstantiationPrefix | string | i_ |
| inputPortPrefix | [string] | [] |
| outputPortPrefix | [string] | [] |
| inoutPortPrefix | [string] | [] |
| allowNestedAnon | bool | false |
| covergroupRegexString | string | "cg_\S*" |
| coverpointRegexString | string | "cp_\S*" |
| crossRegexString | string | "cross_\\S*\" |
| enumRegexString | string | "[a-z_0-9]+_e" |
| structRegexString | string | "[a-z_0-9]+_t" |
| unionRegexString | string | "[a-z_0-9]+_t" |
| typedefRegexString | string | "[a-z_0-9]+_t" |
resetIsActiveHigh** describes the reset polarity of the design under test: set it to true when the reset named by resetName is asserted high, and false when it is asserted low. It defaults to false to match the default resetName of rst_ni. The synthesis checks use it to work out which branch of an if inside an always_ff is the reset branch.
style-typedef-name applies to all typedefs. If style-enum-name, style-struct-name, or style-union-name are enabled, those checks take precedence for the corresponding typedefs.
An example of a possible configuration file:
Checks: -*, synthesis-only-assigned-on-reset, style-enforce-port-suffix CheckConfigs: clkName: clk, clkNameRegexString: "clk_signal\S*|clock_port\S*", resetName: rst_i, resetIsActiveHigh: true, inputPortSuffix: _k, outputPortSuffix: _p
How to add a new check
- Create a new
cppfile with the name of the check in CamelCase format inside the check kind folder. - Inside the new
cppfile create a class that inherits fromTidyChecks. Use thecheckfunction to implement the code that will perform the check in the AST. - Use the
REGISTERmacro to register the new check in the factory. - Create the new tidy diagnostic in the
TidyDiags.hfile. - Add the new check to the corresponding map in the
TidyConfigconstructor. - Update the documentation accordingly
- Add the new
cppfile to CMakeLists.txt