3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2025-06-06 06:03:23 +00:00

Reorganising documentation

Also changing to furo theme.
This commit is contained in:
Krystine Sherwin 2023-08-03 09:20:29 +12:00
parent 4f1cd66829
commit 045c04096e
No known key found for this signature in database
40 changed files with 661 additions and 1282 deletions

View file

@ -1,410 +0,0 @@
.. _chapter:sota:
Evaluation of other OSS Verilog Synthesis Tools
===============================================
In this appendix [1]_ the existing FOSS Verilog synthesis tools [2]_ are
evaluated. Extremely limited or application specific tools (e.g. pure
Verilog Netlist parsers) as well as Verilog simulators are not included.
These existing solutions are tested using a set of representative
Verilog code snippets. It is shown that no existing FOSS tool implements
even close to a sufficient subset of Verilog to be usable as synthesis
tool for a wide range existing Verilog code.
The packages evaluated are:
- Icarus Verilog [3]_
- Verilog-to-Routing (VTR) / Odin-II
:cite:p:`vtr2012}`:raw-latex:`\cite{Odin`
- HDL Analyzer and Netlist Architect (HANA)
- Verilog front-end to VIS (vl2mv) :cite:p:`Cheng93vl2mv:a`
In each of the following sections Verilog modules that test a certain
Verilog language feature are presented and the support for these
features is tested in all the tools mentioned above. It is evaluated
whether the tools under test successfully generate netlists for the
Verilog input and whether these netlists match the simulation behavior
of the designs using testbenches.
All test cases are verified to be synthesizeable using Xilinx XST from
the Xilinx WebPACK suite.
Trivial features such as support for simple structural Verilog are not
explicitly tested.
Vl2mv and Odin-II generate output in the BLIF (Berkeley Logic
Interchange Format) and BLIF-MV (an extended version of BLIF) formats
respectively. ABC is used to convert this output to Verilog for
verification using testbenches.
Icarus Verilog generates EDIF (Electronic Design Interchange Format)
output utilizing LPM (Library of Parameterized Modules) cells. The EDIF
files are converted to Verilog using edif2ngd and netgen from Xilinx
WebPACK. A hand-written implementation of the LPM cells utilized by the
generated netlists is used for verification.
Following these functional tests, a quick analysis of the extensibility
of the tools under test is provided in a separate section.
The last section of this chapter finally concludes these series of
evaluations with a summary of the results.
.. code:: verilog
:number-lines:
module uut_always01(clock,
reset, count);
input clock, reset;
output [3:0] count;
reg [3:0] count;
always @(posedge clock)
count <= reset ?
0 : count + 1;
endmodule
.. code:: verilog
module uut_always02(clock,
reset, count);
input clock, reset;
output [3:0] count;
reg [3:0] count;
always @(posedge clock) begin
count <= count + 1;
if (reset)
count <= 0;
end
endmodule
[fig:StateOfTheArt_always12]
.. code:: verilog
:number-lines:
module uut_always03(clock, in1, in2, in3, in4, in5, in6, in7,
out1, out2, out3);
input clock, in1, in2, in3, in4, in5, in6, in7;
output out1, out2, out3;
reg out1, out2, out3;
always @(posedge clock) begin
out1 = in1;
if (in2)
out1 = !out1;
out2 <= out1;
if (in3)
out2 <= out2;
if (in4)
if (in5)
out3 <= in6;
else
out3 <= in7;
out1 = out1 ^ out2;
end
endmodule
[fig:StateOfTheArt_always3]
.. _sec:blocking_nonblocking:
Always blocks and blocking vs. nonblocking assignments
------------------------------------------------------
The "always"-block is one of the most fundamental non-trivial Verilog
language features. It can be used to model a combinatorial path (with
optional registers on the outputs) in a way that mimics a regular
programming language.
Within an always block, if- and case-statements can be used to model
multiplexers. Blocking assignments (:math:`=`) and nonblocking
assignments (:math:`<=`) are used to populate the leaf-nodes of these
multiplexer trees. Unassigned leaf-nodes default to feedback paths that
cause the output register to hold the previous value. More advanced
synthesis tools often convert these feedback paths to register enable
signals or even generate circuits with clock gating.
Registers assigned with nonblocking assignments (:math:`<=`) behave
differently from variables in regular programming languages: In a
simulation they are not updated immediately after being assigned.
Instead the right-hand sides are evaluated and the results stored in
temporary memory locations. After all pending updates have been prepared
in this way they are executed, thus yielding semi-parallel execution of
all nonblocking assignments.
For synthesis this means that every occurrence of that register in an
expression addresses the output port of the corresponding register
regardless of the question whether the register has been assigned a new
value in an earlier command in the same always block. Therefore with
nonblocking assignments the order of the assignments has no effect on
the resulting circuit as long as the left-hand sides of the assignments
are unique.
The three example codes in
:numref:`Fig. %s <fig:StateOfTheArt_always12>`
and :numref:`Fig. %s <fig:StateOfTheArt_always3>`
use all these features and can thus be used to test the synthesis tools
capabilities to synthesize always blocks correctly.
The first example is only using the most fundamental Verilog features.
All tools under test were able to successfully synthesize this design.
.. code:: verilog
:number-lines:
module uut_arrays01(clock, we, addr, wr_data, rd_data);
input clock, we;
input [3:0] addr, wr_data;
output [3:0] rd_data;
reg [3:0] rd_data;
reg [3:0] memory [15:0];
always @(posedge clock) begin
if (we)
memory[addr] <= wr_data;
rd_data <= memory[addr];
end
endmodule
[fig:StateOfTheArt_arrays]
The 2nd example is functionally identical to the 1st one but is using an
if-statement inside the always block. Odin-II fails to synthesize it and
instead produces the following error message:
::
ERROR: (File: always02.v) (Line number: 13)
You've defined the driver "count~0" twice
Vl2mv does not produce an error message but outputs an invalid synthesis
result that is not using the reset input at all.
Icarus Verilog also doesn't produce an error message but generates an
invalid output for this 2nd example. The code generated by Icarus
Verilog only implements the reset path for the count register,
effectively setting the output to constant 0.
So of all tools under test only HANA was able to create correct
synthesis results for the 2nd example.
The 3rd example is using blocking and nonblocking assignments and many
if statements. Odin also fails to synthesize this example:
::
ERROR: (File: always03.v) (Line number: 8)
ODIN doesn't handle blocking statements in Sequential blocks
HANA, Icarus Verilog and vl2mv create invalid synthesis results for the
3rd example.
So unfortunately none of the tools under test provide a complete and
correct implementation of blocking and nonblocking assignments.
Arrays for memory modelling
---------------------------
Verilog arrays are part of the synthesizeable subset of Verilog and are
commonly used to model addressable memory. The Verilog code in
:numref:`Fig. %s <fig:StateOfTheArt_arrays>`
demonstrates this by implementing a single port memory.
For this design HANA, vl2m and ODIN-II generate error messages
indicating that arrays are not supported.
.. code:: verilog
:number-lines:
module uut_forgen01(a, y);
input [4:0] a;
output y;
integer i, j;
reg [31:0] lut;
initial begin
for (i = 0; i < 32; i = i+1) begin
lut[i] = i > 1;
for (j = 2; j*j <= i; j = j+1)
if (i % j == 0)
lut[i] = 0;
end
end
assign y = lut[a];
endmodule
[fig:StateOfTheArt_for]
Icarus Verilog produces an invalid output that is using the address only
for reads. Instead of using the address input for writes, the generated
design simply loads the data to all memory locations whenever the
write-enable input is active, effectively turning the design into a
single 4-bit D-Flip-Flop with enable input.
As all tools under test already fail this simple test, there is nothing
to gain by continuing tests on this aspect of Verilog synthesis such as
synthesis of dual port memories, correct handling of write collisions,
and so forth.
.. code:: verilog
:number-lines:
module uut_forgen02(a, b, cin, y, cout);
parameter WIDTH = 8;
input [WIDTH-1:0] a, b;
input cin;
output [WIDTH-1:0] y;
output cout;
genvar i;
wire [WIDTH-1:0] carry;
generate
for (i = 0; i < WIDTH; i=i+1) begin:adder
wire [2:0] D;
assign D[1:0] = { a[i], b[i] };
if (i == 0) begin:chain
assign D[2] = cin;
end else begin:chain
assign D[2] = carry[i-1];
end
assign y[i] = ^D;
assign carry[i] = &D[1:0] | (^D[1:0] & D[2]);
end
endgenerate
assign cout = carry[WIDTH-1];
endmodule
[fig:StateOfTheArt_gen]
For-loops and generate blocks
-----------------------------
For-loops and generate blocks are more advanced Verilog features. These
features allow the circuit designer to add program code to her design
that is evaluated during synthesis to generate (parts of) the circuits
description; something that could only be done using a code generator
otherwise.
For-loops are only allowed in synthesizeable Verilog if they can be
completely unrolled. Then they can be a powerful tool to generate array
logic or static lookup tables. The code in
:numref:`Fig. %s <fig:StateOfTheArt_for>` generates a
circuit that tests a 5 bit value for being a prime number using a static
lookup table.
Generate blocks can be used to model array logic in complex parametric
designs. The code in
:numref:`Fig. %s <fig:StateOfTheArt_gen>` implements a
ripple-carry adder with parametric width from simple assign-statements
and logic operations using a Verilog generate block.
All tools under test failed to synthesize both test cases. HANA creates
invalid output in both cases. Icarus Verilog creates invalid output for
the first test and fails with an error for the second case. The other
two tools fail with error messages for both tests.
Extensibility
-------------
This section briefly discusses the extensibility of the tools under test
and their internal data- and control-flow. As all tools under test
already failed to synthesize simple Verilog always-blocks correctly, not
much resources have been spent on evaluating the extensibility of these
tools and therefore only a very brief discussion of the topic is
provided here.
HANA synthesizes for a built-in library of standard cells using two
passes over an AST representation of the Verilog input. This approach
executes fast but limits the extensibility as everything happens in only
two comparable complex AST walks and there is no universal intermediate
representation that is flexible enough to be used in arbitrary
optimizations.
Odin-II and vl2m are both front ends to existing synthesis flows. As
such they only try to quickly convert the Verilog input into the
internal representation of their respective flows (BLIF). So
extensibility is less of an issue here as potential extensions would
likely be implemented in other components of the flow.
Icarus Verilog is clearly designed to be a simulation tool rather than a
synthesis tool. The synthesis part of Icarus Verilog is an ad-hoc add-on
to Icarus Verilog that aims at converting an internal representation
that is meant for generation of a virtual machine based simulation code
to netlists.
Summary and Outlook
-------------------
Table \ :numref:`tab:StateOfTheArt_sum` summarizes
the tests performed. Clearly none of the tools under test make a serious
attempt at providing a feature-complete implementation of Verilog. It
can be argued that Odin-II performed best in the test as it never
generated incorrect code but instead produced error messages indicating
that unsupported Verilog features where used in the Verilog input.
In conclusion, to the best knowledge of the author, there is no FOSS
Verilog synthesis tool other than Yosys that is anywhere near feature
completeness and therefore there is no other candidate for a generic
Verilog front end and/or synthesis framework to be used as a basis for
custom synthesis tools.
Yosys could also replace vl2m and/or Odin-II in their respective flows
or function as a pre-compiler that can translate full-featured Verilog
code to the simple subset of Verilog that is understood by vl2m and
Odin-II.
Yosys is designed for extensibility. It can be used as-is to synthesize
Verilog code to netlists, but its main purpose is to be used as basis
for custom tools. Yosys is structured in a language dependent Verilog
front end and language independent synthesis code (which is in itself
structured in independent passes). This architecture will simplify
implementing additional HDL front ends and/or additional synthesis
passes.
Chapter \ :numref:`<CHAPTER_eval>` contains a more detailed
evaluation of Yosys using real-world designs that are far out of reach
for any of the other tools discussed in this appendix.
…passed 2em …produced error 2em :math:`\skull` …incorrect output
[tab:StateOfTheArt_sum]
.. [1]
This appendix is an updated version of an unpublished student
research paper. :cite:p:`VerilogFossEval`
.. [2]
To the author's best knowledge, all relevant tools that existed at
the time of this writing are included. But as there is no formal
channel through which such tools are published it is hard to give any
guarantees in that matter.
.. [3]
Icarus Verilog is mainly a simulation tool but also supported
synthesis up to version 0.8. Therefore version 0.8.7 is used for this
evaluation.)

