mirror of https://github.com/YosysHQ/yosys.git
functional backend: more documentation
This commit is contained in:
parent
b428bf4600
commit
27efed27c2
|
@ -5,16 +5,17 @@ To simplify the writing of backends for functional languages or similar targets,
|
|||
|
||||
FunctionalIR represents the design as a function `(inputs, current_state) -> (outputs, next_state)`.
|
||||
This function is broken down into a series of assignments to variables.
|
||||
Those assignments only use simple operations, like a simple addition.
|
||||
Unlike RTLIL cells there is no support for automatically extending operands, every sign and zero extension has to be encoded as a separate operation.
|
||||
Each assignment is a simple operation, such as an addition.
|
||||
Complex operations are broken up into multiple steps.
|
||||
For example, an RTLIL addition will be translated into a sign/zero extension of the inputs, followed by an addition.
|
||||
|
||||
Like SSA form, each variable is assigned to exactly once.
|
||||
We can thus treat variables and assignments as equivalent and, since this is a graph-like representation, those variables are also called "nodes".
|
||||
Unlike RTLIL's cells and wires representation, this representation is strictly ordered (topologically sorted) with definitions preceding their use.
|
||||
|
||||
Nodes are strongly typed, the two types (also called "sorts") available are
|
||||
Every node has a "sort" (the FunctionalIR term for what might otherwise be called a "type"). The sorts available are
|
||||
- `bit[n]` for an `n`-bit bitvector, and
|
||||
- `memory[n,m]` for an immutable array of `2**n` values of type `bit[m]`.
|
||||
- `memory[n,m]` for an immutable array of `2**n` values of sort `bit[m]`.
|
||||
|
||||
In terms of actual code, Yosys provides a class `Functional::IR` that represents a design in FunctionalIR.
|
||||
`Functional::IR::from_module` generates an instance from an RTLIL module.
|
||||
|
@ -22,12 +23,14 @@ The entire design is stored as a whole in an internal data structure.
|
|||
To access the design, the `Functional::Node` class provides a reference to a particular node in the design.
|
||||
The `Functional::IR` class supports the syntax `for(auto node : ir)` to iterate over every node.
|
||||
|
||||
`Functional::IR` also keeps track of inputs, outputs and "states" (registers and memories).
|
||||
They all have a name (equal to their name in RTLIL), a sort and a "type".
|
||||
`Functional::IR` also keeps track of inputs, outputs and states.
|
||||
By a "state" we mean a pair of a "current state" input and a "next state" output.
|
||||
One such pair is created for every register and for every memory.
|
||||
Every input, output and state has a name (equal to their name in RTLIL), a sort and a "type".
|
||||
The "type" field usually remains as the default value `$input`, `$output` or `$state`, however some RTLIL cells such as `$assert` or `$anyseq` generate auxiliary inputs/outputs/states that are given a different type to distinguish them from ordinary RTLIL inputs/outputs/states.
|
||||
- To access an individual input/output/"state", use `ir.input(name, type)`, `ir.output(name, type)` or `ir.state(name, type)`. `type` defaults to the default type.
|
||||
- To iterate over all inputs/outputs/"states" of a certain "type", methods `ir.inputs`, `ir.outputs`, `ir.states` are provided. Their argument defaults to the default types mentioned.
|
||||
- To iterate over inputs/outputs/"states" of any "type", use `ir.all_inputs`, `ir.all_outputs` and `ir.all_states`.
|
||||
- To access an individual input/output/state, use `ir.input(name, type)`, `ir.output(name, type)` or `ir.state(name, type)`. `type` defaults to the default type.
|
||||
- To iterate over all inputs/outputs/states of a certain "type", methods `ir.inputs`, `ir.outputs`, `ir.states` are provided. Their argument defaults to the default types mentioned.
|
||||
- To iterate over inputs/outputs/states of any "type", use `ir.all_inputs`, `ir.all_outputs` and `ir.all_states`.
|
||||
- Outputs have a node that indicate the value of the output, this can be retrieved via `output.value()`.
|
||||
- States have a node that indicate the next value of the state, this can be retrieved via `state.next_value()`.
|
||||
They also have an initial value that is accessed as either `state.initial_value_signal()` or `state.initial_value_memory()`, depending on their sort.
|
||||
|
@ -36,7 +39,47 @@ Each node has a "function", which defines its operation (for a complete list of
|
|||
Functions are represented as an enum `Functional::Fn` and the function field can be accessed as `node.fn()`.
|
||||
Since the most common operation is a switch over the function that also accesses the arguments, the `Node` class provides a method `visit` that implements the visitor pattern.
|
||||
For example, for an addition node `node` with arguments `n1` and `n2`, `node.visit(visitor)` would call `visitor.add(node, n1, n2)`.
|
||||
Thus typically one would implement class with a method for every function.
|
||||
Thus typically one would implement a class with a method for every function.
|
||||
Visitors should inherit from either `Functional::AbstractVisitor<ReturnType>` or `Functional::DefaultVisitor<ReturnType>`.
|
||||
The former will produce a compiler error if a case is unhandled, the latter will call `default_handler(node)` instead.
|
||||
Visitor methods should be marked as `override` to provide compiler errors if the arguments are wrong.
|
||||
Visitor methods should be marked as `override` to provide compiler errors if the arguments are wrong.
|
||||
|
||||
Utility classes
|
||||
-----------------
|
||||
|
||||
`functional.h` also provides utility classes that are independent of the main FunctionalIR representation but are likely to be useful for backends.
|
||||
|
||||
`Functional::Writer` provides a simple formatting class that wraps a `std::ostream` and provides the following methods:
|
||||
- `writer << value` wraps `os << value`.
|
||||
- `writer.print(fmt, value0, value1, value2, ...)` replaces `{0}`, `{1}`, `{2}`, etc in the string `fmt` with `value0`, `value1`, `value2`, resp.
|
||||
Each value is formatted using `os << value`.
|
||||
It is also possible to write `{}` to refer to one past the last index, i.e. `{1} {} {} {7} {}` is equivalent to `{1} {2} {3} {7} {8}`.
|
||||
- `writer.print_with(fn, fmt, value0, value1, value2, ...)` functions much the same as `print` but it uses `os << fn(value)` to print each value and falls back to `os << value` if `fn(value)` is not legal.
|
||||
|
||||
`Functional::Scope` keeps track of variable names in a target language.
|
||||
It is used to translate between different sets of legal characters and to avoid accidentally re-defining identifiers.
|
||||
Users should derive a class from `Scope` and supply the following:
|
||||
- `Scope<Id>` takes a template argument that specifies a type that's used to uniquely distinguish variables.
|
||||
Typically this would be `int` (if variables are used for `Functional::IR` nodes) or `IdString`.
|
||||
- The derived class should provide a constructor that calls `reserve` for every reserved word in the target language.
|
||||
- A method `bool is_legal_character(char c, int index)` has to be provided that returns `true` iff `c` is legal in an identifier at position `index`.
|
||||
Given an instance `scope` of the derived class, the following methods are then available:
|
||||
- `scope.reserve(std::string name)` marks the given name as being in-use
|
||||
- `scope.unique_name(IdString suggestion)` generates a previously unused name and attempts to make it similar to `suggestion`.
|
||||
- `scope(Id id, IdString suggestion)` functions similar to `unique_name`, except that multiple calls with the same `id` are guaranteed to retrieve the same name (independent of `suggestion`).
|
||||
|
||||
`sexpr.h` provides classes that represent and pretty-print s-expressions.
|
||||
S-expressions can be constructed with `SExpr::list`, for example `SExpr expr = SExpr::list("add", "x", SExpr::list("mul", "y", "z"))` represents `(add x (mul y z))`
|
||||
(by adding `using SExprUtil::list` to the top of the file, `list` can be used as shorthand for `SExpr::list`).
|
||||
For prettyprinting, `SExprWriter` wraps an `std::ostream` and provides the following methods:
|
||||
- `writer << sexpr` writes the provided expression to the output, breaking long lines and adding appropriate indentation.
|
||||
- `writer.open(sexpr)` is similar to `writer << sexpr` but will omit the last closing parenthesis.
|
||||
Further arguments can then be added separately with `<<` or `open`.
|
||||
This allows for printing large s-expressions without needing the construct the whole expression in memory first.
|
||||
- `writer.open(sexpr, false)` is similar to `writer.open(sexpr)` but further arguments will not be indented.
|
||||
This is used to avoid unlimited indentation on structures with unlimited nesting.
|
||||
- `writer.close(n = 1)` closes the last `n` open s-expressions.
|
||||
- `writer.push()` and `writer.pop()` are used to automatically close s-expressions.
|
||||
`writer.pop()` closes all s-expressions opened since the last call to `writer.push()`.
|
||||
- `writer.comment(string)` writes a comment on a separate-line.
|
||||
`writer.comment(string, true)` appends a comment to the last printed s-expression.
|
Loading…
Reference in New Issue