mirror of https://github.com/YosysHQ/yosys.git
docs/rosette: Reword
Acknowledge the minimal functional backend, highlighting what's new/different for our SExpr targets. Add and use the reference `minimal backend`. Use `note` directives to point out missing code sections (highlighting that the included diffs are not complete). Racket *is* still strongly-typed, it's just dynamic instead of static. Adjust phrasing to reflect that. Adjust some of the literal includes, adding a new section from the `Functional::AbstractVisitor`, splitting the `Module::write_eval()` in twain and adding a `smtlib.cc` literal include for the node iteration, as well as for the `FunctionalSmtBackend` to compare against the minimal backend. Move `Backend` description up to minimal functional backend section.
This commit is contained in:
parent
e01a413722
commit
3c493d2bef
|
@ -144,6 +144,8 @@ S-expressions can be constructed with ``SExpr::list``, for example ``SExpr expr
|
||||||
parentheses.
|
parentheses.
|
||||||
- The destructor calls ``flush`` but also closes all unclosed parentheses.
|
- The destructor calls ``flush`` but also closes all unclosed parentheses.
|
||||||
|
|
||||||
|
.. _minimal backend:
|
||||||
|
|
||||||
Example: A minimal functional backend
|
Example: A minimal functional backend
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
|
@ -158,6 +160,13 @@ Example: A minimal functional backend
|
||||||
+ handle outputs and next state
|
+ handle outputs and next state
|
||||||
|
|
||||||
- backend pass boiler plate gives us ``write_functional_dummy`` command
|
- backend pass boiler plate gives us ``write_functional_dummy`` command
|
||||||
|
|
||||||
|
The final part is the ``Backend`` itself, which registers the command in Yosys.
|
||||||
|
The ``execute`` method is the part that runs when the user calls the command,
|
||||||
|
handling any options, preparing the output file for writing, and iterating over
|
||||||
|
selected modules in the design. For more on adding new commands to Yosys and
|
||||||
|
how they work, refer to :doc:`/yosys_internals/extending_yosys/extensions`.
|
||||||
|
|
||||||
- pointer ``f`` is a ``std::ostream`` we can write to, being either a file or
|
- pointer ``f`` is a ``std::ostream`` we can write to, being either a file or
|
||||||
stdout
|
stdout
|
||||||
- FunctionalIR conversion done by ``Functional::IR::from_module()``
|
- FunctionalIR conversion done by ``Functional::IR::from_module()``
|
||||||
|
@ -233,18 +242,23 @@ The major changes from the SMT-LIB backend are as follows:
|
||||||
Scope
|
Scope
|
||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
As described above, the ``Functional::Scope`` class is derived in order to avoid
|
Our first addition to the `minimal backend`_ above is that for both SMT-LIB and
|
||||||
collisions between identifiers in the generated output. In the SMT-LIB version
|
Rosette backends, we are now targetting real languages which bring with them
|
||||||
the ``SmtScope`` class implements ``Scope<int>``; provides a constructor that
|
their own sets of constraints with what we can use as identifiers. This is
|
||||||
iterates over a list of reserved keywords, calling ``reserve`` on each; and
|
where the ``Functional::Scope`` class described above comes in; by using this
|
||||||
defines the ``is_character_legal`` method to reject any characters which are not
|
class we can safely rename our identifiers in the generated output without
|
||||||
allowed in SMT-LIB variable names to then be replaced with underscores in the
|
worrying about collisions or illegal names/characters.
|
||||||
output.
|
|
||||||
|
|
||||||
In the Rosette version we switch out ``Smt`` in the class name for ``Smtr`` to
|
In the SMT-LIB version, the ``SmtScope`` class implements ``Scope<int>``;
|
||||||
mean ``smtlib_rosette``; this will happen through the rest of the code too. We
|
provides a constructor that iterates over a list of reserved keywords, calling
|
||||||
also update list of legal ascii characters in the ``is_character_legal`` method
|
``reserve`` on each; and defines the ``is_character_legal`` method to reject any
|
||||||
to only those allowed in Racket variable names.
|
characters which are not allowed in SMT-LIB variable names to then be replaced
|
||||||
|
with underscores in the output. To use this scope we create an instance of it,
|
||||||
|
and call the ``Scope::unique_name()`` method to generate a unique and legal name
|
||||||
|
for each of our identifiers.
|
||||||
|
|
||||||
|
In the Rosette version we update the list of legal ascii characters in the
|
||||||
|
``is_character_legal`` method to only those allowed in Racket variable names.
|
||||||
|
|
||||||
.. literalinclude:: /generated/functional/rosette.diff
|
.. literalinclude:: /generated/functional/rosette.diff
|
||||||
:language: diff
|
:language: diff
|
||||||
|
@ -276,8 +290,8 @@ provides this functionality natively with ``cons``, which we will see later.
|
||||||
nullptr
|
nullptr
|
||||||
};
|
};
|
||||||
|
|
||||||
Note that we skip over the actual list of reserved keywords from both the smtlib
|
.. note:: We skip over the actual list of reserved keywords from both the smtlib
|
||||||
and racket specifications to save on space in this document.
|
and racket specifications to save on space in this document.
|
||||||
|
|
||||||
Sort
|
Sort
|
||||||
~~~~
|
~~~~
|
||||||
|
@ -297,16 +311,17 @@ signals represented as ``bitvector``\ s, and memories as ``list``\ s of signals.
|
||||||
Struct
|
Struct
|
||||||
~~~~~~
|
~~~~~~
|
||||||
|
|
||||||
The ``Functional::IR`` class tracks the set of inputs, the set of outputs, and
|
As we saw in the `minimal backend`_ above, the ``Functional::IR`` class tracks
|
||||||
the set of "state" variables. The SMT-LIB backend maps each of these sets into
|
the set of inputs, the set of outputs, and the set of "state" variables. The
|
||||||
its own ``SmtStruct``, with each variable getting a corresponding field in the
|
SMT-LIB backend maps each of these sets into its own ``SmtStruct``, with each
|
||||||
struct and a specified `Sort`_. `write_functional_smt2` then defines each of
|
variable getting a corresponding field in the struct and a specified `Sort`_.
|
||||||
these structs as a new ``datatype``, with each element being strongly-typed.
|
`write_functional_smt2` then defines each of these structs as a new
|
||||||
|
``datatype``, with each element being strongly-typed.
|
||||||
|
|
||||||
In Rosette, rather than defining new datatypes for our structs, we use the
|
In Rosette, rather than defining new datatypes for our structs, we use the
|
||||||
native ``struct``. We also only declare each field by name because Racket is
|
native ``struct``. We also only declare each field by name because Racket
|
||||||
not as strongly-typed. For ease of use, we provide the expected type for each
|
provides less static typing. For ease of use, we provide the expected type for
|
||||||
field as comments.
|
each field as comments.
|
||||||
|
|
||||||
.. literalinclude:: /generated/functional/rosette.diff
|
.. literalinclude:: /generated/functional/rosette.diff
|
||||||
:language: diff
|
:language: diff
|
||||||
|
@ -355,18 +370,19 @@ Rosette ``struct``\ s.
|
||||||
PrintVisitor
|
PrintVisitor
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
The ``PrintVisitor`` implements the abstract ``Functional::AbstractVisitor``
|
Remember in the `minimal backend`_ we converted nodes into strings for writing
|
||||||
class, described above in `What is FunctionalIR`_, with a return type of
|
using the ``node.to_string()`` method, which wrapped ``node.visit()`` with a
|
||||||
``SExpr``. This class converts FunctionalIR functions into s-expressions,
|
private visitor. We now want a custom visitor which can convert nodes into
|
||||||
including reading inputs/current state with the ``access`` method from the
|
s-expressions. This is where the ``PrintVisitor`` comes in, implementing the
|
||||||
`Struct`_. For most functions, the Rosette output is very similar to the
|
abstract ``Functional::AbstractVisitor`` class with a return type of ``SExpr``.
|
||||||
corresponding SMT-LIB function with minor adjustments for syntax.
|
For most functions, the Rosette output is very similar to the corresponding
|
||||||
|
SMT-LIB function with minor adjustments for syntax.
|
||||||
|
|
||||||
.. literalinclude:: /generated/functional/rosette.diff
|
.. literalinclude:: /generated/functional/rosette.diff
|
||||||
:language: diff
|
:language: diff
|
||||||
:caption: portion of ``Functional::AbstractVisitor`` implementation diff showing similarities
|
:caption: portion of ``Functional::AbstractVisitor`` implementation diff showing similarities
|
||||||
:start-at: SExpr logical_shift_left
|
:start-at: SExpr logical_shift_left
|
||||||
:end-before: SExpr input
|
:end-at: "list-set-bv"
|
||||||
|
|
||||||
However there are some differences in the two formats with regards to how
|
However there are some differences in the two formats with regards to how
|
||||||
booleans are handled, with Rosette providing built-in functions for conversion.
|
booleans are handled, with Rosette providing built-in functions for conversion.
|
||||||
|
@ -377,13 +393,30 @@ booleans are handled, with Rosette providing built-in functions for conversion.
|
||||||
:start-at: SExpr from_bool
|
:start-at: SExpr from_bool
|
||||||
:end-before: SExpr extract
|
:end-before: SExpr extract
|
||||||
|
|
||||||
|
Of note here is the rare instance of the Rosette implementation *gaining* static
|
||||||
|
typing rather than losing it. Where SMT_LIB calls zero/sign extension with the
|
||||||
|
number of extra bits needed (given by ``out_width - a.width()``), Rosette
|
||||||
|
instead specifies the type of the output (given by ``list("bitvector",
|
||||||
|
out_width)``).
|
||||||
|
|
||||||
|
.. literalinclude:: /generated/functional/rosette.diff
|
||||||
|
:language: diff
|
||||||
|
:caption: zero/sign extension implementation diff
|
||||||
|
:start-after: SExpr buf(
|
||||||
|
:end-before: SExpr concat(
|
||||||
|
:lines: 2-3, 5-6
|
||||||
|
|
||||||
|
.. note:: Be sure to check the source code for the full list of differences here.
|
||||||
|
|
||||||
Module
|
Module
|
||||||
~~~~~~
|
~~~~~~
|
||||||
|
|
||||||
The ``Functional::IR`` is wrapped in the ``SmtModule`` class, with the mapping
|
With most of the supporting classes out of the way, we now reach our three main
|
||||||
from RTLIL module to FunctionalIR happening in the constructor. Each of the
|
steps from the `minimal backend`_. These are all handled by the ``SmtModule``
|
||||||
three ``SmtStruct``\ s; inputs, outputs, and state; are created, with each value
|
class, with the mapping from RTLIL module to FunctionalIR happening in the
|
||||||
in the corresponding lists in the IR being ``insert``\ ed.
|
constructor. Each of the three ``SmtStruct``\ s; inputs, outputs, and state;
|
||||||
|
are also created in the constructor, with each value in the corresponding lists
|
||||||
|
in the IR being ``insert``\ ed.
|
||||||
|
|
||||||
.. literalinclude:: /generated/functional/smtlib.cc
|
.. literalinclude:: /generated/functional/smtlib.cc
|
||||||
:language: c++
|
:language: c++
|
||||||
|
@ -403,7 +436,7 @@ uses an underscore for the name of the initial state.
|
||||||
The ``write`` method is then responsible for writing the FunctionalIR to the
|
The ``write`` method is then responsible for writing the FunctionalIR to the
|
||||||
output file, formatted for the corresponding backend. ``SmtModule::write()``
|
output file, formatted for the corresponding backend. ``SmtModule::write()``
|
||||||
breaks the output file down into four parts: defining the three structs,
|
breaks the output file down into four parts: defining the three structs,
|
||||||
declaring the ``pair`` datatype, defining the mapping function ``(inputs,
|
declaring the ``pair`` datatype, defining the transfer function ``(inputs,
|
||||||
current_state) -> (outputs, next_state)`` with ``write_eval``, and declaring the
|
current_state) -> (outputs, next_state)`` with ``write_eval``, and declaring the
|
||||||
initial state with ``write_initial``. The only change for the ``SmtrModule`` is
|
initial state with ``write_initial``. The only change for the ``SmtrModule`` is
|
||||||
that the ``pair`` declaration isn't needed.
|
that the ``pair`` declaration isn't needed.
|
||||||
|
@ -414,33 +447,35 @@ that the ``pair`` declaration isn't needed.
|
||||||
:start-at: void write(std::ostream &out)
|
:start-at: void write(std::ostream &out)
|
||||||
:end-at: }
|
:end-at: }
|
||||||
|
|
||||||
For the ``write_eval`` method, the main differences are syntactical. First we
|
The ``write_eval`` method is where the FunctionalIR nodes, outputs, and next
|
||||||
change the function declaration line for the Rosette style which drops the
|
state are handled. Just as with the `minimal backend`_, we iterate over the
|
||||||
explicit output typing and uses the ``define`` keyword instead of
|
nodes with ``for(auto n : ir)``, and then use the ``Struct::write_value()``
|
||||||
``define-fun``. And then we change the final result from a ``pair`` to the
|
method for the ``output_struct`` and ``state_struct`` to iterate over the
|
||||||
native ``cons`` which acts in much the same way, returning both the ``outputs``
|
outputs and next state respectively.
|
||||||
and the ``next_state`` in a single variable. Iteration over all of the
|
|
||||||
``Functional::Node``\ s in the IR is the same in both.
|
|
||||||
|
|
||||||
.. inlined diff for showing the whole function while skipping the middle part
|
.. literalinclude:: /generated/functional/smtlib.cc
|
||||||
.. code-block:: diff
|
:language: c++
|
||||||
:caption: diff of ``Module::write_eval()`` method
|
:caption: iterating over FunctionalIR nodes in ``SmtModule::write_eval()``
|
||||||
|
:start-at: for(auto n : ir)
|
||||||
|
:end-at: }
|
||||||
|
|
||||||
void write_eval(SExprWriter &w)
|
The main differences between our two backends here are syntactical. First we
|
||||||
{
|
change the ``define-fun`` for the Racket style ``define`` which drops the
|
||||||
w.push();
|
explicitly typed inputs/outputs. And then we change the final result from a
|
||||||
- w.open(list("define-fun", name,
|
``pair`` to the native ``cons`` which acts in much the same way, returning both
|
||||||
- list(list("inputs", input_struct.name),
|
the ``outputs`` and the ``next_state`` in a single variable.
|
||||||
- list("state", state_struct.name)),
|
|
||||||
- list("Pair", output_struct.name, state_struct.name)));
|
.. literalinclude:: /generated/functional/rosette.diff
|
||||||
+ w.open(list("define", list(name, "inputs", "state")));
|
:language: diff
|
||||||
...
|
:caption: diff of ``Module::write_eval()`` transfer function declaration
|
||||||
- w.open(list("pair"));
|
:start-at: w.open(list("define-fun"
|
||||||
+ w.open(list("cons"));
|
:end-at: w.open(list("define"
|
||||||
output_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.output(name).value()); });
|
|
||||||
state_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.state(name).next_value()); });
|
.. literalinclude:: /generated/functional/rosette.diff
|
||||||
w.pop();
|
:language: diff
|
||||||
}
|
:caption: diff of output/next state handling ``Module::write_eval()``
|
||||||
|
:start-at: w.open(list("pair"
|
||||||
|
:end-at: w.pop();
|
||||||
|
|
||||||
For the ``write_initial`` method, the SMT-LIB backend uses ``declare-const`` and
|
For the ``write_initial`` method, the SMT-LIB backend uses ``declare-const`` and
|
||||||
``assert``\ s which must always hold true. For Rosette we instead define the
|
``assert``\ s which must always hold true. For Rosette we instead define the
|
||||||
|
@ -458,12 +493,15 @@ whereas the SMT-LIB code can only verify that a given ``next_state`` is correct.
|
||||||
Backend
|
Backend
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
The final part is the ``Backend`` itself, which registers the command in Yosys.
|
The final part is the ``Backend`` itself, with much of the same boiler plate as
|
||||||
The ``execute`` method is the part that runs when the user calls
|
the `minimal backend`_. The main difference is that we use the `Module`_ to
|
||||||
`write_functional_rosette`, handling any options, preparing the output file for
|
perform the actual processing.
|
||||||
writing, and iterating over selected modules in the design. For more on adding
|
|
||||||
new commands to Yosys and how they work, refer to
|
.. literalinclude:: /generated/functional/smtlib.cc
|
||||||
:doc:`/yosys_internals/extending_yosys/extensions`.
|
:language: c++
|
||||||
|
:caption: The ``FunctionalSmtBackend``
|
||||||
|
:start-at: struct FunctionalSmtBackend
|
||||||
|
:end-at: } FunctionalSmtBackend;
|
||||||
|
|
||||||
There are two additions here for Rosette. The first is that the output file
|
There are two additions here for Rosette. The first is that the output file
|
||||||
needs to start with the ``#lang`` definition which tells the
|
needs to start with the ``#lang`` definition which tells the
|
||||||
|
|
Loading…
Reference in New Issue