View file

@ -1,298 +0,0 @@
.. _chapter:textrtlil:
RTLIL text representation
=========================
This appendix documents the text representation of RTLIL in extended Backus-Naur
form (EBNF).
The grammar is not meant to represent semantic limitations. That is, the grammar
is "permissive", and later stages of processing perform more rigorous checks.
The grammar is also not meant to represent the exact grammar used in the RTLIL
frontend, since that grammar is specific to processing by lex and yacc, is even
more permissive, and is somewhat less understandable than simple EBNF notation.
Finally, note that all statements (rules ending in ``-stmt``) terminate in an
end-of-line. Because of this, a statement cannot be broken into multiple lines.
Lexical elements
----------------
Characters
~~~~~~~~~~
An RTLIL file is a stream of bytes. Strictly speaking, a "character" in an RTLIL
file is a single byte. The lexer treats multi-byte encoded characters as
consecutive single-byte characters. While other encodings *may* work, UTF-8 is
known to be safe to use. Byte order marks at the beginning of the file will
cause an error.
ASCII spaces (32) and tabs (9) separate lexer tokens.
A ``nonws`` character, used in identifiers, is any character whose encoding
consists solely of bytes above ASCII space (32).
An ``eol`` is one or more consecutive ASCII newlines (10) and carriage returns
(13).
Identifiers
~~~~~~~~~~~
There are two types of identifiers in RTLIL:
- Publically visible identifiers
- Auto-generated identifiers
.. code:: BNF
<id> ::= <public-id> | <autogen-id>
<public-id> ::= \ <nonws>+
<autogen-id> ::= $ <nonws>+
Values
~~~~~~
A *value* consists of a width in bits and a bit representation, most
significant bit first. Bits may be any of:
- ``0``: A logic zero value
- ``1``: A logic one value
- ``x``: An unknown logic value (or don't care in case patterns)
- ``z``: A high-impedance value (or don't care in case patterns)
- ``m``: A marked bit (internal use only)
- ``-``: A don't care value
An *integer* is simply a signed integer value in decimal format. **Warning:**
Integer constants are limited to 32 bits. That is, they may only be in the range
:math:`[-2147483648, 2147483648)`. Integers outside this range will result in an
error.
.. code:: BNF
<value> ::= <decimal-digit>+ ' <binary-digit>*
<decimal-digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
<binary-digit> ::= 0 | 1 | x | z | m | -
<integer> ::= -? <decimal-digit>+
Strings
~~~~~~~
A string is a series of characters delimited by double-quote characters. Within
a string, any character except ASCII NUL (0) may be used. In addition, certain
escapes can be used:
- ``\n``: A newline
- ``\t``: A tab
- ``\ooo``: A character specified as a one, two, or three digit octal value
All other characters may be escaped by a backslash, and become the following
character. Thus:
- ``\\``: A backslash
- ``\"``: A double-quote
- ``\r``: An 'r' character
Comments
~~~~~~~~
A comment starts with a ``#`` character and proceeds to the end of the line. All
comments are ignored.
File
----
A file consists of an optional autoindex statement followed by zero or more
modules.
.. code:: BNF
<file> ::= <autoidx-stmt>? <module>*
Autoindex statements
~~~~~~~~~~~~~~~~~~~~
The autoindex statement sets the global autoindex value used by Yosys when it
needs to generate a unique name, e.g. ``flattenN``. The N part is filled with
the value of the global autoindex value, which is subsequently incremented. This
global has to be dumped into RTLIL, otherwise e.g. dumping and running a pass
would have different properties than just running a pass on a warm design.
.. code:: BNF
<autoidx-stmt> ::= autoidx <integer> <eol>
Modules
~~~~~~~
Declares a module, with zero or more attributes, consisting of zero or more
wires, memories, cells, processes, and connections.
.. code:: BNF
<module> ::= <attr-stmt>* <module-stmt> <module-body> <module-end-stmt>
<module-stmt> ::= module <id> <eol>
<module-body> ::= (<param-stmt>
| <wire>
| <memory>
| <cell>
| <process>)*
<param-stmt> ::= parameter <id> <constant>? <eol>
<constant> ::= <value> | <integer> | <string>
<module-end-stmt> ::= end <eol>
Attribute statements
~~~~~~~~~~~~~~~~~~~~
Declares an attribute with the given identifier and value.
.. code:: BNF
<attr-stmt> ::= attribute <id> <constant> <eol>
Signal specifications
~~~~~~~~~~~~~~~~~~~~~
A signal is anything that can be applied to a cell port, i.e. a constant value,
all bits or a selection of bits from a wire, or concatenations of those.
**Warning:** When an integer constant is a sigspec, it is always 32 bits wide,
2's complement. For example, a constant of :math:`-1` is the same as
``32'11111111111111111111111111111111``, while a constant of :math:`1` is the
same as ``32'1``.
See :numref:`Sec. %s <sec:rtlil_sigspec>` for an overview of signal
specifications.
.. code:: BNF
<sigspec> ::= <constant>
| <wire-id>
| <sigspec> [ <integer> (:<integer>)? ]
| { <sigspec>* }
Connections
~~~~~~~~~~~
Declares a connection between the given signals.
.. code:: BNF
<conn-stmt> ::= connect <sigspec> <sigspec> <eol>
Wires
~~~~~
Declares a wire, with zero or more attributes, with the given identifier and
options in the enclosing module.
See :numref:`Sec. %s <sec:rtlil_cell_wire>` for an overview of wires.
.. code:: BNF
<wire> ::= <attr-stmt>* <wire-stmt>
<wire-stmt> ::= wire <wire-option>* <wire-id> <eol>
<wire-id> ::= <id>
<wire-option> ::= width <integer>
| offset <integer>
| input <integer>
| output <integer>
| inout <integer>
| upto
| signed
Memories
~~~~~~~~
Declares a memory, with zero or more attributes, with the given identifier and
options in the enclosing module.
See :numref:`Sec. %s <sec:rtlil_memory>` for an overview of memory cells, and
:numref:`Sec. %s <sec:memcells>` for details about memory cell types.
.. code:: BNF
<memory> ::= <attr-stmt>* <memory-stmt>
<memory-stmt> ::= memory <memory-option>* <id> <eol>
<memory-option> ::= width <integer>
| size <integer>
| offset <integer>
Cells
~~~~~
Declares a cell, with zero or more attributes, with the given identifier and
type in the enclosing module.
Cells perform functions on input signals. See :numref:`Chap. %s
<chapter:celllib>` for a detailed list of cell types.
.. code:: BNF
<cell> ::= <attr-stmt>* <cell-stmt> <cell-body-stmt>* <cell-end-stmt>
<cell-stmt> ::= cell <cell-type> <cell-id> <eol>
<cell-id> ::= <id>
<cell-type> ::= <id>
<cell-body-stmt> ::= parameter (signed | real)? <id> <constant> <eol>
| connect <id> <sigspec> <eol>
<cell-end-stmt> ::= end <eol>
Processes
~~~~~~~~~
Declares a process, with zero or more attributes, with the given identifier in
the enclosing module. The body of a process consists of zero or more
assignments, exactly one switch, and zero or more syncs.
See :numref:`Sec. %s <sec:rtlil_process>` for an overview of processes.
.. code:: BNF
<process> ::= <attr-stmt>* <proc-stmt> <process-body> <proc-end-stmt>
<proc-stmt> ::= process <id> <eol>
<process-body> ::= <assign-stmt>* <switch>? <assign-stmt>* <sync>*
<assign-stmt> ::= assign <dest-sigspec> <src-sigspec> <eol>
<dest-sigspec> ::= <sigspec>
<src-sigspec> ::= <sigspec>
<proc-end-stmt> ::= end <eol>
Switches
~~~~~~~~
Switches test a signal for equality against a list of cases. Each case specifies
a comma-separated list of signals to check against. If there are no signals in
the list, then the case is the default case. The body of a case consists of zero
or more switches and assignments. Both switches and cases may have zero or more
attributes.
.. code:: BNF
<switch> ::= <switch-stmt> <case>* <switch-end-stmt>
<switch-stmt> := <attr-stmt>* switch <sigspec> <eol>
<case> ::= <attr-stmt>* <case-stmt> <case-body>
<case-stmt> ::= case <compare>? <eol>
<compare> ::= <sigspec> (, <sigspec>)*
<case-body> ::= (<switch> | <assign-stmt>)*
<switch-end-stmt> ::= end <eol>
Syncs
~~~~~
Syncs update signals with other signals when an event happens. Such an event may
be:
- An edge or level on a signal
- Global clock ticks
- Initialization
- Always
.. code:: BNF
<sync> ::= <sync-stmt> <update-stmt>*
<sync-stmt> ::= sync <sync-type> <sigspec> <eol>
| sync global <eol>
| sync init <eol>
| sync always <eol>
<sync-type> ::= low | high | posedge | negedge | edge
<update-stmt> ::= update <dest-sigspec> <src-sigspec> <eol>

View file

@ -17,7 +17,7 @@ BigInt
The files in ``libs/bigint/`` provide a library for performing arithmetic with
arbitrary length integers. It is written by Matt McCutchen.
The BigInt library is used for evaluating constant expressions, e.g. using the
The BigInt library is used for evaluating constant expressions, e.g. using the
ConstEval class provided in kernel/consteval.h.
See also: http://mattmccutchen.net/bigint/

View file

@ -9,7 +9,7 @@ yosys-config
The yosys-config tool (an auto-generated shell-script) can be used to query
compiler options and other information needed for building loadable modules for
Yosys. See Sec. \ :numref:`chapter:prog` for details.
Yosys. See :ref:`chapter:prog` for details.
.. _sec:filterlib:
@ -17,7 +17,7 @@ yosys-filterlib
---------------
The yosys-filterlib tool is a small utility that can be used to strip or extract
information from a Liberty file. See :numref:`Sec. %s <sec:techmap_extern>` for
information from a Liberty file. See :ref:`sec:techmap_extern` for
details.
yosys-abc

View file

@ -0,0 +1,776 @@
.. role:: verilog(code)
:language: Verilog
.. _chapter:basics:
A primer on digital circuit synthesis
=====================================
This chapter contains a short introduction to the basic principles of digital
circuit synthesis.
Levels of abstraction
---------------------
Digital circuits can be represented at different levels of abstraction. During
the design process a circuit is usually first specified using a higher level
abstraction. Implementation can then be understood as finding a functionally
equivalent representation at a lower abstraction level. When this is done
automatically using software, the term synthesis is used.
So synthesis is the automatic conversion of a high-level representation of a
circuit to a functionally equivalent low-level representation of a circuit.
:numref:`Figure %s <fig:Basics_abstractions>` lists the different levels of
abstraction and how they relate to different kinds of synthesis.
.. figure:: ../../images/basics_abstractions.*
:class: width-helper
:name: fig:Basics_abstractions
Different levels of abstraction and synthesis.
Regardless of the way a lower level representation of a circuit is obtained
(synthesis or manual design), the lower level representation is usually verified
by comparing simulation results of the lower level and the higher level
representation [1]_. Therefore even if no synthesis is used, there must still
be a simulatable representation of the circuit in all levels to allow for
verification of the design.
Note: The exact meaning of terminology such as "High-Level" is of course not
fixed over time. For example the HDL "ABEL" was first introduced in 1985 as "A
High-Level Design Language for Programmable Logic Devices" :cite:p:`ABEL`, but
would not be considered a "High-Level Language" today.
System level
~~~~~~~~~~~~
The System Level abstraction of a system only looks at its biggest building
blocks like CPUs and computing cores. At this level the circuit is usually
described using traditional programming languages like C/C++ or Matlab.
Sometimes special software libraries are used that are aimed at simulation
circuits on the system level, such as SystemC.
Usually no synthesis tools are used to automatically transform a system level
representation of a circuit to a lower-level representation. But system level
design tools exist that can be used to connect system level building blocks.
The IEEE 1685-2009 standard defines the IP-XACT file format that can be used to
represent designs on the system level and building blocks that can be used in
such system level designs. :cite:p:`IP-XACT`
High level
~~~~~~~~~~
The high-level abstraction of a system (sometimes referred to as algorithmic
level) is also often represented using traditional programming languages, but
with a reduced feature set. For example when representing a design at the high
level abstraction in C, pointers can only be used to mimic concepts that can be
found in hardware, such as memory interfaces. Full featured dynamic memory
management is not allowed as it has no corresponding concept in digital
circuits.
Tools exist to synthesize high level code (usually in the form of C/C++/SystemC
code with additional metadata) to behavioural HDL code (usually in the form of
Verilog or VHDL code). Aside from the many commercial tools for high level
synthesis there are also a number of FOSS tools for high level synthesis .
Behavioural level
~~~~~~~~~~~~~~~~~
At the behavioural abstraction level a language aimed at hardware description
such as Verilog or VHDL is used to describe the circuit, but so-called
behavioural modelling is used in at least part of the circuit description. In
behavioural modelling there must be a language feature that allows for
imperative programming to be used to describe data paths and registers. This is
the always-block in Verilog and the process-block in VHDL.
In behavioural modelling, code fragments are provided together with a
sensitivity list; a list of signals and conditions. In simulation, the code
fragment is executed whenever a signal in the sensitivity list changes its value
or a condition in the sensitivity list is triggered. A synthesis tool must be
able to transfer this representation into an appropriate datapath followed by
the appropriate types of register.
For example consider the following Verilog code fragment:
.. code:: verilog
:number-lines:
always @(posedge clk)
y <= a + b;
In simulation the statement ``y <= a + b`` is executed whenever a positive edge
on the signal ``clk`` is detected. The synthesis result however will contain an
adder that calculates the sum ``a + b`` all the time, followed by a d-type
flip-flop with the adder output on its D-input and the signal ``y`` on its
Q-output.
Usually the imperative code fragments used in behavioural modelling can contain
statements for conditional execution (``if``- and ``case``-statements in
Verilog) as well as loops, as long as those loops can be completely unrolled.
Interestingly there seems to be no other FOSS Tool that is capable of performing
Verilog or VHDL behavioural syntheses besides Yosys.
Register-Transfer Level (RTL)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
On the Register-Transfer Level the design is represented by combinatorial data
paths and registers (usually d-type flip flops). The following Verilog code
fragment is equivalent to the previous Verilog example, but is in RTL
representation:
.. code:: verilog
:number-lines:
assign tmp = a + b; // combinatorial data path
always @(posedge clk) // register
y <= tmp;
A design in RTL representation is usually stored using HDLs like Verilog and
VHDL. But only a very limited subset of features is used, namely minimalistic
always-blocks (Verilog) or process-blocks (VHDL) that model the register type
used and unconditional assignments for the datapath logic. The use of HDLs on
this level simplifies simulation as no additional tools are required to simulate
a design in RTL representation.
Many optimizations and analyses can be performed best at the RTL level. Examples
include FSM detection and optimization, identification of memories or other
larger building blocks and identification of shareable resources.
Note that RTL is the first abstraction level in which the circuit is represented
as a graph of circuit elements (registers and combinatorial cells) and signals.
Such a graph, when encoded as list of cells and connections, is called a
netlist.
RTL synthesis is easy as each circuit node element in the netlist can simply be
replaced with an equivalent gate-level circuit. However, usually the term RTL
synthesis does not only refer to synthesizing an RTL netlist to a gate level
netlist but also to performing a number of highly sophisticated optimizations
within the RTL representation, such as the examples listed above.
A number of FOSS tools exist that can perform isolated tasks within the domain
of RTL synthesis steps. But there seems to be no FOSS tool that covers a wide
range of RTL synthesis operations.
Logical gate level
~~~~~~~~~~~~~~~~~~
At the logical gate level the design is represented by a netlist that uses only
cells from a small number of single-bit cells, such as basic logic gates (AND,
OR, NOT, XOR, etc.) and registers (usually D-Type Flip-flops).
A number of netlist formats exists that can be used on this level, e.g. the
Electronic Design Interchange Format (EDIF), but for ease of simulation often a
HDL netlist is used. The latter is a HDL file (Verilog or VHDL) that only uses
the most basic language constructs for instantiation and connecting of cells.
There are two challenges in logic synthesis: First finding opportunities for
optimizations within the gate level netlist and second the optimal (or at least
good) mapping of the logic gate netlist to an equivalent netlist of physically
available gate types.
The simplest approach to logic synthesis is two-level logic synthesis, where a
logic function is converted into a sum-of-products representation, e.g. using a
Karnaugh map. This is a simple approach, but has exponential worst-case effort
and cannot make efficient use of physical gates other than AND/NAND-, OR/NOR-
and NOT-Gates.
Therefore modern logic synthesis tools utilize much more complicated multi-level
logic synthesis algorithms :cite:p:`MultiLevelLogicSynth`. Most of these
algorithms convert the logic function to a Binary-Decision-Diagram (BDD) or
And-Inverter-Graph (AIG) and work from that representation. The former has the
advantage that it has a unique normalized form. The latter has much better worst
case performance and is therefore better suited for the synthesis of large logic
functions.
Good FOSS tools exists for multi-level logic synthesis .
Yosys contains basic logic synthesis functionality but can also use ABC for the
logic synthesis step. Using ABC is recommended.
Physical gate level
~~~~~~~~~~~~~~~~~~~
On the physical gate level only gates are used that are physically available on
the target architecture. In some cases this may only be NAND, NOR and NOT gates
as well as D-Type registers. In other cases this might include cells that are
more complex than the cells used at the logical gate level (e.g. complete
half-adders). In the case of an FPGA-based design the physical gate level
representation is a netlist of LUTs with optional output registers, as these are
the basic building blocks of FPGA logic cells.
For the synthesis tool chain this abstraction is usually the lowest level. In
case of an ASIC-based design the cell library might contain further information
on how the physical cells map to individual switches (transistors).
Switch level
~~~~~~~~~~~~
A switch level representation of a circuit is a netlist utilizing single
transistors as cells. Switch level modelling is possible in Verilog and VHDL,
but is seldom used in modern designs, as in modern digital ASIC or FPGA flows
the physical gates are considered the atomic build blocks of the logic circuit.
Yosys
~~~~~
Yosys is a Verilog HDL synthesis tool. This means that it takes a behavioural
design description as input and generates an RTL, logical gate or physical gate
level description of the design as output. Yosys' main strengths are behavioural
and RTL synthesis. A wide range of commands (synthesis passes) exist within
Yosys that can be used to perform a wide range of synthesis tasks within the
domain of behavioural, rtl and logic synthesis. Yosys is designed to be
extensible and therefore is a good basis for implementing custom synthesis tools
for specialised tasks.
Features of synthesizable Verilog
---------------------------------
The subset of Verilog :cite:p:`Verilog2005` that is synthesizable is specified
in a separate IEEE standards document, the IEEE standard 1364.1-2002
:cite:p:`VerilogSynth`. This standard also describes how certain language
constructs are to be interpreted in the scope of synthesis.
This section provides a quick overview of the most important features of
synthesizable Verilog, structured in order of increasing complexity.
Structural Verilog
~~~~~~~~~~~~~~~~~~
Structural Verilog (also known as Verilog Netlists) is a Netlist in Verilog
syntax. Only the following language constructs are used in this
case:
- Constant values
- Wire and port declarations
- Static assignments of signals to other signals
- Cell instantiations
Many tools (especially at the back end of the synthesis chain) only support
structural Verilog as input. ABC is an example of such a tool. Unfortunately
there is no standard specifying what Structural Verilog actually is, leading to
some confusion about what syntax constructs are supported in structural Verilog
when it comes to features such as attributes or multi-bit signals.
Expressions in Verilog
~~~~~~~~~~~~~~~~~~~~~~
In all situations where Verilog accepts a constant value or signal name,
expressions using arithmetic operations such as ``+``, ``-`` and ``*``, boolean
operations such as ``&`` (AND), ``|`` (OR) and ``^`` (XOR) and many others
(comparison operations, unary operator, etc.) can also be used.
During synthesis these operators are replaced by cells that implement the
respective function.
Many FOSS tools that claim to be able to process Verilog in fact only support
basic structural Verilog and simple expressions. Yosys can be used to convert
full featured synthesizable Verilog to this simpler subset, thus enabling such
applications to be used with a richer set of Verilog features.
Behavioural modelling
~~~~~~~~~~~~~~~~~~~~~
Code that utilizes the Verilog always statement is using Behavioural Modelling.
In behavioural modelling, a circuit is described by means of imperative program
code that is executed on certain events, namely any change, a rising edge, or a
falling edge of a signal. This is a very flexible construct during simulation
but is only synthesizable when one
of the following is modelled:
- | **Asynchronous or latched logic**
| In this case the sensitivity list must contain all expressions that
are used within the always block. The syntax ``@*`` can be used for
these cases. Examples of this kind include:
.. code:: verilog
:number-lines:
// asynchronous
always @* begin
if (add_mode)
y <= a + b;
else
y <= a - b;
end
// latched
always @* begin
if (!hold)
y <= a + b;
end
Note that latched logic is often considered bad style and in many
cases just the result of sloppy HDL design. Therefore many synthesis
tools generate warnings whenever latched logic is generated.
- | **Synchronous logic (with optional synchronous reset)**
| This is logic with d-type flip-flops on the output. In this case
the sensitivity list must only contain the respective clock edge.
Example:
.. code:: verilog
:number-lines:
// counter with synchronous reset
always @(posedge clk) begin
if (reset)
y <= 0;
else
y <= y + 1;
end
- | **Synchronous logic with asynchronous reset**
| This is logic with d-type flip-flops with asynchronous resets on
the output. In this case the sensitivity list must only contain the
respective clock and reset edges. The values assigned in the reset
branch must be constant. Example:
.. code:: verilog
:number-lines:
// counter with asynchronous reset
always @(posedge clk, posedge reset) begin
if (reset)
y <= 0;
else
y <= y + 1;
end
Many synthesis tools support a wider subset of flip-flops that can be modelled
using always-statements (including Yosys). But only the ones listed above are
covered by the Verilog synthesis standard and when writing new designs one
should limit herself or himself to these cases.
In behavioural modelling, blocking assignments (=) and non-blocking assignments
(<=) can be used. The concept of blocking vs. non-blocking assignment is one of
the most misunderstood constructs in Verilog :cite:p:`Cummings00`.
The blocking assignment behaves exactly like an assignment in any imperative
programming language, while with the non-blocking assignment the right hand side
of the assignment is evaluated immediately but the actual update of the left
hand side register is delayed until the end of the time-step. For example the
Verilog code ``a <= b; b <= a;`` exchanges the values of the two registers.
Functions and tasks
~~~~~~~~~~~~~~~~~~~
Verilog supports Functions and Tasks to bundle statements that are used in
multiple places (similar to Procedures in imperative programming). Both
constructs can be implemented easily by substituting the function/task-call with
the body of the function or task.
Conditionals, loops and generate-statements
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Verilog supports ``if-else``-statements and ``for``-loops inside
``always``-statements.
It also supports both features in ``generate``-statements on the module level.
This can be used to selectively enable or disable parts of the module based on
the module parameters (``if-else``) or to generate a set of similar subcircuits
(``for``).
While the ``if-else``-statement inside an always-block is part of behavioural
modelling, the three other cases are (at least for a synthesis tool) part of a
built-in macro processor. Therefore it must be possible for the synthesis tool
to completely unroll all loops and evaluate the condition in all
``if-else``-statement in ``generate``-statements using const-folding..
Arrays and memories
~~~~~~~~~~~~~~~~~~~
Verilog supports arrays. This is in general a synthesizable language feature. In
most cases arrays can be synthesized by generating addressable memories.
However, when complex or asynchronous access patterns are used, it is not
possible to model an array as memory. In these cases the array must be modelled
using individual signals for each word and all accesses to the array must be
implemented using large multiplexers.
In some cases it would be possible to model an array using memories, but it is
not desired. Consider the following delay circuit:
.. code:: verilog
:number-lines:
module (clk, in_data, out_data);
parameter BITS = 8;
parameter STAGES = 4;
input clk;
input [BITS-1:0] in_data;
output [BITS-1:0] out_data;
reg [BITS-1:0] ffs [STAGES-1:0];
integer i;
always @(posedge clk) begin
ffs[0] <= in_data;
for (i = 1; i < STAGES; i = i+1)
ffs[i] <= ffs[i-1];
end
assign out_data = ffs[STAGES-1];
endmodule
This could be implemented using an addressable memory with STAGES input and
output ports. A better implementation would be to use a simple chain of
flip-flops (a so-called shift register). This better implementation can either
be obtained by first creating a memory-based implementation and then optimizing
it based on the static address signals for all ports or directly identifying
such situations in the language front end and converting all memory accesses to
direct accesses to the correct signals.
Challenges in digital circuit synthesis
---------------------------------------
This section summarizes the most important challenges in digital circuit
synthesis. Tools can be characterized by how well they address these topics.
Standards compliance
~~~~~~~~~~~~~~~~~~~~
The most important challenge is compliance with the HDL standards in question
(in case of Verilog the IEEE Standards 1364.1-2002 and 1364-2005). This can be
broken down in two items:
- Completeness of implementation of the standard
- Correctness of implementation of the standard
Completeness is mostly important to guarantee compatibility with existing HDL
code. Once a design has been verified and tested, HDL designers are very
reluctant regarding changes to the design, even if it is only about a few minor
changes to work around a missing feature in a new synthesis tool.
Correctness is crucial. In some areas this is obvious (such as correct synthesis
of basic behavioural models). But it is also crucial for the areas that concern
minor details of the standard, such as the exact rules for handling signed
expressions, even when the HDL code does not target different synthesis tools.
This is because (unlike software source code that is only processed by
compilers), in most design flows HDL code is not only processed by the synthesis
tool but also by one or more simulators and sometimes even a formal verification
tool. It is key for this verification process that all these tools use the same
interpretation for the HDL code.
Optimizations
~~~~~~~~~~~~~
Generally it is hard to give a one-dimensional description of how well a
synthesis tool optimizes the design. First of all because not all optimizations
are applicable to all designs and all synthesis tasks. Some optimizations work
(best) on a coarse-grained level (with complex cells such as adders or
multipliers) and others work (best) on a fine-grained level (single bit gates).
Some optimizations target area and others target speed. Some work well on large
designs while others don't scale well and can only be applied to small designs.
A good tool is capable of applying a wide range of optimizations at different
levels of abstraction and gives the designer control over which optimizations
are performed (or skipped) and what the optimization goals are.
Technology mapping
~~~~~~~~~~~~~~~~~~
Technology mapping is the process of converting the design into a netlist of
cells that are available in the target architecture. In an ASIC flow this might
be the process-specific cell library provided by the fab. In an FPGA flow this
might be LUT cells as well as special function units such as dedicated
multipliers. In a coarse-grain flow this might even be more complex special
function units.
An open and vendor independent tool is especially of interest if it supports a
wide range of different types of target architectures.
Script-based synthesis flows
----------------------------
A digital design is usually started by implementing a high-level or system-level
simulation of the desired function. This description is then manually
transformed (or re-implemented) into a synthesizable lower-level description
(usually at the behavioural level) and the equivalence of the two
representations is verified by simulating both and comparing the simulation
results.
Then the synthesizable description is transformed to lower-level representations
using a series of tools and the results are again verified using simulation.
This process is illustrated in :numref:`Fig. %s <fig:Basics_flow>`.
.. figure:: ../../images/basics_flow.*
:class: width-helper
:name: fig:Basics_flow
Typical design flow. Green boxes represent manually created models.
Orange boxes represent modesl generated by synthesis tools.
In this example the System Level Model and the Behavioural Model are both
manually written design files. After the equivalence of system level model and
behavioural model has been verified, the lower level representations of the
design can be generated using synthesis tools. Finally the RTL Model and the
Gate-Level Model are verified and the design process is finished.
However, in any real-world design effort there will be multiple iterations for
this design process. The reason for this can be the late change of a design
requirement or the fact that the analysis of a low-abstraction model
(e.g. gate-level timing analysis) revealed that a design change is required in
order to meet the design requirements (e.g. maximum possible clock speed).
Whenever the behavioural model or the system level model is changed their
equivalence must be re-verified by re-running the simulations and comparing the
results. Whenever the behavioural model is changed the synthesis must be re-run
and the synthesis results must be re-verified.
In order to guarantee reproducibility it is important to be able to re-run all
automatic steps in a design project with a fixed set of settings easily. Because
of this, usually all programs used in a synthesis flow can be controlled using
scripts. This means that all functions are available via text commands. When
such a tool provides a GUI, this is complementary to, and not instead of, a
command line interface.
Usually a synthesis flow in an UNIX/Linux environment would be controlled by a
shell script that calls all required tools (synthesis and
simulation/verification in this example) in the correct order. Each of these
tools would be called with a script file containing commands for the respective
tool. All settings required for the tool would be provided by these script files
so that no manual interaction would be necessary. These script files are
considered design sources and should be kept under version control just like the
source code of the system level and the behavioural model.
Methods from compiler design
----------------------------
Some parts of synthesis tools involve problem domains that are traditionally
known from compiler design. This section addresses some of these domains.
Lexing and parsing
~~~~~~~~~~~~~~~~~~
The best known concepts from compiler design are probably lexing and parsing.
These are two methods that together can be used to process complex computer
languages easily. :cite:p:`Dragonbook`
A lexer consumes single characters from the input and generates a stream of
lexical tokens that consist of a type and a value. For example the Verilog input
:verilog:`assign foo = bar + 42;` might be translated by the lexer to the list
of lexical tokens given in :numref:`Tab. %s <tab:Basics_tokens>`.
.. table:: Exemplary token list for the statement :verilog:`assign foo = bar + 42;`
:name: tab:Basics_tokens
============== ===============
Token-Type Token-Value
============== ===============
TOK_ASSIGN \-
TOK_IDENTIFIER "foo"
TOK_EQ \-
TOK_IDENTIFIER "bar"
TOK_PLUS \-
TOK_NUMBER 42
TOK_SEMICOLON \-
============== ===============
The lexer is usually generated by a lexer generator (e.g. flex ) from a
description file that is using regular expressions to specify the text pattern
that should match the individual tokens.
The lexer is also responsible for skipping ignored characters (such as
whitespace outside string constants and comments in the case of Verilog) and
converting the original text snippet to a token value.
Note that individual keywords use different token types (instead of a keyword
type with different token values). This is because the parser usually can only
use the Token-Type to make a decision on the grammatical role of a token.
The parser then transforms the list of tokens into a parse tree that closely
resembles the productions from the computer languages grammar. As the lexer, the
parser is also typically generated by a code generator (e.g. bison ) from a
grammar description in Backus-Naur Form (BNF).
Let's consider the following BNF (in Bison syntax):
.. code:: none
:number-lines:
assign_stmt: TOK_ASSIGN TOK_IDENTIFIER TOK_EQ expr TOK_SEMICOLON;
expr: TOK_IDENTIFIER | TOK_NUMBER | expr TOK_PLUS expr;
.. figure:: ../../images/basics_parsetree.*
:class: width-helper
:name: fig:Basics_parsetree
Example parse tree for the Verilog expression
:verilog:`assign foo = bar + 42;`
The parser converts the token list to the parse tree in :numref:`Fig. %s
<fig:Basics_parsetree>`. Note that the parse tree never actually exists as a
whole as data structure in memory. Instead the parser calls user-specified code
snippets (so-called reduce-functions) for all inner nodes of the parse tree in
depth-first order.
In some very simple applications (e.g. code generation for stack machines) it is
possible to perform the task at hand directly in the reduce functions. But
usually the reduce functions are only used to build an in-memory data structure
with the relevant information from the parse tree. This data structure is called
an abstract syntax tree (AST).
The exact format for the abstract syntax tree is application specific (while the
format of the parse tree and token list are mostly dictated by the grammar of
the language at hand). :numref:`Figure %s <fig:Basics_ast>` illustrates what an
AST for the parse tree in :numref:`Fig. %s <fig:Basics_parsetree>` could look
like.
Usually the AST is then converted into yet another representation that is more
suitable for further processing. In compilers this is often an assembler-like
three-address-code intermediate representation. :cite:p:`Dragonbook`
.. figure:: ../../images/basics_ast.*
:class: width-helper
:name: fig:Basics_ast
Example abstract syntax tree for the Verilog expression
:verilog:`assign foo = bar + 42;`
Multi-pass compilation
~~~~~~~~~~~~~~~~~~~~~~
Complex problems are often best solved when split up into smaller problems. This
is certainly true for compilers as well as for synthesis tools. The components
responsible for solving the smaller problems can be connected in two different
ways: through Single-Pass Pipelining and by using Multiple Passes.
Traditionally a parser and lexer are connected using the pipelined approach: The
lexer provides a function that is called by the parser. This function reads data
from the input until a complete lexical token has been read. Then this token is
returned to the parser. So the lexer does not first generate a complete list of
lexical tokens and then pass it to the parser. Instead they run concurrently and
the parser can consume tokens as the lexer produces them.
The single-pass pipelining approach has the advantage of lower memory footprint
(at no time must the complete design be kept in memory) but has the disadvantage
of tighter coupling between the interacting components.
Therefore single-pass pipelining should only be used when the lower memory
footprint is required or the components are also conceptually tightly coupled.
The latter certainly is the case for a parser and its lexer. But when data is
passed between two conceptually loosely coupled components it is often
beneficial to use a multi-pass approach.
In the multi-pass approach the first component processes all the data and the
result is stored in a in-memory data structure. Then the second component is
called with this data. This reduces complexity, as only one component is running
at a time. It also improves flexibility as components can be exchanged easier.
Most modern compilers are multi-pass compilers.
Static Single Assignment (SSA) form
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In imperative programming (and behavioural HDL design) it is possible to assign
the same variable multiple times. This can either mean that the variable is
independently used in two different contexts or that the final value of the
variable depends on a condition.
The following examples show C code in which one variable is used independently
in two different contexts:
.. code:: c++
:number-lines:
void demo1()
{
int a = 1;
printf("%d\n", a);
a = 2;
printf("%d\n", a);
}
.. code:: c++
void demo1()
{
int a = 1;
printf("%d\n", a);
int b = 2;
printf("%d\n", b);
}
.. code:: c++
:number-lines:
void demo2(bool foo)
{
int a;
if (foo) {
a = 23;
printf("%d\n", a);
} else {
a = 42;
printf("%d\n", a);
}
}
.. code:: c++
void demo2(bool foo)
{
int a, b;
if (foo) {
a = 23;
printf("%d\n", a);
} else {
b = 42;
printf("%d\n", b);
}
}
In both examples the left version (only variable ``a``) and the right version
(variables ``a`` and ``b``) are equivalent. Therefore it is desired for further
processing to bring the code in an equivalent form for both cases.
In the following example the variable is assigned twice but it cannot be easily
replaced by two variables:
.. code:: c++
void demo3(bool foo)
{
int a = 23
if (foo)
a = 42;
printf("%d\n", a);
}
Static single assignment (SSA) form is a representation of imperative code that
uses identical representations for the left and right version of demos 1 and 2,
but can still represent demo 3. In SSA form each assignment assigns a new
variable (usually written with an index). But it also introduces a special
:math:`\Phi`-function to merge the different instances of a variable when
needed. In C-pseudo-code the demo 3 would be written as follows using SSA from:
.. code:: c++
void demo3(bool foo)
{
int a_1, a_2, a_3;
a_1 = 23
if (foo)
a_2 = 42;
a_3 = phi(a_1, a_2);
printf("%d\n", a_3);
}
The :math:`\Phi`-function is usually interpreted as "these variables must be
stored in the same memory location" during code generation. Most modern
compilers for imperative languages such as C/C++ use SSA form for at least some
of its passes as it is very easy to manipulate and analyse.
.. [1]
In recent years formal equivalence checking also became an important
verification method for validating RTL and lower abstraction
representation of the design.