mirror of
				https://github.com/YosysHQ/yosys
				synced 2025-11-04 05:19:11 +00:00 
			
		
		
		
	functional backend: more documentation
This commit is contained in:
		
							parent
							
								
									b428bf4600
								
							
						
					
					
						commit
						27efed27c2
					
				
					 1 changed files with 54 additions and 11 deletions
				
			
		| 
						 | 
				
			
			@ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue