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
):
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.
#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:
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
.
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:
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 theport_*
properties of wires.You can safely remove cells or change the
connections
property of a cell, but be careful when changing the size of theSigSpec
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:
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
.
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
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
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