-
Notifications
You must be signed in to change notification settings - Fork 76
Creating a Standard Plugin
WARNING: This description is outdated and should not be followed! For the moment, take a look at existing plugins shipped with HAL and build your own plugin accordingly.
To use the full capabilities of HAL, you can develop plugins yourself. Although we can use scripts to use the HAL API, sometimes the speed of C++ is necessary to achieve the desired performance. Here we show you how to develop plugins by implementing the example hal-netlist-statistics plugin. Furthermore, we show how to include python bindings to the code.
The plugin we will count all gate types that within a netlist. Our goal is to get the following output for the fsm.v file:
[BUF] = 9
[FFR] = 2
[GND] = 1
[LUT1] = 1
[LUT2] = 1
[LUT6] = 4
[MUX] = 2
[VCC] = 1
Here you can see that the netlist contains 9 BUF
gates, 4 LUT6
gates, and 1 LUT2
gate beside other elements.
In order to set-up a new plugin cd
to you development directory and run <pathToHAL>/tools/new_plugin.py <name>
.
This will create all the required directories and files to start implementing your new plugin directly.
For our netlist statistics plugin, we will extend the base plugin (which can only be used from code) to a CLI plugin that offers command-line options and implement the functionalities.
The script will generate the following files for you:
.
├── CMakeLists.txt
├── include
│ └── plugin_netlist_statistics.h
├── python
│ └── python_definitions.cpp
└── src
└── plugin_netlist_statistics.cpp
So let's start with adding content to these files.
Attention: The actual repo contains several more files. These are necessary to implement a plugin that can be build either from within the hal repo plugin directory or on a system with HAL installed. We elaborate a bit more on this in the section on writing the CMakeLists.txt
file.
In this tutorial we only consider the case of in hal source plugin development.
So let's start by implementing the code necessary to produce the output seen above. For this, we need the plugin_netlist_statistics
class. Let us create both the header and source file.
// include/plugin_netlist_statistics.h
#ifndef __HAL_PLUGIN_NETLIST_STATISTICS_H
#define __HAL_PLUGIN_NETLIST_STATISTICS_H
#include "core/interface_cli.h"
#include "def.h"
class PLUGIN_API plugin_netlist_statistics : virtual public i_cli
{
public:
plugin_netlist_statistics() = default;
~plugin_netlist_statistics() override = default;
/*
* interface implementations
*/
/** interface implementation: i_base */
std::string get_name() const override;
/** interface implementation: i_base */
std::string get_version() const override;
void initialize() override;
/** interface implementation: i_cli */
program_options get_cli_options() const override;
/** interface implementation: i_cli */
bool handle_cli_call(std::shared_ptr<netlist> n, program_arguments& args) override;
static std::string get_statistics(std::shared_ptr<netlist> n);
};
#endif //__HAL_PLUGIN_NETLIST_STATISTICS_H
Here all methods except static std::string get_statistics(std::shared_ptr<netlist> n)
are necessary interface functions used by HAL to load the plugin. Besides the name and version of your plugin, you need to provide a list of interfaces the plugin implements via the std::set<interface_type> get_type()
function. There are two notable interfaces you can use in the current version of HAL:
-
i_base
for plugins that can be accessed only from other plugins or the framework itself. -
i_cli
for plugins that should be executed by command line parameters of the HAL executable. It is a subclass of thei_base
interface and adds one functionget_cli_options()
to define the CLI parameter andhandle_cli_call()
as an entry point to the plugin.
The actual functionality is implemented in the get_statistics(std::shared_ptr<netlist> n)
function. This method allows us to use it as a python plugin. So let's take a look at the actual implementation.
// src/plugin_netlist_statistics.cpp
#include "plugin_netlist_statistics.h"
#include "core/log.h"
#include "netlist/gate.h"
#include "netlist/netlist.h"
#include <sstream>
extern std::shared_ptr<i_base> get_plugin_instance()
{
return std::make_shared<plugin_netlist_statistics>();
}
std::string plugin_netlist_statistics::get_name() const
{
return std::string("netlist_statistics");
}
std::string plugin_netlist_statistics::get_version() const
{
return std::string("0.1");
}
void plugin_netlist_statistics::initialize()
{
}
program_options plugin_netlist_statistics::get_cli_options() const
{
program_options description;
description.add("--netlist_statistics", "executes the netlist_statistics");
return description;
}
bool plugin_netlist_statistics::handle_cli_call(std::shared_ptr<netlist> netlist, program_arguments &args)
{
UNUSED(args);
log("netlist_statistics executed");
log_info(this->get_name(), get_statistics(netlist));
return true;
}
std::string plugin_netlist_statistics::get_statistics(std::shared_ptr<netlist> netlist)
{
std::stringstream stream;
std::map<std::string, u32> type_cnt;
std::map<u32, std::shared_ptr<gate>> ordered_gates;
for (const auto& gate : netlist->get_gates())
{
type_cnt[gate->get_type()->to_string()]++;
ordered_gates[gate->get_id()] = gate;
}
for (const auto& it : ordered_gates)
stream << it.second->get_id() << " : " << it.second->get_name() << " (" << it.second->get_type() << ")" << std::endl;
stream << "##########" << std::endl;
for (const auto& it : type_cnt)
stream << "[" << it.first << "] = " << it.second << std::endl;
stream << "##########" << std::endl;
return stream.str();
}
This function allows hal to load the plugin dynamically. Here you need to return an instance of your main plugin object.
The plugin we implement here is named netlist_statistics
in version 0.1, as you can see from the get_name
and get_version
methods. In the get_cli_options()
method we need to return the supported interface types i_cli
, and it's superclass i_base
.
The parameters to call this plugin from the command line is specified in get_cli_options()
. Here we define the parameter by creating a program_options
object and adding the option via program_options.add
.
program_options description;
description.add("--netlist_statistics", "executes the netlist_statistics");
program_options.add
in it's simpelest form just requires the option flag itself and a help string. You can also get some input from the commandline by requireing some command line parameters. For this you must call add as follows:
program_options description;
description.add("--netlist_statistics", "executes the netlist_statistics", {""});
If the command line interpreter detects that an option of a plugin is triggered, it executes the handle_cli_call
function of every plugin in the order of its appearance in the call string. The first parameter is always the netlist we want to work on and is parsed beforehand by the HAL parser.
In our example, we do not need to check which option is active for the current call. If you specify multiple options, you might need to do that within handle_cli_call
. If you have specified the parameters foo
and bar
that might look like the following:
if (vm.is_option_set("--foo"))
handle_foo(netlist, vm.get_parameter("--foo"));
if (vm.is_option_set("--bar"))
handle_bar(netlist);
You might have noticed that for foo
we specified a parameter which we now sent to the handler function. bar
in this example does not require a parameter.
For our plugin, we request the statistics data from get_statistics
and print it out on the command line.
At this point, we can now start to analyze the given netlist. First, we query all gates of the netlist and sort them by its type.
...
std::map<std::string, u32> type_cnt;
std::map<u32, std::shared_ptr<gate>> ordered_gates;
for (const auto& gate : netlist->get_gates())
{
type_cnt[gate->get_type()]++;
ordered_gates[gate->get_id()] = gate;
}
...
For debugging purposes we list all gates with its corresponding type:
...
for (const auto& it : ordered_gates)
stream << it.second->get_id() << " : " << it.second->get_name() << " (" << it.second->get_type() << ")" << std::endl;
...
Finally we print out all gate types with its corresponding number of occurences and return the resulting string.
As HAL also brings a python shell for scripted analysis with it, you might want to expose your plugin to this environment. For this purpose, we use pybind11. The actual mapping is done in the python/python_definitions.cpp
file.
// python/python_definitions.cpp
#include "core/log.h"
#include "core/utils.h"
#include "netlist/gate.h"
#include "netlist/net.h"
#include "netlist/netlist.h"
#include "pybind11/operators.h"
#include "pybind11/pybind11.h"
#include "pybind11/stl.h"
#include "pybind11/stl_bind.h"
#include "plugin_netlist_statistics.h"
namespace py = pybind11;
#ifdef PYBIND11_MODULE
PYBIND11_MODULE(libnetlist_statistics, m)
{
m.doc() = "hal netlist_statistics python bindings";
#else
PYBIND11_PLUGIN(libnetlist_statistics)
{
py::module m("libnetlist_statistics", "hal netlist_statistics python bindings");
#endif // ifdef PYBIND11_MODULE
py::implicitly_convertible<std::string, hal::path>();
py::class_<plugin_netlist_statistics, std::shared_ptr<plugin_netlist_statistics>, i_base>(m, "netlist_statistics")
.def(py::init<>())
.def_property_readonly("name", &plugin_netlist_statistics::get_name)
.def_property_readonly("version", &plugin_netlist_statistics::get_version)
.def("get_cli_options", &plugin_netlist_statistics::get_cli_options)
.def("handle_cli_call", &plugin_netlist_statistics::handle_cli_call)
.def_static("get_statistics", &plugin_netlist_statistics::get_statistics, py::arg("netlist"));
#ifndef PYBIND11_MODULE
return m.ptr();
#endif // PYBIND11_MODULE
}
Here we map the plugin_netlist_statistics
C++ class to the netlist_statistics
python class. For further information on how to define mappings see the pybind11 documentation.
To build this plugin using HAL, we use the CMakeLists.txt
from the main HAL project. As mentioned above, you can also build the example plugin on a system with HAL installed. For this purpose, the CMakeLists.txt
of our plugin file adds several directives we will not discuss in this tutorial.
# CMakeLists.txt
...
# Define the option to enable or disable the build of this plugin by a cmake flag.
option(PL_NETLIST_STATISTICS "PL_NETLIST_STATISTICS" ON)
if(PL_NETLIST_STATISTICS OR BUILD_ALL_PLUGINS)
# Define the Header and sources files
set(netlist_statistics_INC
${CMAKE_CURRENT_SOURCE_DIR}/include/plugin_netlist_statistics.h
)
set(netlist_statistics_SRC
${CMAKE_CURRENT_SOURCE_DIR}/src/plugin_netlist_statistics.cpp
${CMAKE_CURRENT_SOURCE_DIR}/python/python_definitions.cpp
)
hal_add_plugin(netlist_statistics
SHARED
SOURCES ${netlist_statistics_SRC}
HEADER ${netlist_statistics_INC})
endif()
And that's it. Now you can call your plugin by for example:
hal -i examples/fsm.v --netlist_statistics
Alternatively, you can use it via the HAL python interpreter:
>>> n = netlist_factory.load_netlist("examples/fsm.v", "verilog", "EXAMPLE_LIB")
>>> from hal_plugins.libnetlist_statistics import netlist_statistics
>>> stat = netlist_statistics()
>>> stat.get_statistics(n)
'1 : CLK_BUF_BUF_inst_inst (BUF)\n2 : CLK_BUF_inst_inst (BUF)\n3 : FSM_sequential_STATE_REG_0_i_2_inst (LUT6)\n4 : FSM_sequential_STATE_REG_0_i_3_inst (LUT6)\n5 : FSM_sequential_STATE_REG_1_i_2_inst (LUT6)\n6 : FSM_sequential_STATE_REG_1_i_3_inst (LUT6)\n7 : FSM_sequential_STATE_REG_reg_0_inst (FFR)\n8 : FSM_sequential_STATE_REG_reg_0_i_1_inst (MUX)\n9 : FSM_sequential_STATE_REG_reg_1_inst (FFR)\n10 : GND_inst (GND)\n11 : FSM_sequential_STATE_REG_reg_1_i_1_inst (MUX)\n12 : INPUT_BUF_0_inst_inst (BUF)\n13 : INPUT_BUF_1_inst_inst (BUF)\n14 : INPUT_BUF_2_inst_inst (BUF)\n15 : INPUT_BUF_3_inst_inst (BUF)\n16 : INPUT_BUF_4_inst_inst (BUF)\n17 : OUTPUT_BUF_0_inst_inst (BUF)\n18 : OUTPUT_BUF_0_inst_i_1_inst (LUT1)\n19 : OUTPUT_BUF_1_inst_inst (BUF)\n20 : OUTPUT_BUF_1_inst_i_1_inst (LUT2)\n21 : VCC_inst (VCC)\n##########\n[BUF] = 9\n[FFR] = 2\n[GND] = 1\n[LUT1] = 1\n[LUT2] = 1\n[LUT6] = 4\n[MUX] = 2\n[VCC] = 1\n##########\n'
If you have any questions, feel free to ask them on our Slack channel: