Writing extensions

This chapter contains some bits and pieces of information about programming yosys extensions. Don’t be afraid to ask questions on the YosysHQ Slack.

The guidelines/ directory of the Yosys source code contains notes on various aspects of Yosys development. In particular, the files GettingStarted and CodingStyle may be of interest.

Quick guide

Code examples from this section are included in the docs/source/code_examples/extensions directory of the Yosys source code.

Program components and data formats

See The RTL Intermediate Language (RTLIL) document for more information about the internal data storage format used in Yosys and the classes that it provides.

This document will focus on the much simpler version of RTLIL left after the commands proc and memory (or memory -nomap):

../../_images/simplified_rtlil.svg

Fig. 58 Simplified RTLIL entity-relationship diagram without memories and processes

It is possible to only work on this simpler version:

for (RTLIL::Module *module : design->selected_modules() {
    if (module->has_memories_warn() || module->has_processes_warn())
        continue;
    ....
}

When trying to understand what a command does, creating a small test case to look at the output of dump and show before and after the command has been executed can be helpful. Selections has more information on using these commands.

Creating a command

Let’s create a very simple test command which prints the arguments we called it with, and lists off the current design’s modules.

Listing 92 Example command my_cmd from my_cmd.cc
#include "kernel/yosys.h"
USING_YOSYS_NAMESPACE

struct MyPass : public Pass {
    MyPass() : Pass("my_cmd", "just a simple test") { }
    void execute(std::vector<std::string> args, RTLIL::Design *design) override
    {
        log("Arguments to my_cmd:\n");
        for (auto &arg : args)
            log("  %s\n", arg.c_str());

        log("Modules in current design:\n");
        for (auto mod : design->modules())
            log("  %s (%d wires, %d cells)\n", log_id(mod),
                    GetSize(mod->wires()), GetSize(mod->cells()));
    }
} MyPass;

Note that we are making a global instance of a class derived from Yosys::Pass, which we get by including kernel/yosys.h.

Compiling to a plugin

Yosys can be extended by adding additional C++ code to the Yosys code base, or by loading plugins into Yosys. For maintainability it is generally recommended to create plugins.

The following command compiles our example my_cmd to a Yosys plugin:

yosys-config --exec --cxx --cxxflags --ldflags \
-o my_cmd.so -shared my_cmd.cc --ldlibs

Or shorter:

yosys-config --build my_cmd.so my_cmd.cc

Running Yosys with the -m option allows the plugin to be used. Here’s a quick example that also uses the -p option to run my_cmd foo bar.

$ yosys -m ./my_cmd.so -p 'my_cmd foo bar'

-- Running command `my_cmd foo bar' --
Arguments to my_cmd:
  my_cmd
  foo
  bar
Modules in current design:

Creating modules from scratch

Let’s create the following module using the RTLIL API:

Listing 93 absval_ref.v
module absval_ref(input signed [3:0] a, output [3:0] y);
	assign y = a[3] ? -a : a;
endmodule

We’ll do the same as before and format it as a a Yosys::Pass.

Listing 94 test1 - creating the absval module, from my_cmd.cc
struct Test1Pass : public Pass {
    Test1Pass() : Pass("test1", "creating the absval module") { }
    void execute(std::vector<std::string>, RTLIL::Design *design) override
    {
        if (design->has("\\absval") != 0)
            log_error("A module with the name absval already exists!\n");

        RTLIL::Module *module = design->addModule("\\absval");
        log("Name of this module: %s\n", log_id(module));

        RTLIL::Wire *a = module->addWire("\\a", 4);
        a->port_input = true;
        a->port_id = 1;

        RTLIL::Wire *y = module->addWire("\\y", 4);
        y->port_output = true;
        y->port_id = 2;

        RTLIL::Wire *a_inv = module->addWire(NEW_ID, 4);
        module->addNeg(NEW_ID, a, a_inv, true);
        module->addMux(NEW_ID, a, a_inv, RTLIL::SigSpec(a, 3), y);

	module->fixup_ports();
    }
} Test1Pass;
$ yosys -m ./my_cmd.so -p 'test1' -Q

-- Running command `test1' --
Name of this module: absval

And if we look at the schematic for this new module we see the following:

../../_images/test1.svg

Fig. 59 Output of yosys -m ./my_cmd.so -p 'test1; show'

Modifying modules

Most commands modify existing modules, not create new ones.

When modifying existing modules, stick to the following DOs and DON’Ts:

  • Do not remove wires. Simply disconnect them and let a successive clean command worry about removing it.

  • Use module->fixup_ports() after changing the port_* properties of wires.

  • You can safely remove cells or change the connections property of a cell, but be careful when changing the size of the SigSpec connected to a cell port.

  • Use the SigMap helper class (see next section) when you need a unique handle for each signal bit.

Using the SigMap helper class

Consider the following module:

Listing 95 sigmap_test.v
module test(input a, output x, y);
	assign x = a, y = a;
endmodule

In this case a, x, and y are all different names for the same signal. However:

RTLIL::SigSpec a(module->wire("\\a")), x(module->wire("\\x")),
                                       y(module->wire("\\y"));
log("%d %d %d\n", a == x, x == y, y == a); // will print "0 0 0"

The SigMap helper class can be used to map all such aliasing signals to a unique signal from the group (usually the wire that is directly driven by a cell or port).

SigMap sigmap(module);
log("%d %d %d\n", sigmap(a) == sigmap(x), sigmap(x) == sigmap(y),
                  sigmap(y) == sigmap(a)); // will print "1 1 1"

Printing log messages

The log() function is a printf()-like function that can be used to create log messages.

Use log_signal() to create a C-string for a SigSpec object:

log("Mapped signal x: %s\n", log_signal(sigmap(x)));

The pointer returned by log_signal() is automatically freed by the log framework at a later time.

Use log_id() to create a C-string for an RTLIL::IdString:

log("Name of this module: %s\n", log_id(module->name));

Use log_header() and log_push()/log_pop() to structure log messages:

log_header(design, "Doing important stuff!\n");
log_push();
for (int i = 0; i < 10; i++)
    log("Log message #%d.\n", i);
log_pop();

Error handling

Use log_error() to report a non-recoverable error:

if (design->modules.count(module->name) != 0)
    log_error("A module with the name %s already exists!\n",
               RTLIL::id2cstr(module->name));

Use log_cmd_error() to report a recoverable error:

if (design->selection_stack.back().empty())
    log_cmd_error("This command can't operator on an empty selection!\n");

Use log_assert() and log_abort() instead of assert() and abort().

The “stubnets” example module

The following is the complete code of the “stubnets” example module. It is included in the Yosys source distribution under docs/source/code_examples/stubnets.

Listing 96 stubnets.cc
  1// This is free and unencumbered software released into the public domain.
  2//
  3// Anyone is free to copy, modify, publish, use, compile, sell, or
  4// distribute this software, either in source code form or as a compiled
  5// binary, for any purpose, commercial or non-commercial, and by any
  6// means.
  7
  8#include "kernel/yosys.h"
  9#include "kernel/sigtools.h"
 10
 11#include <string>
 12#include <map>
 13#include <set>
 14
 15USING_YOSYS_NAMESPACE
 16PRIVATE_NAMESPACE_BEGIN
 17
 18// this function is called for each module in the design
 19static void find_stub_nets(RTLIL::Design *design, RTLIL::Module *module, bool report_bits)
 20{
 21	// use a SigMap to convert nets to a unique representation
 22	SigMap sigmap(module);
 23
 24	// count how many times a single-bit signal is used
 25	std::map<RTLIL::SigBit, int> bit_usage_count;
 26
 27	// count output lines for this module (needed only for summary output at the end)
 28	int line_count = 0;
 29
 30	log("Looking for stub wires in module %s:\n", RTLIL::id2cstr(module->name));
 31
 32	// For all ports on all cells
 33	for (auto &cell_iter : module->cells_)
 34	for (auto &conn : cell_iter.second->connections())
 35	{
 36		// Get the signals on the port
 37		// (use sigmap to get a uniqe signal name)
 38		RTLIL::SigSpec sig = sigmap(conn.second);
 39
 40		// add each bit to bit_usage_count, unless it is a constant
 41		for (auto &bit : sig)
 42			if (bit.wire != NULL)
 43				bit_usage_count[bit]++;
 44	}
 45
 46	// for each wire in the module
 47	for (auto &wire_iter : module->wires_)
 48	{
 49		RTLIL::Wire *wire = wire_iter.second;
 50
 51		// .. but only selected wires
 52		if (!design->selected(module, wire))
 53			continue;
 54
 55		// add +1 usage if this wire actually is a port
 56		int usage_offset = wire->port_id > 0 ? 1 : 0;
 57
 58		// we will record which bits of the (possibly multi-bit) wire are stub signals
 59		std::set<int> stub_bits;
 60
 61		// get a signal description for this wire and split it into separate bits
 62		RTLIL::SigSpec sig = sigmap(wire);
 63
 64		// for each bit (unless it is a constant):
 65		// check if it is used at least two times and add to stub_bits otherwise
 66		for (int i = 0; i < GetSize(sig); i++)
 67			if (sig[i].wire != NULL && (bit_usage_count[sig[i]] + usage_offset) < 2)
 68				stub_bits.insert(i);
 69
 70		// continue if no stub bits found
 71		if (stub_bits.size() == 0)
 72			continue;
 73
 74		// report stub bits and/or stub wires, don't report single bits
 75		// if called with report_bits set to false.
 76		if (GetSize(stub_bits) == GetSize(sig)) {
 77			log("  found stub wire: %s\n", RTLIL::id2cstr(wire->name));
 78		} else {
 79			if (!report_bits)
 80				continue;
 81			log("  found wire with stub bits: %s [", RTLIL::id2cstr(wire->name));
 82			for (int bit : stub_bits)
 83				log("%s%d", bit == *stub_bits.begin() ? "" : ", ", bit);
 84			log("]\n");
 85		}
 86
 87		// we have outputted a line, increment summary counter
 88		line_count++;
 89	}
 90
 91	// report summary
 92	if (report_bits)
 93		log("  found %d stub wires or wires with stub bits.\n", line_count);
 94	else
 95		log("  found %d stub wires.\n", line_count);
 96}
 97
 98// each pass contains a singleton object that is derived from Pass
 99struct StubnetsPass : public Pass {
100	StubnetsPass() : Pass("stubnets") { }
101	void execute(std::vector<std::string> args, RTLIL::Design *design) override
102	{
103		// variables to mirror information from passed options
104		bool report_bits = 0;
105
106		log_header(design, "Executing STUBNETS pass (find stub nets).\n");
107
108		// parse options
109		size_t argidx;
110		for (argidx = 1; argidx < args.size(); argidx++) {
111			std::string arg = args[argidx];
112			if (arg == "-report_bits") {
113				report_bits = true;
114				continue;
115			}
116			break;
117		}
118
119		// handle extra options (e.g. selection)
120		extra_args(args, argidx, design);
121
122		// call find_stub_nets() for each module that is either
123		// selected as a whole or contains selected objects.
124		for (auto &it : design->modules_)
125			if (design->selected_module(it.first))
126				find_stub_nets(design, it.second, report_bits);
127	}
128} StubnetsPass;
129
130PRIVATE_NAMESPACE_END
Listing 97 Makefile
 1.PHONY: all dots
 2all: dots
 3dots:
 4
 5.PHONY: test
 6test: stubnets.so
 7	yosys -ql test1.log -m ./stubnets.so test.v -p "stubnets"
 8	yosys -ql test2.log -m ./stubnets.so test.v -p "opt; stubnets"
 9	yosys -ql test3.log -m ./stubnets.so test.v -p "techmap; opt; stubnets -report_bits"
10	tail test1.log test2.log test3.log
11
12stubnets.so: stubnets.cc
13	yosys-config --exec --cxx --cxxflags --ldflags -o $@ -shared $^ --ldlibs
14
15.PHONY: clean
16clean:
17	rm -f test1.log test2.log test3.log
18	rm -f stubnets.so stubnets.d
Listing 98 test.v
1module uut(in1, in2, in3, out1, out2);
2
3input [8:0] in1, in2, in3;
4output [8:0] out1, out2;
5
6assign out1 = in1 + in2 + (in3 >> 4);
7
8endmodule