Naja-Verilog is a structural (gate-level) Verilog parser and can be used to read synthesis generated netlists.
This library provides a verilog interface to Naja SNL, however both projects are not tied and naja-verilog can be integrated in any project needing structural verilog support.
:information_desk_person: If you have any questions, bug to report or request, please open an issue or send me a mail.
:star: If you find Naja-Verilog interesting, and would like to stay up-to-date, consider starring this repo to help spread the word.
This project is supported and funded by NLNet through the NGI0 Entrust Fund.
This parser is dedicated to a very small part of verilog: the subset allowing to describe hierarchical gate level netlists. This is the format found at the output of synthesis tools such as Yosys.
To parse complete RTL level verilog or system verilog, please use projects such as: Verible. Apart the language support, the main difference with such RTL level parsing systems is that naja-verilog does not construct any AST but allows to construct the netlist on the fly while visiting the verilog source. Purpose is to reduce memory footprint and accelerate parsing time.
A comparable project can be found here: Parser-Verilog.
# First clone the repository and go inside it
git clone https://github.com/najaeda/naja-verilog.git
cd naja-verilog
git submodule init
git submodule update
Mandatory dependencies:
Embedded dependencies, through git sub module: google test.
On Ubuntu:
sudo apt-get install bison
sudo apt-get install flex
Using nix-shell:
nix-shell -p cmake bison flex
On macOS, using Homebrew:
brew install cmake bison flex
Ensure the versions of bison
and flex
installed via Homebrew take precedence over the macOS defaults by modifying your $PATH environment variable as follows:
export PATH="/opt/homebrew/opt/flex/bin:/opt/homebrew/opt/bison/bin:$PATH"
#First define an env variable that points to the directory where you want naja-verilog to be installed:
export NAJA_INSTALL=<path_to_installation_dir>
# Create a build dir and go inside it
mkdir build
cd build
cmake <path_to_naja_sources_dir> -DCMAKE_INSTALL_PREFIX=$NAJA_INSTALL
#For instance: cmake ~/srcs/naja-verilog -DCMAKE_INSTALL_PREFIX=$NAJA_INSTALL
make
make test
make install
Best starting point is to copy existing examples/implementations:
The principle of the parser is straightforward: inherit from VerilogConstructor and override callback methods launched while visiting verilog source.
As ordering of verilog modules in single or across multiple files is not preknown and module interfaces need to be created before instances and connectivity are created, parsing can be done in a two pass way with:
Stuctures (Identifier, Net, Port, Expression) details constructed by following callbacks can be found in VerilogType.h header.
An Identifier is a struct that holds the unescaped string. It also includes a boolean flag indicating whether the collected identifier was escaped or not.
void startModule(const naja::verilog::Identifier& module);
This callback is invoked when a module declaration begins. It receives the identifier of the module as an argument.
For instance, the callback is called with identifier:{name="foo",escaped=false} for the Verilog module declaration below:
module foo;
void endModule();
This function is called upon reaching the end of a module declaration. It serves as a notifier for the end of the module's scope, allowing for any necessary cleanup.
The corresponding verilog end declaration is:
endmodule //foo
void moduleInterfaceSimplePort(const naja::verilog::Identifier& port)
Triggered when a module uses a simple declaration (only the port identifier) for ports. It will be called multiple times depending on the number of ports declared.
For example, it is called with identifier.name="a", identifier.{name="b@", escaped=true}, and identifier.name="c" for:
module foo(a, \b@ , c);
Associated to previous method, following method collects port details: direction and size in module implementation section.
void moduleImplementationPort(const Port& port)
is called for each port listed in the implementation section of a module.
module foo(\a# , b, c);
input a;
output [3:0] b;
inout c;
//will invoke 3 times moduleImplementationPort with:
//Port identifier_={a#,true}, direction=Input, isBus()=true, range_.msb_=3, range_.lsb_=0
//Port identifier_={b,false}, direction=Output, isBus()=true, range_.msb_=0, range_.lsb_=3
//Port identifier_={c,false}, direction=InOut, isBus()=false
void moduleInterfaceCompletePort(const Port& port)
Invoked for a complete interface port declaration, detailing port direction (input/output/inout) and in case of bus: msb and lsb.
module foo(input[3:0] a, output[0:3] b, inout c);
//will invoke 3 times moduleInterfaceCompletePort with:
//Port identifier_={a,false}, direction=Input, isBus()=true, range_.msb_=3, range_.lsb_=0
//Port identifier_={b,false}, direction=Output, isBus()=true, range_.msb_=0, range_.lsb_=3
//Port identifier_={c,false}, direction=InOut, isBus()=false
void addNet(const Net& net)
This callback is invoked for each net declaration within a module and will construct Net structure containing net details: msb, lsb (for busses) and type: Wire, Supply0 or Supply1.
wire net0, net1, net2; // constructs 3 Net(s) named net0, net1, net2, type_=Wire, isBus()=false
wire [31:0] net3; // construct 1 Net named net3, type_=Wire, isBus()=true, range_.msb_=31, range_.lsb_=0
wire [-2:1] net4; // construct 1 Net named net4, type_=Wire, isBus()=true, range_.msb_=-2, range_.lsb_=1
supply0 constant0; // construct 1 Net named constant0, type_=Supply0, isBus()=false
supply1 constant1; // construct 1 Net named constant1, type_=Supply1, isBus()=false
void addAssign(const Identifiers& identifiers, const Expression& expression)
is called for each assign statement, facilitating the capture of signal assignments.
Below are Verilog examples followed by pseudo C++ representations of the data structures that might be constructed by this callback.
assign n0 = n1;
//identifiers = { {identifier_={n0,false}, range_.valid_=false} }
//expressions =
// {
// {
// value_.index()=naja::verilog::Expression::Type::IDENTIFIER
// with auto id=std::get<naja::verilog::Expression::Type::IDENTIFIER>(value_)
// id.identifier_={"n1",false}, id.range_.valid_=false
// }
// }
assign n1 = 1'b0;
//identifiers = { {identifier_={n1,false}, range_.valid_=false} }
//expressions =
// {
// {
// value_.index()=naja::verilog::Expression::Type::NUMBER
// with auto nb=std::get<naja::verilog::Expression::Type::NUMBER>(value_)
// nb.base_=naja::verilog::BasedNumber::BINARY
// nb.sign_=true, nb.signed_=false, nb.size_=1, nb.digits_="0"
// }
// }
assign { n2[3:2], n2[1:0] } = { n0, n1, 2'h2 };
//identifiers =
// {
// { identifier_={n2,false}, range_.valid_=true, range_.msb_=3, range_.lsb=3 },
// { identifier_={n2,false}, range_.valid_=true, range_.msb_=1, range_.lsb=0 },
// }
//expressions =
// {
// {
// value_.index()=naja::verilog::Expression::Type::CONCATENATION
// with auto concat=std::get<naja::verilog::Expression::Type::CONCATENATION>(value_)
// concat[0] is an Identifier identifier_={n0,false}, range_.valid_=false
// concat[1] is an Identifier identifier_={n1,false}, range_.valid_=false
// concat[2] is an NUMBER with:
// nb.base_=naja::verilog::BasedNumber::HEX
// nb.size_=2, nb.digits_="2"
// }
// }
void startInstantiation(const naja::verilog::Identifier& model)
allows to collect module (used as a model) name for one or multiple instanciations. This method will collect model={Model,false}
for the two following declarations:
Model ins();
Model ins0(), ins1(), ins2();
void addInstance(const naja::verilog::Identifier& instance)
will be called 3 times with instance={ins1,false}, {ins2,false}, {ins3,false}
for following declaration:
Model ins(), ins2(), ins3();
void endInstantiation();
is called at the conclusion of an instance declaration, it signals that all instances have been processed and allows for post-processing cleanup.
void addInstanceConnection(const naja::verilog::Identifier& port, const Expression& expression);
This function is called for each named port connection in an instance, capturing the relationship between the port and its connected net or expression.
mod1 inst2(.i0(net4[3:6]), .o0(net5));
//addInstanceConnection is called 2 times with:
//port=Identifier{"i0",false}
//expression_.value_.index() = naja::verilog::Expression::Type::IDENTIFIER,
//with auto id = std::get<naja::verilog::Expression::Type::IDENTIFIER>(expression.value_)
//id.identifier_={"net4",false}, id.isBus=true, id.range_.msb_=3, is.range_.lsb_=6
//and:
//port=Identifier{"o0",false}
//expression_.value_.index() = naja::verilog::Expression::Type::IDENTIFIER,
//with auto id = std::get<naja::verilog::Expression::Type::IDENTIFIER>(expression.value_)
//id.identifier_={"net5",false}, id.isBus=false
void addOrderedInstanceConnection(size_t portIndex, const Expression& expression);
is invoked for each port connection when ports are connected by order rather than by name.
mod1 inst4(net4[7:10], {net0, net1, net2, net3});
//addOrderedInstanceConnection is called 2 times with:
//portIndex=0
//expression_.value_.index() = naja::verilog::Expression::Type::IDENTIFIER,
//with auto id = std::get<naja::verilog::Expression::Type::IDENTIFIER>(expression.value_)
//id.identifier_={"net4",false}, id.isBus=true, id.range_.msb_=7, is.range_.lsb_=10
//and:
//portIndex=1
//expression_.value_.index() = naja::verilog::Expression::Type::CONCATENATION,
//with auto concat = std::get<naja::verilog::Expression::Type::CONCATENATION>(expression.value_)
//concat.size()=4 and all values are simple Identifiers
void addParameterAssignment(const naja::verilog::Identifier& parameter, const Expression& expression);
This callback function is designed to handle parameter assignments within module instantiations.
module test();
mod #(
.PARAM0("VAL"),
) ins();
endmodule
//addParameterAssignment is called one time with:
//parameter={"PARAM0",false} and expression is a RangeIdentifier with name="VAL"