Skip to content

Creating a Standard Plugin

SJulianS edited this page Feb 19, 2021 · 1 revision

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 Netlist Statistics Plugin

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.

Quick Start

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.

The Plugin Class plugin_netlist_statistics

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 the i_base interface and adds one function get_cli_options() to define the CLI parameter and handle_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();
}

get_plugin_instance

This function allows hal to load the plugin dynamically. Here you need to return an instance of your main plugin object.

get_cli_options

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", {""});

handle_cli_call

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.

get_statistics

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.

Adding python bindings

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.

CMakeLists.txt

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()

We are done

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: Slack

Clone this wiki locally