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:
parent
4f1cd66829
commit
045c04096e
40 changed files with 661 additions and 1282 deletions
|
@ -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.)
|
|
@ -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>
|
|
@ -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/
|
|
@ -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
|
776
docs/source/appendix/primer.rst
Normal file
776
docs/source/appendix/primer.rst
Normal 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.
|
Loading…
Add table
Add a link
Reference in a new issue