mirror of https://github.com/YosysHQ/yosys.git
convert class FunctionalIR to a namespace Functional, rename functionalir.h to functional.h, rename functional.h to compute_graph.h
This commit is contained in:
parent
8c0f625c3a
commit
850b3a6c29
2
Makefile
2
Makefile
|
@ -640,7 +640,7 @@ $(eval $(call add_include_file,backends/rtlil/rtlil_backend.h))
|
||||||
OBJS += kernel/driver.o kernel/register.o kernel/rtlil.o kernel/log.o kernel/calc.o kernel/yosys.o
|
OBJS += kernel/driver.o kernel/register.o kernel/rtlil.o kernel/log.o kernel/calc.o kernel/yosys.o
|
||||||
OBJS += kernel/binding.o
|
OBJS += kernel/binding.o
|
||||||
OBJS += kernel/cellaigs.o kernel/celledges.o kernel/cost.o kernel/satgen.o kernel/scopeinfo.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o kernel/sexpr.o
|
OBJS += kernel/cellaigs.o kernel/celledges.o kernel/cost.o kernel/satgen.o kernel/scopeinfo.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o kernel/sexpr.o
|
||||||
OBJS += kernel/drivertools.o kernel/functionalir.o
|
OBJS += kernel/drivertools.o kernel/functional.o
|
||||||
ifeq ($(ENABLE_ZLIB),1)
|
ifeq ($(ENABLE_ZLIB),1)
|
||||||
OBJS += kernel/fstdata.o
|
OBJS += kernel/fstdata.o
|
||||||
endif
|
endif
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "kernel/yosys.h"
|
#include "kernel/yosys.h"
|
||||||
#include "kernel/functionalir.h"
|
#include "kernel/functional.h"
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
|
||||||
USING_YOSYS_NAMESPACE
|
USING_YOSYS_NAMESPACE
|
||||||
|
@ -42,7 +42,7 @@ const char *reserved_keywords[] = {
|
||||||
nullptr
|
nullptr
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename Id> struct CxxScope : public FunctionalTools::Scope<Id> {
|
template<typename Id> struct CxxScope : public Functional::Scope<Id> {
|
||||||
CxxScope() {
|
CxxScope() {
|
||||||
for(const char **p = reserved_keywords; *p != nullptr; p++)
|
for(const char **p = reserved_keywords; *p != nullptr; p++)
|
||||||
this->reserve(*p);
|
this->reserve(*p);
|
||||||
|
@ -53,8 +53,8 @@ template<typename Id> struct CxxScope : public FunctionalTools::Scope<Id> {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CxxType {
|
struct CxxType {
|
||||||
FunctionalIR::Sort sort;
|
Functional::Sort sort;
|
||||||
CxxType(FunctionalIR::Sort sort) : sort(sort) {}
|
CxxType(Functional::Sort sort) : sort(sort) {}
|
||||||
std::string to_string() const {
|
std::string to_string() const {
|
||||||
if(sort.is_memory()) {
|
if(sort.is_memory()) {
|
||||||
return stringf("Memory<%d, %d>", sort.addr_width(), sort.data_width());
|
return stringf("Memory<%d, %d>", sort.addr_width(), sort.data_width());
|
||||||
|
@ -66,7 +66,7 @@ struct CxxType {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using CxxWriter = FunctionalTools::Writer;
|
using CxxWriter = Functional::Writer;
|
||||||
|
|
||||||
struct CxxStruct {
|
struct CxxStruct {
|
||||||
std::string name;
|
std::string name;
|
||||||
|
@ -111,8 +111,8 @@ std::string cxx_const(RTLIL::Const const &value) {
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class NodePrinter> struct CxxPrintVisitor : public FunctionalIR::AbstractVisitor<void> {
|
template<class NodePrinter> struct CxxPrintVisitor : public Functional::AbstractVisitor<void> {
|
||||||
using Node = FunctionalIR::Node;
|
using Node = Functional::Node;
|
||||||
CxxWriter &f;
|
CxxWriter &f;
|
||||||
NodePrinter np;
|
NodePrinter np;
|
||||||
CxxStruct &input_struct;
|
CxxStruct &input_struct;
|
||||||
|
@ -165,12 +165,12 @@ bool equal_def(RTLIL::Const const &a, RTLIL::Const const &b) {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CxxModule {
|
struct CxxModule {
|
||||||
FunctionalIR ir;
|
Functional::IR ir;
|
||||||
CxxStruct input_struct, output_struct, state_struct;
|
CxxStruct input_struct, output_struct, state_struct;
|
||||||
std::string module_name;
|
std::string module_name;
|
||||||
|
|
||||||
explicit CxxModule(Module *module) :
|
explicit CxxModule(Module *module) :
|
||||||
ir(FunctionalIR::from_module(module)),
|
ir(Functional::IR::from_module(module)),
|
||||||
input_struct("Inputs"),
|
input_struct("Inputs"),
|
||||||
output_struct("Outputs"),
|
output_struct("Outputs"),
|
||||||
state_struct("State")
|
state_struct("State")
|
||||||
|
@ -222,7 +222,7 @@ struct CxxModule {
|
||||||
locals.reserve("output");
|
locals.reserve("output");
|
||||||
locals.reserve("current_state");
|
locals.reserve("current_state");
|
||||||
locals.reserve("next_state");
|
locals.reserve("next_state");
|
||||||
auto node_name = [&](FunctionalIR::Node n) { return locals(n.id(), n.name()); };
|
auto node_name = [&](Functional::Node n) { return locals(n.id(), n.name()); };
|
||||||
CxxPrintVisitor printVisitor(f, node_name, input_struct, state_struct);
|
CxxPrintVisitor printVisitor(f, node_name, input_struct, state_struct);
|
||||||
for (auto node : ir) {
|
for (auto node : ir) {
|
||||||
f.print("\t{} {} = ", CxxType(node.sort()).to_string(), node_name(node));
|
f.print("\t{} {} = ", CxxType(node.sort()).to_string(), node_name(node));
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "kernel/functionalir.h"
|
#include "kernel/functional.h"
|
||||||
#include "kernel/yosys.h"
|
#include "kernel/yosys.h"
|
||||||
#include "kernel/sexpr.h"
|
#include "kernel/sexpr.h"
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
@ -42,7 +42,7 @@ const char *reserved_keywords[] = {
|
||||||
nullptr
|
nullptr
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SmtScope : public FunctionalTools::Scope<int> {
|
struct SmtScope : public Functional::Scope<int> {
|
||||||
SmtScope() {
|
SmtScope() {
|
||||||
for(const char **p = reserved_keywords; *p != nullptr; p++)
|
for(const char **p = reserved_keywords; *p != nullptr; p++)
|
||||||
reserve(*p);
|
reserve(*p);
|
||||||
|
@ -53,8 +53,8 @@ struct SmtScope : public FunctionalTools::Scope<int> {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SmtSort {
|
struct SmtSort {
|
||||||
FunctionalIR::Sort sort;
|
Functional::Sort sort;
|
||||||
SmtSort(FunctionalIR::Sort sort) : sort(sort) {}
|
SmtSort(Functional::Sort sort) : sort(sort) {}
|
||||||
SExpr to_sexpr() const {
|
SExpr to_sexpr() const {
|
||||||
if(sort.is_memory()) {
|
if(sort.is_memory()) {
|
||||||
return list("Array", list("_", "BitVec", sort.addr_width()), list("_", "BitVec", sort.data_width()));
|
return list("Array", list("_", "BitVec", sort.addr_width()), list("_", "BitVec", sort.data_width()));
|
||||||
|
@ -116,8 +116,8 @@ std::string smt_const(RTLIL::Const const &c) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SmtPrintVisitor : public FunctionalIR::AbstractVisitor<SExpr> {
|
struct SmtPrintVisitor : public Functional::AbstractVisitor<SExpr> {
|
||||||
using Node = FunctionalIR::Node;
|
using Node = Functional::Node;
|
||||||
std::function<SExpr(Node)> n;
|
std::function<SExpr(Node)> n;
|
||||||
SmtStruct &input_struct;
|
SmtStruct &input_struct;
|
||||||
SmtStruct &state_struct;
|
SmtStruct &state_struct;
|
||||||
|
@ -183,7 +183,7 @@ struct SmtPrintVisitor : public FunctionalIR::AbstractVisitor<SExpr> {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SmtModule {
|
struct SmtModule {
|
||||||
FunctionalIR ir;
|
Functional::IR ir;
|
||||||
SmtScope scope;
|
SmtScope scope;
|
||||||
std::string name;
|
std::string name;
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ struct SmtModule {
|
||||||
SmtStruct state_struct;
|
SmtStruct state_struct;
|
||||||
|
|
||||||
SmtModule(Module *module)
|
SmtModule(Module *module)
|
||||||
: ir(FunctionalIR::from_module(module))
|
: ir(Functional::IR::from_module(module))
|
||||||
, scope()
|
, scope()
|
||||||
, name(scope.unique_name(module->name))
|
, name(scope.unique_name(module->name))
|
||||||
, input_struct(scope.unique_name(module->name.str() + "_Inputs"), scope)
|
, input_struct(scope.unique_name(module->name.str() + "_Inputs"), scope)
|
||||||
|
@ -215,11 +215,11 @@ struct SmtModule {
|
||||||
list(list("inputs", input_struct.name),
|
list(list("inputs", input_struct.name),
|
||||||
list("state", state_struct.name)),
|
list("state", state_struct.name)),
|
||||||
list("Pair", output_struct.name, state_struct.name)));
|
list("Pair", output_struct.name, state_struct.name)));
|
||||||
auto inlined = [&](FunctionalIR::Node n) {
|
auto inlined = [&](Functional::Node n) {
|
||||||
return n.fn() == FunctionalIR::Fn::constant;
|
return n.fn() == Functional::Fn::constant;
|
||||||
};
|
};
|
||||||
SmtPrintVisitor visitor(input_struct, state_struct);
|
SmtPrintVisitor visitor(input_struct, state_struct);
|
||||||
auto node_to_sexpr = [&](FunctionalIR::Node n) -> SExpr {
|
auto node_to_sexpr = [&](Functional::Node n) -> SExpr {
|
||||||
if(inlined(n))
|
if(inlined(n))
|
||||||
return n.visit(visitor);
|
return n.visit(visitor);
|
||||||
else
|
else
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "kernel/yosys.h"
|
#include "kernel/yosys.h"
|
||||||
#include "kernel/functionalir.h"
|
#include "kernel/functional.h"
|
||||||
#include <random>
|
#include <random>
|
||||||
|
|
||||||
USING_YOSYS_NAMESPACE
|
USING_YOSYS_NAMESPACE
|
||||||
|
@ -139,7 +139,7 @@ struct FunctionalTestGeneric : public Pass
|
||||||
|
|
||||||
for (auto module : design->selected_modules()) {
|
for (auto module : design->selected_modules()) {
|
||||||
log("Dumping module `%s'.\n", module->name.c_str());
|
log("Dumping module `%s'.\n", module->name.c_str());
|
||||||
auto fir = FunctionalIR::from_module(module);
|
auto fir = Functional::IR::from_module(module);
|
||||||
for(auto node : fir)
|
for(auto node : fir)
|
||||||
std::cout << RTLIL::unescape_id(node.name()) << " = " << node.to_string([](auto n) { return RTLIL::unescape_id(n.name()); }) << "\n";
|
std::cout << RTLIL::unescape_id(node.name()) << " = " << node.to_string([](auto n) { return RTLIL::unescape_id(n.name()); }) << "\n";
|
||||||
for(auto [name, sort] : fir.outputs())
|
for(auto [name, sort] : fir.outputs())
|
||||||
|
|
|
@ -0,0 +1,403 @@
|
||||||
|
/*
|
||||||
|
* yosys -- Yosys Open SYnthesis Suite
|
||||||
|
*
|
||||||
|
* Copyright (C) 2024 Jannis Harder <jix@yosyshq.com> <me@jix.one>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef COMPUTE_GRAPH_H
|
||||||
|
#define COMPUTE_GRAPH_H
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
#include "kernel/yosys.h"
|
||||||
|
|
||||||
|
YOSYS_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
template<
|
||||||
|
typename Fn, // Function type (deduplicated across whole graph)
|
||||||
|
typename Attr = std::tuple<>, // Call attributes (present in every node)
|
||||||
|
typename SparseAttr = std::tuple<>, // Sparse call attributes (optional per node)
|
||||||
|
typename Key = std::tuple<> // Stable keys to refer to nodes
|
||||||
|
>
|
||||||
|
struct ComputeGraph
|
||||||
|
{
|
||||||
|
struct Ref;
|
||||||
|
private:
|
||||||
|
|
||||||
|
// Functions are deduplicated by assigning unique ids
|
||||||
|
idict<Fn> functions;
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
int fn_index;
|
||||||
|
int arg_offset;
|
||||||
|
int arg_count;
|
||||||
|
Attr attr;
|
||||||
|
|
||||||
|
Node(int fn_index, Attr &&attr, int arg_offset, int arg_count = 0)
|
||||||
|
: fn_index(fn_index), arg_offset(arg_offset), arg_count(arg_count), attr(std::move(attr)) {}
|
||||||
|
|
||||||
|
Node(int fn_index, Attr const &attr, int arg_offset, int arg_count = 0)
|
||||||
|
: fn_index(fn_index), arg_offset(arg_offset), arg_count(arg_count), attr(attr) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<Node> nodes;
|
||||||
|
std::vector<int> args;
|
||||||
|
dict<Key, int> keys_;
|
||||||
|
dict<int, SparseAttr> sparse_attrs;
|
||||||
|
|
||||||
|
public:
|
||||||
|
template<typename Graph>
|
||||||
|
struct BaseRef
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
friend struct ComputeGraph;
|
||||||
|
Graph *graph_;
|
||||||
|
int index_;
|
||||||
|
BaseRef(Graph *graph, int index) : graph_(graph), index_(index) {
|
||||||
|
log_assert(index_ >= 0);
|
||||||
|
check();
|
||||||
|
}
|
||||||
|
|
||||||
|
void check() const { log_assert(index_ < graph_->size()); }
|
||||||
|
|
||||||
|
Node const &deref() const { check(); return graph_->nodes[index_]; }
|
||||||
|
|
||||||
|
public:
|
||||||
|
ComputeGraph const &graph() const { return graph_; }
|
||||||
|
int index() const { return index_; }
|
||||||
|
|
||||||
|
int size() const { return deref().arg_count; }
|
||||||
|
|
||||||
|
BaseRef arg(int n) const
|
||||||
|
{
|
||||||
|
Node const &node = deref();
|
||||||
|
log_assert(n >= 0 && n < node.arg_count);
|
||||||
|
return BaseRef(graph_, graph_->args[node.arg_offset + n]);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int>::const_iterator arg_indices_cbegin() const
|
||||||
|
{
|
||||||
|
Node const &node = deref();
|
||||||
|
return graph_->args.cbegin() + node.arg_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int>::const_iterator arg_indices_cend() const
|
||||||
|
{
|
||||||
|
Node const &node = deref();
|
||||||
|
return graph_->args.cbegin() + node.arg_offset + node.arg_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
Fn const &function() const { return graph_->functions[deref().fn_index]; }
|
||||||
|
Attr const &attr() const { return deref().attr; }
|
||||||
|
|
||||||
|
bool has_sparse_attr() const { return graph_->sparse_attrs.count(index_); }
|
||||||
|
|
||||||
|
SparseAttr const &sparse_attr() const
|
||||||
|
{
|
||||||
|
auto found = graph_->sparse_attrs.find(index_);
|
||||||
|
log_assert(found != graph_->sparse_attrs.end());
|
||||||
|
return found->second;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using ConstRef = BaseRef<ComputeGraph const>;
|
||||||
|
|
||||||
|
struct Ref : public BaseRef<ComputeGraph>
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
friend struct ComputeGraph;
|
||||||
|
Ref(ComputeGraph *graph, int index) : BaseRef<ComputeGraph>(graph, index) {}
|
||||||
|
Node &deref() const { this->check(); return this->graph_->nodes[this->index_]; }
|
||||||
|
|
||||||
|
public:
|
||||||
|
Ref(BaseRef<ComputeGraph> ref) : Ref(ref.graph_, ref.index_) {}
|
||||||
|
|
||||||
|
void set_function(Fn const &function) const
|
||||||
|
{
|
||||||
|
deref().fn_index = this->graph_->functions(function);
|
||||||
|
}
|
||||||
|
|
||||||
|
Attr &attr() const { return deref().attr; }
|
||||||
|
|
||||||
|
void append_arg(ConstRef arg) const
|
||||||
|
{
|
||||||
|
log_assert(arg.graph_ == this->graph_);
|
||||||
|
append_arg(arg.index());
|
||||||
|
}
|
||||||
|
|
||||||
|
void append_arg(int arg) const
|
||||||
|
{
|
||||||
|
log_assert(arg >= 0 && arg < this->graph_->size());
|
||||||
|
Node &node = deref();
|
||||||
|
if (node.arg_offset + node.arg_count != GetSize(this->graph_->args))
|
||||||
|
move_args(node);
|
||||||
|
this->graph_->args.push_back(arg);
|
||||||
|
node.arg_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator ConstRef() const
|
||||||
|
{
|
||||||
|
return ConstRef(this->graph_, this->index_);
|
||||||
|
}
|
||||||
|
|
||||||
|
SparseAttr &sparse_attr() const
|
||||||
|
{
|
||||||
|
return this->graph_->sparse_attrs[this->index_];
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_sparse_attr() const
|
||||||
|
{
|
||||||
|
this->graph_->sparse_attrs.erase(this->index_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void assign_key(Key const &key) const
|
||||||
|
{
|
||||||
|
this->graph_->keys_.emplace(key, this->index_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void move_args(Node &node) const
|
||||||
|
{
|
||||||
|
auto &args = this->graph_->args;
|
||||||
|
int old_offset = node.arg_offset;
|
||||||
|
node.arg_offset = GetSize(args);
|
||||||
|
for (int i = 0; i != node.arg_count; ++i)
|
||||||
|
args.push_back(args[old_offset + i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
bool has_key(Key const &key) const
|
||||||
|
{
|
||||||
|
return keys_.count(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
dict<Key, int> const &keys() const
|
||||||
|
{
|
||||||
|
return keys_;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConstRef operator()(Key const &key) const
|
||||||
|
{
|
||||||
|
auto it = keys_.find(key);
|
||||||
|
log_assert(it != keys_.end());
|
||||||
|
return (*this)[it->second];
|
||||||
|
}
|
||||||
|
|
||||||
|
Ref operator()(Key const &key)
|
||||||
|
{
|
||||||
|
auto it = keys_.find(key);
|
||||||
|
log_assert(it != keys_.end());
|
||||||
|
return (*this)[it->second];
|
||||||
|
}
|
||||||
|
|
||||||
|
int size() const { return GetSize(nodes); }
|
||||||
|
|
||||||
|
ConstRef operator[](int index) const { return ConstRef(this, index); }
|
||||||
|
Ref operator[](int index) { return Ref(this, index); }
|
||||||
|
|
||||||
|
Ref add(Fn const &function, Attr &&attr)
|
||||||
|
{
|
||||||
|
int index = GetSize(nodes);
|
||||||
|
int fn_index = functions(function);
|
||||||
|
nodes.emplace_back(fn_index, std::move(attr), GetSize(args));
|
||||||
|
return Ref(this, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ref add(Fn const &function, Attr const &attr)
|
||||||
|
{
|
||||||
|
int index = GetSize(nodes);
|
||||||
|
int fn_index = functions(function);
|
||||||
|
nodes.emplace_back(fn_index, attr, GetSize(args));
|
||||||
|
return Ref(this, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
Ref add(Fn const &function, Attr const &attr, T &&args)
|
||||||
|
{
|
||||||
|
Ref added = add(function, attr);
|
||||||
|
for (auto arg : args)
|
||||||
|
added.append_arg(arg);
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
Ref add(Fn const &function, Attr &&attr, T &&args)
|
||||||
|
{
|
||||||
|
Ref added = add(function, std::move(attr));
|
||||||
|
for (auto arg : args)
|
||||||
|
added.append_arg(arg);
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ref add(Fn const &function, Attr const &attr, std::initializer_list<Ref> args)
|
||||||
|
{
|
||||||
|
Ref added = add(function, attr);
|
||||||
|
for (auto arg : args)
|
||||||
|
added.append_arg(arg);
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ref add(Fn const &function, Attr &&attr, std::initializer_list<Ref> args)
|
||||||
|
{
|
||||||
|
Ref added = add(function, std::move(attr));
|
||||||
|
for (auto arg : args)
|
||||||
|
added.append_arg(arg);
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
Ref add(Fn const &function, Attr const &attr, T begin, T end)
|
||||||
|
{
|
||||||
|
Ref added = add(function, attr);
|
||||||
|
for (; begin != end; ++begin)
|
||||||
|
added.append_arg(*begin);
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
void compact_args()
|
||||||
|
{
|
||||||
|
std::vector<int> new_args;
|
||||||
|
for (auto &node : nodes)
|
||||||
|
{
|
||||||
|
int new_offset = GetSize(new_args);
|
||||||
|
for (int i = 0; i < node.arg_count; i++)
|
||||||
|
new_args.push_back(args[node.arg_offset + i]);
|
||||||
|
node.arg_offset = new_offset;
|
||||||
|
}
|
||||||
|
std::swap(args, new_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void permute(std::vector<int> const &perm)
|
||||||
|
{
|
||||||
|
log_assert(perm.size() <= nodes.size());
|
||||||
|
std::vector<int> inv_perm;
|
||||||
|
inv_perm.resize(nodes.size(), -1);
|
||||||
|
for (int i = 0; i < GetSize(perm); ++i)
|
||||||
|
{
|
||||||
|
int j = perm[i];
|
||||||
|
log_assert(j >= 0 && j < GetSize(nodes));
|
||||||
|
log_assert(inv_perm[j] == -1);
|
||||||
|
inv_perm[j] = i;
|
||||||
|
}
|
||||||
|
permute(perm, inv_perm);
|
||||||
|
}
|
||||||
|
|
||||||
|
void permute(std::vector<int> const &perm, std::vector<int> const &inv_perm)
|
||||||
|
{
|
||||||
|
log_assert(inv_perm.size() == nodes.size());
|
||||||
|
std::vector<Node> new_nodes;
|
||||||
|
new_nodes.reserve(perm.size());
|
||||||
|
dict<int, SparseAttr> new_sparse_attrs;
|
||||||
|
for (int i : perm)
|
||||||
|
{
|
||||||
|
int j = GetSize(new_nodes);
|
||||||
|
new_nodes.emplace_back(std::move(nodes[i]));
|
||||||
|
auto found = sparse_attrs.find(i);
|
||||||
|
if (found != sparse_attrs.end())
|
||||||
|
new_sparse_attrs.emplace(j, std::move(found->second));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::swap(nodes, new_nodes);
|
||||||
|
std::swap(sparse_attrs, new_sparse_attrs);
|
||||||
|
|
||||||
|
compact_args();
|
||||||
|
for (int &arg : args)
|
||||||
|
{
|
||||||
|
log_assert(arg < GetSize(inv_perm));
|
||||||
|
log_assert(inv_perm[arg] >= 0);
|
||||||
|
arg = inv_perm[arg];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &key : keys_)
|
||||||
|
{
|
||||||
|
log_assert(key.second < GetSize(inv_perm));
|
||||||
|
log_assert(inv_perm[key.second] >= 0);
|
||||||
|
key.second = inv_perm[key.second];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SccAdaptor
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
ComputeGraph const &graph_;
|
||||||
|
std::vector<int> indices_;
|
||||||
|
public:
|
||||||
|
SccAdaptor(ComputeGraph const &graph) : graph_(graph)
|
||||||
|
{
|
||||||
|
indices_.resize(graph.size(), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
typedef int node_type;
|
||||||
|
|
||||||
|
struct node_enumerator {
|
||||||
|
private:
|
||||||
|
friend struct SccAdaptor;
|
||||||
|
int current, end;
|
||||||
|
node_enumerator(int current, int end) : current(current), end(end) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
bool finished() const { return current == end; }
|
||||||
|
node_type next() {
|
||||||
|
log_assert(!finished());
|
||||||
|
node_type result = current;
|
||||||
|
++current;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
node_enumerator enumerate_nodes() {
|
||||||
|
return node_enumerator(0, GetSize(indices_));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct successor_enumerator {
|
||||||
|
private:
|
||||||
|
friend struct SccAdaptor;
|
||||||
|
std::vector<int>::const_iterator current, end;
|
||||||
|
successor_enumerator(std::vector<int>::const_iterator current, std::vector<int>::const_iterator end) :
|
||||||
|
current(current), end(end) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool finished() const { return current == end; }
|
||||||
|
node_type next() {
|
||||||
|
log_assert(!finished());
|
||||||
|
node_type result = *current;
|
||||||
|
++current;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
successor_enumerator enumerate_successors(int index) const {
|
||||||
|
auto const &ref = graph_[index];
|
||||||
|
return successor_enumerator(ref.arg_indices_cbegin(), ref.arg_indices_cend());
|
||||||
|
}
|
||||||
|
|
||||||
|
int &dfs_index(node_type const &node) { return indices_[node]; }
|
||||||
|
|
||||||
|
std::vector<int> const &dfs_indices() { return indices_; }
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
YOSYS_NAMESPACE_END
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
|
@ -17,55 +17,55 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "kernel/functionalir.h"
|
#include "kernel/functional.h"
|
||||||
#include <optional>
|
#include "kernel/topo_scc.h"
|
||||||
#include "ff.h"
|
#include "ff.h"
|
||||||
#include "ffinit.h"
|
#include "ffinit.h"
|
||||||
|
|
||||||
YOSYS_NAMESPACE_BEGIN
|
YOSYS_NAMESPACE_BEGIN
|
||||||
|
namespace Functional {
|
||||||
|
|
||||||
const char *FunctionalIR::fn_to_string(FunctionalIR::Fn fn) {
|
const char *fn_to_string(Fn fn) {
|
||||||
switch(fn) {
|
switch(fn) {
|
||||||
case FunctionalIR::Fn::invalid: return "invalid";
|
case Fn::invalid: return "invalid";
|
||||||
case FunctionalIR::Fn::buf: return "buf";
|
case Fn::buf: return "buf";
|
||||||
case FunctionalIR::Fn::slice: return "slice";
|
case Fn::slice: return "slice";
|
||||||
case FunctionalIR::Fn::zero_extend: return "zero_extend";
|
case Fn::zero_extend: return "zero_extend";
|
||||||
case FunctionalIR::Fn::sign_extend: return "sign_extend";
|
case Fn::sign_extend: return "sign_extend";
|
||||||
case FunctionalIR::Fn::concat: return "concat";
|
case Fn::concat: return "concat";
|
||||||
case FunctionalIR::Fn::add: return "add";
|
case Fn::add: return "add";
|
||||||
case FunctionalIR::Fn::sub: return "sub";
|
case Fn::sub: return "sub";
|
||||||
case FunctionalIR::Fn::mul: return "mul";
|
case Fn::mul: return "mul";
|
||||||
case FunctionalIR::Fn::unsigned_div: return "unsigned_div";
|
case Fn::unsigned_div: return "unsigned_div";
|
||||||
case FunctionalIR::Fn::unsigned_mod: return "unsigned_mod";
|
case Fn::unsigned_mod: return "unsigned_mod";
|
||||||
case FunctionalIR::Fn::bitwise_and: return "bitwise_and";
|
case Fn::bitwise_and: return "bitwise_and";
|
||||||
case FunctionalIR::Fn::bitwise_or: return "bitwise_or";
|
case Fn::bitwise_or: return "bitwise_or";
|
||||||
case FunctionalIR::Fn::bitwise_xor: return "bitwise_xor";
|
case Fn::bitwise_xor: return "bitwise_xor";
|
||||||
case FunctionalIR::Fn::bitwise_not: return "bitwise_not";
|
case Fn::bitwise_not: return "bitwise_not";
|
||||||
case FunctionalIR::Fn::reduce_and: return "reduce_and";
|
case Fn::reduce_and: return "reduce_and";
|
||||||
case FunctionalIR::Fn::reduce_or: return "reduce_or";
|
case Fn::reduce_or: return "reduce_or";
|
||||||
case FunctionalIR::Fn::reduce_xor: return "reduce_xor";
|
case Fn::reduce_xor: return "reduce_xor";
|
||||||
case FunctionalIR::Fn::unary_minus: return "unary_minus";
|
case Fn::unary_minus: return "unary_minus";
|
||||||
case FunctionalIR::Fn::equal: return "equal";
|
case Fn::equal: return "equal";
|
||||||
case FunctionalIR::Fn::not_equal: return "not_equal";
|
case Fn::not_equal: return "not_equal";
|
||||||
case FunctionalIR::Fn::signed_greater_than: return "signed_greater_than";
|
case Fn::signed_greater_than: return "signed_greater_than";
|
||||||
case FunctionalIR::Fn::signed_greater_equal: return "signed_greater_equal";
|
case Fn::signed_greater_equal: return "signed_greater_equal";
|
||||||
case FunctionalIR::Fn::unsigned_greater_than: return "unsigned_greater_than";
|
case Fn::unsigned_greater_than: return "unsigned_greater_than";
|
||||||
case FunctionalIR::Fn::unsigned_greater_equal: return "unsigned_greater_equal";
|
case Fn::unsigned_greater_equal: return "unsigned_greater_equal";
|
||||||
case FunctionalIR::Fn::logical_shift_left: return "logical_shift_left";
|
case Fn::logical_shift_left: return "logical_shift_left";
|
||||||
case FunctionalIR::Fn::logical_shift_right: return "logical_shift_right";
|
case Fn::logical_shift_right: return "logical_shift_right";
|
||||||
case FunctionalIR::Fn::arithmetic_shift_right: return "arithmetic_shift_right";
|
case Fn::arithmetic_shift_right: return "arithmetic_shift_right";
|
||||||
case FunctionalIR::Fn::mux: return "mux";
|
case Fn::mux: return "mux";
|
||||||
case FunctionalIR::Fn::constant: return "constant";
|
case Fn::constant: return "constant";
|
||||||
case FunctionalIR::Fn::input: return "input";
|
case Fn::input: return "input";
|
||||||
case FunctionalIR::Fn::state: return "state";
|
case Fn::state: return "state";
|
||||||
case FunctionalIR::Fn::memory_read: return "memory_read";
|
case Fn::memory_read: return "memory_read";
|
||||||
case FunctionalIR::Fn::memory_write: return "memory_write";
|
case Fn::memory_write: return "memory_write";
|
||||||
}
|
}
|
||||||
log_error("fn_to_string: unknown FunctionalIR::Fn value %d", (int)fn);
|
log_error("fn_to_string: unknown Functional::Fn value %d", (int)fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PrintVisitor : FunctionalIR::DefaultVisitor<std::string> {
|
struct PrintVisitor : DefaultVisitor<std::string> {
|
||||||
using Node = FunctionalIR::Node;
|
|
||||||
std::function<std::string(Node)> np;
|
std::function<std::string(Node)> np;
|
||||||
PrintVisitor(std::function<std::string(Node)> np) : np(np) { }
|
PrintVisitor(std::function<std::string(Node)> np) : np(np) { }
|
||||||
// as a general rule the default handler is good enough iff the only arguments are of type Node
|
// as a general rule the default handler is good enough iff the only arguments are of type Node
|
||||||
|
@ -76,7 +76,7 @@ struct PrintVisitor : FunctionalIR::DefaultVisitor<std::string> {
|
||||||
std::string input(Node, IdString name) override { return "input(" + name.str() + ")"; }
|
std::string input(Node, IdString name) override { return "input(" + name.str() + ")"; }
|
||||||
std::string state(Node, IdString name) override { return "state(" + name.str() + ")"; }
|
std::string state(Node, IdString name) override { return "state(" + name.str() + ")"; }
|
||||||
std::string default_handler(Node self) override {
|
std::string default_handler(Node self) override {
|
||||||
std::string ret = FunctionalIR::fn_to_string(self.fn());
|
std::string ret = fn_to_string(self.fn());
|
||||||
ret += "(";
|
ret += "(";
|
||||||
for(size_t i = 0; i < self.arg_count(); i++) {
|
for(size_t i = 0; i < self.arg_count(); i++) {
|
||||||
if(i > 0) ret += ", ";
|
if(i > 0) ret += ", ";
|
||||||
|
@ -87,19 +87,18 @@ struct PrintVisitor : FunctionalIR::DefaultVisitor<std::string> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string FunctionalIR::Node::to_string()
|
std::string Node::to_string()
|
||||||
{
|
{
|
||||||
return to_string([](Node n) { return RTLIL::unescape_id(n.name()); });
|
return to_string([](Node n) { return RTLIL::unescape_id(n.name()); });
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string FunctionalIR::Node::to_string(std::function<std::string(Node)> np)
|
std::string Node::to_string(std::function<std::string(Node)> np)
|
||||||
{
|
{
|
||||||
return visit(PrintVisitor(np));
|
return visit(PrintVisitor(np));
|
||||||
}
|
}
|
||||||
|
|
||||||
class CellSimplifier {
|
class CellSimplifier {
|
||||||
using Node = FunctionalIR::Node;
|
Factory &factory;
|
||||||
FunctionalIR::Factory &factory;
|
|
||||||
Node sign(Node a) {
|
Node sign(Node a) {
|
||||||
return factory.slice(a, a.width() - 1, 1);
|
return factory.slice(a, a.width() - 1, 1);
|
||||||
}
|
}
|
||||||
|
@ -138,7 +137,7 @@ public:
|
||||||
Node bb = factory.bitwise_and(b, s);
|
Node bb = factory.bitwise_and(b, s);
|
||||||
return factory.bitwise_or(aa, bb);
|
return factory.bitwise_or(aa, bb);
|
||||||
}
|
}
|
||||||
CellSimplifier(FunctionalIR::Factory &f) : factory(f) {}
|
CellSimplifier(Factory &f) : factory(f) {}
|
||||||
private:
|
private:
|
||||||
Node handle_pow(Node a0, Node b, int y_width, bool is_signed) {
|
Node handle_pow(Node a0, Node b, int y_width, bool is_signed) {
|
||||||
Node a = factory.extend(a0, y_width, is_signed);
|
Node a = factory.extend(a0, y_width, is_signed);
|
||||||
|
@ -400,12 +399,11 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
class FunctionalIRConstruction {
|
class FunctionalIRConstruction {
|
||||||
using Node = FunctionalIR::Node;
|
|
||||||
std::deque<std::variant<DriveSpec, Cell *>> queue;
|
std::deque<std::variant<DriveSpec, Cell *>> queue;
|
||||||
dict<DriveSpec, Node> graph_nodes;
|
dict<DriveSpec, Node> graph_nodes;
|
||||||
dict<std::pair<Cell *, IdString>, Node> cell_outputs;
|
dict<std::pair<Cell *, IdString>, Node> cell_outputs;
|
||||||
DriverMap driver_map;
|
DriverMap driver_map;
|
||||||
FunctionalIR::Factory& factory;
|
Factory& factory;
|
||||||
CellSimplifier simplifier;
|
CellSimplifier simplifier;
|
||||||
vector<Mem> memories_vector;
|
vector<Mem> memories_vector;
|
||||||
dict<Cell*, Mem*> memories;
|
dict<Cell*, Mem*> memories;
|
||||||
|
@ -442,7 +440,7 @@ class FunctionalIRConstruction {
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
public:
|
public:
|
||||||
FunctionalIRConstruction(Module *module, FunctionalIR::Factory &f)
|
FunctionalIRConstruction(Module *module, Factory &f)
|
||||||
: factory(f)
|
: factory(f)
|
||||||
, simplifier(f)
|
, simplifier(f)
|
||||||
, sig_map(module)
|
, sig_map(module)
|
||||||
|
@ -497,7 +495,7 @@ public:
|
||||||
// - Since wr port j can only have priority over wr port i if j > i, if we do writes in
|
// - Since wr port j can only have priority over wr port i if j > i, if we do writes in
|
||||||
// ascending index order the result will obey the priorty relation.
|
// ascending index order the result will obey the priorty relation.
|
||||||
vector<Node> read_results;
|
vector<Node> read_results;
|
||||||
factory.add_state(mem->cell->name, FunctionalIR::Sort(ceil_log2(mem->size), mem->width));
|
factory.add_state(mem->cell->name, Sort(ceil_log2(mem->size), mem->width));
|
||||||
factory.set_initial_state(mem->cell->name, MemContents(mem));
|
factory.set_initial_state(mem->cell->name, MemContents(mem));
|
||||||
Node node = factory.current_state(mem->cell->name);
|
Node node = factory.current_state(mem->cell->name);
|
||||||
for (size_t i = 0; i < mem->wr_ports.size(); i++) {
|
for (size_t i = 0; i < mem->wr_ports.size(); i++) {
|
||||||
|
@ -542,7 +540,7 @@ public:
|
||||||
if (!ff.has_gclk)
|
if (!ff.has_gclk)
|
||||||
log_error("The design contains a %s flip-flop at %s. This is not supported by the functional backend. "
|
log_error("The design contains a %s flip-flop at %s. This is not supported by the functional backend. "
|
||||||
"Call async2sync or clk2fflogic to avoid this error.\n", log_id(cell->type), log_id(cell));
|
"Call async2sync or clk2fflogic to avoid this error.\n", log_id(cell->type), log_id(cell));
|
||||||
factory.add_state(ff.name, FunctionalIR::Sort(ff.width));
|
factory.add_state(ff.name, Sort(ff.width));
|
||||||
Node q_value = factory.current_state(ff.name);
|
Node q_value = factory.current_state(ff.name);
|
||||||
factory.suggest_name(q_value, ff.name);
|
factory.suggest_name(q_value, ff.name);
|
||||||
factory.update_pending(cell_outputs.at({cell, ID(Q)}), q_value);
|
factory.update_pending(cell_outputs.at({cell, ID(Q)}), q_value);
|
||||||
|
@ -643,8 +641,8 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
FunctionalIR FunctionalIR::from_module(Module *module) {
|
IR IR::from_module(Module *module) {
|
||||||
FunctionalIR ir;
|
IR ir;
|
||||||
auto factory = ir.factory();
|
auto factory = ir.factory();
|
||||||
FunctionalIRConstruction ctor(module, factory);
|
FunctionalIRConstruction ctor(module, factory);
|
||||||
ctor.process_queue();
|
ctor.process_queue();
|
||||||
|
@ -653,7 +651,7 @@ FunctionalIR FunctionalIR::from_module(Module *module) {
|
||||||
return ir;
|
return ir;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FunctionalIR::topological_sort() {
|
void IR::topological_sort() {
|
||||||
Graph::SccAdaptor compute_graph_scc(_graph);
|
Graph::SccAdaptor compute_graph_scc(_graph);
|
||||||
bool scc = false;
|
bool scc = false;
|
||||||
std::vector<int> perm;
|
std::vector<int> perm;
|
||||||
|
@ -687,7 +685,7 @@ static IdString merge_name(IdString a, IdString b) {
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FunctionalIR::forward_buf() {
|
void IR::forward_buf() {
|
||||||
std::vector<int> perm, alias;
|
std::vector<int> perm, alias;
|
||||||
perm.clear();
|
perm.clear();
|
||||||
|
|
||||||
|
@ -734,7 +732,7 @@ static std::string quote_fmt(const char *fmt)
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FunctionalTools::Writer::print_impl(const char *fmt, vector<std::function<void()>> &fns)
|
void Writer::print_impl(const char *fmt, vector<std::function<void()>> &fns)
|
||||||
{
|
{
|
||||||
size_t next_index = 0;
|
size_t next_index = 0;
|
||||||
for(const char *p = fmt; *p != 0; p++)
|
for(const char *p = fmt; *p != 0; p++)
|
||||||
|
@ -770,4 +768,5 @@ void FunctionalTools::Writer::print_impl(const char *fmt, vector<std::function<v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
YOSYS_NAMESPACE_END
|
YOSYS_NAMESPACE_END
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* yosys -- Yosys Open SYnthesis Suite
|
* yosys -- Yosys Open SYnthesis Suite
|
||||||
*
|
*
|
||||||
* Copyright (C) 2024 Jannis Harder <jix@yosyshq.com> <me@jix.one>
|
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
||||||
*
|
*
|
||||||
* Permission to use, copy, modify, and/or distribute this software for any
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
@ -20,384 +20,571 @@
|
||||||
#ifndef FUNCTIONAL_H
|
#ifndef FUNCTIONAL_H
|
||||||
#define FUNCTIONAL_H
|
#define FUNCTIONAL_H
|
||||||
|
|
||||||
#include <tuple>
|
|
||||||
#include "kernel/yosys.h"
|
#include "kernel/yosys.h"
|
||||||
|
#include "kernel/compute_graph.h"
|
||||||
|
#include "kernel/drivertools.h"
|
||||||
|
#include "kernel/mem.h"
|
||||||
|
#include "kernel/utils.h"
|
||||||
|
|
||||||
|
USING_YOSYS_NAMESPACE
|
||||||
YOSYS_NAMESPACE_BEGIN
|
YOSYS_NAMESPACE_BEGIN
|
||||||
|
|
||||||
template<
|
namespace Functional {
|
||||||
typename Fn, // Function type (deduplicated across whole graph)
|
// each function is documented with a short pseudocode declaration or definition
|
||||||
typename Attr = std::tuple<>, // Call attributes (present in every node)
|
// standard C/Verilog operators are used to describe the result
|
||||||
typename SparseAttr = std::tuple<>, // Sparse call attributes (optional per node)
|
//
|
||||||
typename Key = std::tuple<> // Stable keys to refer to nodes
|
// the types used in this are:
|
||||||
>
|
// - bit[N]: a bitvector of N bits
|
||||||
struct ComputeGraph
|
// bit[N] can be indicated as signed or unsigned. this is not tracked by the functional backend
|
||||||
{
|
// but is meant to indicate how the value is interpreted
|
||||||
struct Ref;
|
// if a bit[N] is marked as neither signed nor unsigned, this means the result should be valid with *either* interpretation
|
||||||
private:
|
// - memory[N, M]: a memory with N address and M data bits
|
||||||
|
// - int: C++ int
|
||||||
// Functions are deduplicated by assigning unique ids
|
// - Const[N]: yosys RTLIL::Const (with size() == N)
|
||||||
idict<Fn> functions;
|
// - IdString: yosys IdString
|
||||||
|
// - any: used in documentation to indicate that the type is unconstrained
|
||||||
struct Node {
|
//
|
||||||
int fn_index;
|
// nodes in the functional backend are either of type bit[N] or memory[N,M] (for some N, M: int)
|
||||||
int arg_offset;
|
// additionally, they can carry a constant of type int, Const[N] or IdString
|
||||||
int arg_count;
|
// each node has a 'sort' field that stores the type of the node
|
||||||
Attr attr;
|
// slice, zero_extend, sign_extend use the type field to store out_width
|
||||||
|
enum class Fn {
|
||||||
Node(int fn_index, Attr &&attr, int arg_offset, int arg_count = 0)
|
// invalid() = known-invalid/shouldn't happen value
|
||||||
: fn_index(fn_index), arg_offset(arg_offset), arg_count(arg_count), attr(std::move(attr)) {}
|
// TODO: maybe remove this and use e.g. std::optional instead?
|
||||||
|
invalid,
|
||||||
Node(int fn_index, Attr const &attr, int arg_offset, int arg_count = 0)
|
// buf(a: any): any = a
|
||||||
: fn_index(fn_index), arg_offset(arg_offset), arg_count(arg_count), attr(attr) {}
|
// no-op operation
|
||||||
};
|
// when constructing the compute graph we generate invalid buf() nodes as a placeholder
|
||||||
|
// and later insert the argument
|
||||||
|
buf,
|
||||||
std::vector<Node> nodes;
|
// slice(a: bit[in_width], offset: int, out_width: int): bit[out_width] = a[offset +: out_width]
|
||||||
std::vector<int> args;
|
// required: offset + out_width <= in_width
|
||||||
dict<Key, int> keys_;
|
slice,
|
||||||
dict<int, SparseAttr> sparse_attrs;
|
// zero_extend(a: unsigned bit[in_width], out_width: int): unsigned bit[out_width] = a (zero extended)
|
||||||
|
// required: out_width > in_width
|
||||||
public:
|
zero_extend,
|
||||||
template<typename Graph>
|
// sign_extend(a: signed bit[in_width], out_width: int): signed bit[out_width] = a (sign extended)
|
||||||
struct BaseRef
|
// required: out_width > in_width
|
||||||
{
|
sign_extend,
|
||||||
protected:
|
// concat(a: bit[N], b: bit[M]): bit[N+M] = {b, a} (verilog syntax)
|
||||||
friend struct ComputeGraph;
|
// concatenates two bitvectors, with a in the least significant position and b in the more significant position
|
||||||
Graph *graph_;
|
concat,
|
||||||
int index_;
|
// add(a: bit[N], b: bit[N]): bit[N] = a + b
|
||||||
BaseRef(Graph *graph, int index) : graph_(graph), index_(index) {
|
add,
|
||||||
log_assert(index_ >= 0);
|
// sub(a: bit[N], b: bit[N]): bit[N] = a - b
|
||||||
check();
|
sub,
|
||||||
}
|
// mul(a: bit[N], b: bit[N]): bit[N] = a * b
|
||||||
|
mul,
|
||||||
void check() const { log_assert(index_ < graph_->size()); }
|
// unsigned_div(a: unsigned bit[N], b: unsigned bit[N]): bit[N] = a / b
|
||||||
|
unsigned_div,
|
||||||
Node const &deref() const { check(); return graph_->nodes[index_]; }
|
// unsigned_mod(a: signed bit[N], b: signed bit[N]): bit[N] = a % b
|
||||||
|
unsigned_mod,
|
||||||
public:
|
// bitwise_and(a: bit[N], b: bit[N]): bit[N] = a & b
|
||||||
ComputeGraph const &graph() const { return graph_; }
|
bitwise_and,
|
||||||
int index() const { return index_; }
|
// bitwise_or(a: bit[N], b: bit[N]): bit[N] = a | b
|
||||||
|
bitwise_or,
|
||||||
int size() const { return deref().arg_count; }
|
// bitwise_xor(a: bit[N], b: bit[N]): bit[N] = a ^ b
|
||||||
|
bitwise_xor,
|
||||||
BaseRef arg(int n) const
|
// bitwise_not(a: bit[N]): bit[N] = ~a
|
||||||
{
|
bitwise_not,
|
||||||
Node const &node = deref();
|
// reduce_and(a: bit[N]): bit[1] = &a
|
||||||
log_assert(n >= 0 && n < node.arg_count);
|
reduce_and,
|
||||||
return BaseRef(graph_, graph_->args[node.arg_offset + n]);
|
// reduce_or(a: bit[N]): bit[1] = |a
|
||||||
}
|
reduce_or,
|
||||||
|
// reduce_xor(a: bit[N]): bit[1] = ^a
|
||||||
std::vector<int>::const_iterator arg_indices_cbegin() const
|
reduce_xor,
|
||||||
{
|
// unary_minus(a: bit[N]): bit[N] = -a
|
||||||
Node const &node = deref();
|
unary_minus,
|
||||||
return graph_->args.cbegin() + node.arg_offset;
|
// equal(a: bit[N], b: bit[N]): bit[1] = (a == b)
|
||||||
}
|
equal,
|
||||||
|
// not_equal(a: bit[N], b: bit[N]): bit[1] = (a != b)
|
||||||
std::vector<int>::const_iterator arg_indices_cend() const
|
not_equal,
|
||||||
{
|
// signed_greater_than(a: signed bit[N], b: signed bit[N]): bit[1] = (a > b)
|
||||||
Node const &node = deref();
|
signed_greater_than,
|
||||||
return graph_->args.cbegin() + node.arg_offset + node.arg_count;
|
// signed_greater_equal(a: signed bit[N], b: signed bit[N]): bit[1] = (a >= b)
|
||||||
}
|
signed_greater_equal,
|
||||||
|
// unsigned_greater_than(a: unsigned bit[N], b: unsigned bit[N]): bit[1] = (a > b)
|
||||||
Fn const &function() const { return graph_->functions[deref().fn_index]; }
|
unsigned_greater_than,
|
||||||
Attr const &attr() const { return deref().attr; }
|
// unsigned_greater_equal(a: unsigned bit[N], b: unsigned bit[N]): bit[1] = (a >= b)
|
||||||
|
unsigned_greater_equal,
|
||||||
bool has_sparse_attr() const { return graph_->sparse_attrs.count(index_); }
|
// logical_shift_left(a: bit[N], b: unsigned bit[M]): bit[N] = a << b
|
||||||
|
// required: M == clog2(N)
|
||||||
SparseAttr const &sparse_attr() const
|
logical_shift_left,
|
||||||
{
|
// logical_shift_right(a: unsigned bit[N], b: unsigned bit[M]): unsigned bit[N] = a >> b
|
||||||
auto found = graph_->sparse_attrs.find(index_);
|
// required: M == clog2(N)
|
||||||
log_assert(found != graph_->sparse_attrs.end());
|
logical_shift_right,
|
||||||
return found->second;
|
// arithmetic_shift_right(a: signed bit[N], b: unsigned bit[M]): signed bit[N] = a >> b
|
||||||
}
|
// required: M == clog2(N)
|
||||||
};
|
arithmetic_shift_right,
|
||||||
|
// mux(a: bit[N], b: bit[N], s: bit[1]): bit[N] = s ? b : a
|
||||||
using ConstRef = BaseRef<ComputeGraph const>;
|
mux,
|
||||||
|
// constant(a: Const[N]): bit[N] = a
|
||||||
struct Ref : public BaseRef<ComputeGraph>
|
constant,
|
||||||
{
|
// input(a: IdString): any
|
||||||
private:
|
// returns the current value of the input with the specified name
|
||||||
friend struct ComputeGraph;
|
input,
|
||||||
Ref(ComputeGraph *graph, int index) : BaseRef<ComputeGraph>(graph, index) {}
|
// state(a: IdString): any
|
||||||
Node &deref() const { this->check(); return this->graph_->nodes[this->index_]; }
|
// returns the current value of the state variable with the specified name
|
||||||
|
state,
|
||||||
public:
|
// memory_read(memory: memory[addr_width, data_width], addr: bit[addr_width]): bit[data_width] = memory[addr]
|
||||||
Ref(BaseRef<ComputeGraph> ref) : Ref(ref.graph_, ref.index_) {}
|
memory_read,
|
||||||
|
// memory_write(memory: memory[addr_width, data_width], addr: bit[addr_width], data: bit[data_width]): memory[addr_width, data_width]
|
||||||
void set_function(Fn const &function) const
|
// returns a copy of `memory` but with the value at `addr` changed to `data`
|
||||||
{
|
memory_write
|
||||||
deref().fn_index = this->graph_->functions(function);
|
};
|
||||||
}
|
// returns the name of a Fn value, as a string literal
|
||||||
|
const char *fn_to_string(Fn);
|
||||||
Attr &attr() const { return deref().attr; }
|
// Sort represents the sort or type of a node
|
||||||
|
// currently the only two types are signal/bit and memory
|
||||||
void append_arg(ConstRef arg) const
|
class Sort {
|
||||||
{
|
std::variant<int, std::pair<int, int>> _v;
|
||||||
log_assert(arg.graph_ == this->graph_);
|
public:
|
||||||
append_arg(arg.index());
|
explicit Sort(int width) : _v(width) { }
|
||||||
}
|
Sort(int addr_width, int data_width) : _v(std::make_pair(addr_width, data_width)) { }
|
||||||
|
bool is_signal() const { return _v.index() == 0; }
|
||||||
void append_arg(int arg) const
|
bool is_memory() const { return _v.index() == 1; }
|
||||||
{
|
// returns the width of a bitvector type, errors out for other types
|
||||||
log_assert(arg >= 0 && arg < this->graph_->size());
|
int width() const { return std::get<0>(_v); }
|
||||||
Node &node = deref();
|
// returns the address width of a bitvector type, errors out for other types
|
||||||
if (node.arg_offset + node.arg_count != GetSize(this->graph_->args))
|
int addr_width() const { return std::get<1>(_v).first; }
|
||||||
move_args(node);
|
// returns the data width of a bitvector type, errors out for other types
|
||||||
this->graph_->args.push_back(arg);
|
int data_width() const { return std::get<1>(_v).second; }
|
||||||
node.arg_count++;
|
bool operator==(Sort const& other) const { return _v == other._v; }
|
||||||
}
|
unsigned int hash() const { return mkhash(_v); }
|
||||||
|
};
|
||||||
operator ConstRef() const
|
class Factory;
|
||||||
{
|
class Node;
|
||||||
return ConstRef(this->graph_, this->index_);
|
class IR {
|
||||||
}
|
friend class Factory;
|
||||||
|
friend class Node;
|
||||||
SparseAttr &sparse_attr() const
|
// one NodeData is stored per Node, containing the function and non-node arguments
|
||||||
{
|
// note that NodeData is deduplicated by ComputeGraph
|
||||||
return this->graph_->sparse_attrs[this->index_];
|
class NodeData {
|
||||||
}
|
Fn _fn;
|
||||||
|
std::variant<
|
||||||
void clear_sparse_attr() const
|
std::monostate,
|
||||||
{
|
RTLIL::Const,
|
||||||
this->graph_->sparse_attrs.erase(this->index_);
|
IdString,
|
||||||
}
|
int
|
||||||
|
> _extra;
|
||||||
void assign_key(Key const &key) const
|
public:
|
||||||
{
|
NodeData() : _fn(Fn::invalid) {}
|
||||||
this->graph_->keys_.emplace(key, this->index_);
|
NodeData(Fn fn) : _fn(fn) {}
|
||||||
}
|
template<class T> NodeData(Fn fn, T &&extra) : _fn(fn), _extra(std::forward<T>(extra)) {}
|
||||||
|
Fn fn() const { return _fn; }
|
||||||
private:
|
const RTLIL::Const &as_const() const { return std::get<RTLIL::Const>(_extra); }
|
||||||
void move_args(Node &node) const
|
IdString as_idstring() const { return std::get<IdString>(_extra); }
|
||||||
{
|
int as_int() const { return std::get<int>(_extra); }
|
||||||
auto &args = this->graph_->args;
|
int hash() const {
|
||||||
int old_offset = node.arg_offset;
|
return mkhash((unsigned int) _fn, mkhash(_extra));
|
||||||
node.arg_offset = GetSize(args);
|
}
|
||||||
for (int i = 0; i != node.arg_count; ++i)
|
bool operator==(NodeData const &other) const {
|
||||||
args.push_back(args[old_offset + i]);
|
return _fn == other._fn && _extra == other._extra;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
};
|
// Attr contains all the information about a note that should not be deduplicated
|
||||||
|
struct Attr {
|
||||||
bool has_key(Key const &key) const
|
Sort sort;
|
||||||
{
|
};
|
||||||
return keys_.count(key);
|
// our specialised version of ComputeGraph
|
||||||
}
|
// the sparse_attr IdString stores a naming suggestion, retrieved with name()
|
||||||
|
// the key is currently used to identify the nodes that represent output and next state values
|
||||||
dict<Key, int> const &keys() const
|
// the bool is true for next state values
|
||||||
{
|
using Graph = ComputeGraph<NodeData, Attr, IdString, std::pair<IdString, bool>>;
|
||||||
return keys_;
|
Graph _graph;
|
||||||
}
|
dict<IdString, Sort> _input_sorts;
|
||||||
|
dict<IdString, Sort> _output_sorts;
|
||||||
ConstRef operator()(Key const &key) const
|
dict<IdString, Sort> _state_sorts;
|
||||||
{
|
dict<IdString, RTLIL::Const> _initial_state_signal;
|
||||||
auto it = keys_.find(key);
|
dict<IdString, MemContents> _initial_state_memory;
|
||||||
log_assert(it != keys_.end());
|
public:
|
||||||
return (*this)[it->second];
|
static IR from_module(Module *module);
|
||||||
}
|
Factory factory();
|
||||||
|
int size() const { return _graph.size(); }
|
||||||
Ref operator()(Key const &key)
|
Node operator[](int i);
|
||||||
{
|
void topological_sort();
|
||||||
auto it = keys_.find(key);
|
void forward_buf();
|
||||||
log_assert(it != keys_.end());
|
dict<IdString, Sort> inputs() const { return _input_sorts; }
|
||||||
return (*this)[it->second];
|
dict<IdString, Sort> outputs() const { return _output_sorts; }
|
||||||
}
|
dict<IdString, Sort> state() const { return _state_sorts; }
|
||||||
|
RTLIL::Const const &get_initial_state_signal(IdString name) { return _initial_state_signal.at(name); }
|
||||||
int size() const { return GetSize(nodes); }
|
MemContents const &get_initial_state_memory(IdString name) { return _initial_state_memory.at(name); }
|
||||||
|
Node get_output_node(IdString name);
|
||||||
ConstRef operator[](int index) const { return ConstRef(this, index); }
|
Node get_state_next_node(IdString name);
|
||||||
Ref operator[](int index) { return Ref(this, index); }
|
class iterator {
|
||||||
|
friend class IR;
|
||||||
Ref add(Fn const &function, Attr &&attr)
|
IR *_ir;
|
||||||
{
|
int _index;
|
||||||
int index = GetSize(nodes);
|
iterator(IR *ir, int index) : _ir(ir), _index(index) {}
|
||||||
int fn_index = functions(function);
|
public:
|
||||||
nodes.emplace_back(fn_index, std::move(attr), GetSize(args));
|
using iterator_category = std::input_iterator_tag;
|
||||||
return Ref(this, index);
|
using value_type = Node;
|
||||||
}
|
using pointer = arrow_proxy<Node>;
|
||||||
|
using reference = Node;
|
||||||
Ref add(Fn const &function, Attr const &attr)
|
using difference_type = ptrdiff_t;
|
||||||
{
|
Node operator*();
|
||||||
int index = GetSize(nodes);
|
iterator &operator++() { _index++; return *this; }
|
||||||
int fn_index = functions(function);
|
bool operator!=(iterator const &other) const { return _ir != other._ir || _index != other._index; }
|
||||||
nodes.emplace_back(fn_index, attr, GetSize(args));
|
bool operator==(iterator const &other) const { return !(*this != other); }
|
||||||
return Ref(this, index);
|
pointer operator->();
|
||||||
}
|
// TODO: implement operator-> using the arrow_proxy class currently in mem.h
|
||||||
|
};
|
||||||
template<typename T>
|
iterator begin() { return iterator(this, 0); }
|
||||||
Ref add(Fn const &function, Attr const &attr, T &&args)
|
iterator end() { return iterator(this, _graph.size()); }
|
||||||
{
|
};
|
||||||
Ref added = add(function, attr);
|
// Node is an immutable reference to a FunctionalIR node
|
||||||
for (auto arg : args)
|
class Node {
|
||||||
added.append_arg(arg);
|
friend class Factory;
|
||||||
return added;
|
friend class IR;
|
||||||
}
|
IR::Graph::ConstRef _ref;
|
||||||
|
explicit Node(IR::Graph::ConstRef ref) : _ref(ref) { }
|
||||||
template<typename T>
|
explicit operator IR::Graph::ConstRef() { return _ref; }
|
||||||
Ref add(Fn const &function, Attr &&attr, T &&args)
|
public:
|
||||||
{
|
// the node's index. may change if nodes are added or removed
|
||||||
Ref added = add(function, std::move(attr));
|
int id() const { return _ref.index(); }
|
||||||
for (auto arg : args)
|
// a name suggestion for the node, which need not be unique
|
||||||
added.append_arg(arg);
|
IdString name() const {
|
||||||
return added;
|
if(_ref.has_sparse_attr())
|
||||||
}
|
return _ref.sparse_attr();
|
||||||
|
else
|
||||||
Ref add(Fn const &function, Attr const &attr, std::initializer_list<Ref> args)
|
return std::string("\\n") + std::to_string(id());
|
||||||
{
|
}
|
||||||
Ref added = add(function, attr);
|
Fn fn() const { return _ref.function().fn(); }
|
||||||
for (auto arg : args)
|
Sort sort() const { return _ref.attr().sort; }
|
||||||
added.append_arg(arg);
|
// returns the width of a bitvector node, errors out for other nodes
|
||||||
return added;
|
int width() const { return sort().width(); }
|
||||||
}
|
size_t arg_count() const { return _ref.size(); }
|
||||||
|
Node arg(int n) const { return Node(_ref.arg(n)); }
|
||||||
Ref add(Fn const &function, Attr &&attr, std::initializer_list<Ref> args)
|
// visit calls the appropriate visitor method depending on the type of the node
|
||||||
{
|
template<class Visitor> auto visit(Visitor v) const
|
||||||
Ref added = add(function, std::move(attr));
|
{
|
||||||
for (auto arg : args)
|
// currently templated but could be switched to AbstractVisitor &
|
||||||
added.append_arg(arg);
|
switch(_ref.function().fn()) {
|
||||||
return added;
|
case Fn::invalid: log_error("invalid node in visit"); break;
|
||||||
}
|
case Fn::buf: return v.buf(*this, arg(0)); break;
|
||||||
|
case Fn::slice: return v.slice(*this, arg(0), _ref.function().as_int(), sort().width()); break;
|
||||||
template<typename T>
|
case Fn::zero_extend: return v.zero_extend(*this, arg(0), width()); break;
|
||||||
Ref add(Fn const &function, Attr const &attr, T begin, T end)
|
case Fn::sign_extend: return v.sign_extend(*this, arg(0), width()); break;
|
||||||
{
|
case Fn::concat: return v.concat(*this, arg(0), arg(1)); break;
|
||||||
Ref added = add(function, attr);
|
case Fn::add: return v.add(*this, arg(0), arg(1)); break;
|
||||||
for (; begin != end; ++begin)
|
case Fn::sub: return v.sub(*this, arg(0), arg(1)); break;
|
||||||
added.append_arg(*begin);
|
case Fn::mul: return v.mul(*this, arg(0), arg(1)); break;
|
||||||
return added;
|
case Fn::unsigned_div: return v.unsigned_div(*this, arg(0), arg(1)); break;
|
||||||
}
|
case Fn::unsigned_mod: return v.unsigned_mod(*this, arg(0), arg(1)); break;
|
||||||
|
case Fn::bitwise_and: return v.bitwise_and(*this, arg(0), arg(1)); break;
|
||||||
void compact_args()
|
case Fn::bitwise_or: return v.bitwise_or(*this, arg(0), arg(1)); break;
|
||||||
{
|
case Fn::bitwise_xor: return v.bitwise_xor(*this, arg(0), arg(1)); break;
|
||||||
std::vector<int> new_args;
|
case Fn::bitwise_not: return v.bitwise_not(*this, arg(0)); break;
|
||||||
for (auto &node : nodes)
|
case Fn::unary_minus: return v.unary_minus(*this, arg(0)); break;
|
||||||
{
|
case Fn::reduce_and: return v.reduce_and(*this, arg(0)); break;
|
||||||
int new_offset = GetSize(new_args);
|
case Fn::reduce_or: return v.reduce_or(*this, arg(0)); break;
|
||||||
for (int i = 0; i < node.arg_count; i++)
|
case Fn::reduce_xor: return v.reduce_xor(*this, arg(0)); break;
|
||||||
new_args.push_back(args[node.arg_offset + i]);
|
case Fn::equal: return v.equal(*this, arg(0), arg(1)); break;
|
||||||
node.arg_offset = new_offset;
|
case Fn::not_equal: return v.not_equal(*this, arg(0), arg(1)); break;
|
||||||
}
|
case Fn::signed_greater_than: return v.signed_greater_than(*this, arg(0), arg(1)); break;
|
||||||
std::swap(args, new_args);
|
case Fn::signed_greater_equal: return v.signed_greater_equal(*this, arg(0), arg(1)); break;
|
||||||
}
|
case Fn::unsigned_greater_than: return v.unsigned_greater_than(*this, arg(0), arg(1)); break;
|
||||||
|
case Fn::unsigned_greater_equal: return v.unsigned_greater_equal(*this, arg(0), arg(1)); break;
|
||||||
void permute(std::vector<int> const &perm)
|
case Fn::logical_shift_left: return v.logical_shift_left(*this, arg(0), arg(1)); break;
|
||||||
{
|
case Fn::logical_shift_right: return v.logical_shift_right(*this, arg(0), arg(1)); break;
|
||||||
log_assert(perm.size() <= nodes.size());
|
case Fn::arithmetic_shift_right: return v.arithmetic_shift_right(*this, arg(0), arg(1)); break;
|
||||||
std::vector<int> inv_perm;
|
case Fn::mux: return v.mux(*this, arg(0), arg(1), arg(2)); break;
|
||||||
inv_perm.resize(nodes.size(), -1);
|
case Fn::constant: return v.constant(*this, _ref.function().as_const()); break;
|
||||||
for (int i = 0; i < GetSize(perm); ++i)
|
case Fn::input: return v.input(*this, _ref.function().as_idstring()); break;
|
||||||
{
|
case Fn::state: return v.state(*this, _ref.function().as_idstring()); break;
|
||||||
int j = perm[i];
|
case Fn::memory_read: return v.memory_read(*this, arg(0), arg(1)); break;
|
||||||
log_assert(j >= 0 && j < GetSize(nodes));
|
case Fn::memory_write: return v.memory_write(*this, arg(0), arg(1), arg(2)); break;
|
||||||
log_assert(inv_perm[j] == -1);
|
}
|
||||||
inv_perm[j] = i;
|
}
|
||||||
}
|
std::string to_string();
|
||||||
permute(perm, inv_perm);
|
std::string to_string(std::function<std::string(Node)>);
|
||||||
}
|
};
|
||||||
|
inline Node IR::operator[](int i) { return Node(_graph[i]); }
|
||||||
void permute(std::vector<int> const &perm, std::vector<int> const &inv_perm)
|
inline Node IR::get_output_node(IdString name) { return Node(_graph({name, false})); }
|
||||||
{
|
inline Node IR::get_state_next_node(IdString name) { return Node(_graph({name, true})); }
|
||||||
log_assert(inv_perm.size() == nodes.size());
|
inline Node IR::iterator::operator*() { return Node(_ir->_graph[_index]); }
|
||||||
std::vector<Node> new_nodes;
|
inline arrow_proxy<Node> IR::iterator::operator->() { return arrow_proxy<Node>(**this); }
|
||||||
new_nodes.reserve(perm.size());
|
// AbstractVisitor provides an abstract base class for visitors
|
||||||
dict<int, SparseAttr> new_sparse_attrs;
|
template<class T> struct AbstractVisitor {
|
||||||
for (int i : perm)
|
virtual T buf(Node self, Node n) = 0;
|
||||||
{
|
virtual T slice(Node self, Node a, int offset, int out_width) = 0;
|
||||||
int j = GetSize(new_nodes);
|
virtual T zero_extend(Node self, Node a, int out_width) = 0;
|
||||||
new_nodes.emplace_back(std::move(nodes[i]));
|
virtual T sign_extend(Node self, Node a, int out_width) = 0;
|
||||||
auto found = sparse_attrs.find(i);
|
virtual T concat(Node self, Node a, Node b) = 0;
|
||||||
if (found != sparse_attrs.end())
|
virtual T add(Node self, Node a, Node b) = 0;
|
||||||
new_sparse_attrs.emplace(j, std::move(found->second));
|
virtual T sub(Node self, Node a, Node b) = 0;
|
||||||
}
|
virtual T mul(Node self, Node a, Node b) = 0;
|
||||||
|
virtual T unsigned_div(Node self, Node a, Node b) = 0;
|
||||||
std::swap(nodes, new_nodes);
|
virtual T unsigned_mod(Node self, Node a, Node b) = 0;
|
||||||
std::swap(sparse_attrs, new_sparse_attrs);
|
virtual T bitwise_and(Node self, Node a, Node b) = 0;
|
||||||
|
virtual T bitwise_or(Node self, Node a, Node b) = 0;
|
||||||
compact_args();
|
virtual T bitwise_xor(Node self, Node a, Node b) = 0;
|
||||||
for (int &arg : args)
|
virtual T bitwise_not(Node self, Node a) = 0;
|
||||||
{
|
virtual T unary_minus(Node self, Node a) = 0;
|
||||||
log_assert(arg < GetSize(inv_perm));
|
virtual T reduce_and(Node self, Node a) = 0;
|
||||||
log_assert(inv_perm[arg] >= 0);
|
virtual T reduce_or(Node self, Node a) = 0;
|
||||||
arg = inv_perm[arg];
|
virtual T reduce_xor(Node self, Node a) = 0;
|
||||||
}
|
virtual T equal(Node self, Node a, Node b) = 0;
|
||||||
|
virtual T not_equal(Node self, Node a, Node b) = 0;
|
||||||
for (auto &key : keys_)
|
virtual T signed_greater_than(Node self, Node a, Node b) = 0;
|
||||||
{
|
virtual T signed_greater_equal(Node self, Node a, Node b) = 0;
|
||||||
log_assert(key.second < GetSize(inv_perm));
|
virtual T unsigned_greater_than(Node self, Node a, Node b) = 0;
|
||||||
log_assert(inv_perm[key.second] >= 0);
|
virtual T unsigned_greater_equal(Node self, Node a, Node b) = 0;
|
||||||
key.second = inv_perm[key.second];
|
virtual T logical_shift_left(Node self, Node a, Node b) = 0;
|
||||||
}
|
virtual T logical_shift_right(Node self, Node a, Node b) = 0;
|
||||||
}
|
virtual T arithmetic_shift_right(Node self, Node a, Node b) = 0;
|
||||||
|
virtual T mux(Node self, Node a, Node b, Node s) = 0;
|
||||||
struct SccAdaptor
|
virtual T constant(Node self, RTLIL::Const const & value) = 0;
|
||||||
{
|
virtual T input(Node self, IdString name) = 0;
|
||||||
private:
|
virtual T state(Node self, IdString name) = 0;
|
||||||
ComputeGraph const &graph_;
|
virtual T memory_read(Node self, Node mem, Node addr) = 0;
|
||||||
std::vector<int> indices_;
|
virtual T memory_write(Node self, Node mem, Node addr, Node data) = 0;
|
||||||
public:
|
};
|
||||||
SccAdaptor(ComputeGraph const &graph) : graph_(graph)
|
// DefaultVisitor provides defaults for all visitor methods which just calls default_handler
|
||||||
{
|
template<class T> struct DefaultVisitor : public AbstractVisitor<T> {
|
||||||
indices_.resize(graph.size(), -1);
|
virtual T default_handler(Node self) = 0;
|
||||||
}
|
T buf(Node self, Node) override { return default_handler(self); }
|
||||||
|
T slice(Node self, Node, int, int) override { return default_handler(self); }
|
||||||
|
T zero_extend(Node self, Node, int) override { return default_handler(self); }
|
||||||
typedef int node_type;
|
T sign_extend(Node self, Node, int) override { return default_handler(self); }
|
||||||
|
T concat(Node self, Node, Node) override { return default_handler(self); }
|
||||||
struct node_enumerator {
|
T add(Node self, Node, Node) override { return default_handler(self); }
|
||||||
private:
|
T sub(Node self, Node, Node) override { return default_handler(self); }
|
||||||
friend struct SccAdaptor;
|
T mul(Node self, Node, Node) override { return default_handler(self); }
|
||||||
int current, end;
|
T unsigned_div(Node self, Node, Node) override { return default_handler(self); }
|
||||||
node_enumerator(int current, int end) : current(current), end(end) {}
|
T unsigned_mod(Node self, Node, Node) override { return default_handler(self); }
|
||||||
|
T bitwise_and(Node self, Node, Node) override { return default_handler(self); }
|
||||||
public:
|
T bitwise_or(Node self, Node, Node) override { return default_handler(self); }
|
||||||
|
T bitwise_xor(Node self, Node, Node) override { return default_handler(self); }
|
||||||
bool finished() const { return current == end; }
|
T bitwise_not(Node self, Node) override { return default_handler(self); }
|
||||||
node_type next() {
|
T unary_minus(Node self, Node) override { return default_handler(self); }
|
||||||
log_assert(!finished());
|
T reduce_and(Node self, Node) override { return default_handler(self); }
|
||||||
node_type result = current;
|
T reduce_or(Node self, Node) override { return default_handler(self); }
|
||||||
++current;
|
T reduce_xor(Node self, Node) override { return default_handler(self); }
|
||||||
return result;
|
T equal(Node self, Node, Node) override { return default_handler(self); }
|
||||||
}
|
T not_equal(Node self, Node, Node) override { return default_handler(self); }
|
||||||
};
|
T signed_greater_than(Node self, Node, Node) override { return default_handler(self); }
|
||||||
|
T signed_greater_equal(Node self, Node, Node) override { return default_handler(self); }
|
||||||
node_enumerator enumerate_nodes() {
|
T unsigned_greater_than(Node self, Node, Node) override { return default_handler(self); }
|
||||||
return node_enumerator(0, GetSize(indices_));
|
T unsigned_greater_equal(Node self, Node, Node) override { return default_handler(self); }
|
||||||
}
|
T logical_shift_left(Node self, Node, Node) override { return default_handler(self); }
|
||||||
|
T logical_shift_right(Node self, Node, Node) override { return default_handler(self); }
|
||||||
|
T arithmetic_shift_right(Node self, Node, Node) override { return default_handler(self); }
|
||||||
struct successor_enumerator {
|
T mux(Node self, Node, Node, Node) override { return default_handler(self); }
|
||||||
private:
|
T constant(Node self, RTLIL::Const const &) override { return default_handler(self); }
|
||||||
friend struct SccAdaptor;
|
T input(Node self, IdString) override { return default_handler(self); }
|
||||||
std::vector<int>::const_iterator current, end;
|
T state(Node self, IdString) override { return default_handler(self); }
|
||||||
successor_enumerator(std::vector<int>::const_iterator current, std::vector<int>::const_iterator end) :
|
T memory_read(Node self, Node, Node) override { return default_handler(self); }
|
||||||
current(current), end(end) {}
|
T memory_write(Node self, Node, Node, Node) override { return default_handler(self); }
|
||||||
|
};
|
||||||
public:
|
// a factory is used to modify a FunctionalIR. it creates new nodes and allows for some modification of existing nodes.
|
||||||
bool finished() const { return current == end; }
|
class Factory {
|
||||||
node_type next() {
|
friend class IR;
|
||||||
log_assert(!finished());
|
IR &_ir;
|
||||||
node_type result = *current;
|
explicit Factory(IR &ir) : _ir(ir) {}
|
||||||
++current;
|
Node add(IR::NodeData &&fn, Sort &&sort, std::initializer_list<Node> args) {
|
||||||
return result;
|
log_assert(!sort.is_signal() || sort.width() > 0);
|
||||||
}
|
log_assert(!sort.is_memory() || sort.addr_width() > 0 && sort.data_width() > 0);
|
||||||
};
|
IR::Graph::Ref ref = _ir._graph.add(std::move(fn), {std::move(sort)});
|
||||||
|
for (auto arg : args)
|
||||||
successor_enumerator enumerate_successors(int index) const {
|
ref.append_arg(IR::Graph::ConstRef(arg));
|
||||||
auto const &ref = graph_[index];
|
return Node(ref);
|
||||||
return successor_enumerator(ref.arg_indices_cbegin(), ref.arg_indices_cend());
|
}
|
||||||
}
|
IR::Graph::Ref mutate(Node n) {
|
||||||
|
return _ir._graph[n._ref.index()];
|
||||||
int &dfs_index(node_type const &node) { return indices_[node]; }
|
}
|
||||||
|
void check_basic_binary(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && a.sort() == b.sort()); }
|
||||||
std::vector<int> const &dfs_indices() { return indices_; }
|
void check_shift(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && b.sort().is_signal() && b.width() == ceil_log2(a.width())); }
|
||||||
};
|
void check_unary(Node const &a) { log_assert(a.sort().is_signal()); }
|
||||||
|
public:
|
||||||
};
|
Node slice(Node a, int offset, int out_width) {
|
||||||
|
log_assert(a.sort().is_signal() && offset + out_width <= a.sort().width());
|
||||||
|
if(offset == 0 && out_width == a.width())
|
||||||
|
return a;
|
||||||
|
return add(IR::NodeData(Fn::slice, offset), Sort(out_width), {a});
|
||||||
|
}
|
||||||
|
// extend will either extend or truncate the provided value to reach the desired width
|
||||||
|
Node extend(Node a, int out_width, bool is_signed) {
|
||||||
|
int in_width = a.sort().width();
|
||||||
|
log_assert(a.sort().is_signal());
|
||||||
|
if(in_width == out_width)
|
||||||
|
return a;
|
||||||
|
if(in_width > out_width)
|
||||||
|
return slice(a, 0, out_width);
|
||||||
|
if(is_signed)
|
||||||
|
return add(Fn::sign_extend, Sort(out_width), {a});
|
||||||
|
else
|
||||||
|
return add(Fn::zero_extend, Sort(out_width), {a});
|
||||||
|
}
|
||||||
|
Node concat(Node a, Node b) {
|
||||||
|
log_assert(a.sort().is_signal() && b.sort().is_signal());
|
||||||
|
return add(Fn::concat, Sort(a.sort().width() + b.sort().width()), {a, b});
|
||||||
|
}
|
||||||
|
Node add(Node a, Node b) { check_basic_binary(a, b); return add(Fn::add, a.sort(), {a, b}); }
|
||||||
|
Node sub(Node a, Node b) { check_basic_binary(a, b); return add(Fn::sub, a.sort(), {a, b}); }
|
||||||
|
Node mul(Node a, Node b) { check_basic_binary(a, b); return add(Fn::mul, a.sort(), {a, b}); }
|
||||||
|
Node unsigned_div(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_div, a.sort(), {a, b}); }
|
||||||
|
Node unsigned_mod(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_mod, a.sort(), {a, b}); }
|
||||||
|
Node bitwise_and(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_and, a.sort(), {a, b}); }
|
||||||
|
Node bitwise_or(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_or, a.sort(), {a, b}); }
|
||||||
|
Node bitwise_xor(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_xor, a.sort(), {a, b}); }
|
||||||
|
Node bitwise_not(Node a) { check_unary(a); return add(Fn::bitwise_not, a.sort(), {a}); }
|
||||||
|
Node unary_minus(Node a) { check_unary(a); return add(Fn::unary_minus, a.sort(), {a}); }
|
||||||
|
Node reduce_and(Node a) {
|
||||||
|
check_unary(a);
|
||||||
|
if(a.width() == 1)
|
||||||
|
return a;
|
||||||
|
return add(Fn::reduce_and, Sort(1), {a});
|
||||||
|
}
|
||||||
|
Node reduce_or(Node a) {
|
||||||
|
check_unary(a);
|
||||||
|
if(a.width() == 1)
|
||||||
|
return a;
|
||||||
|
return add(Fn::reduce_or, Sort(1), {a});
|
||||||
|
}
|
||||||
|
Node reduce_xor(Node a) {
|
||||||
|
check_unary(a);
|
||||||
|
if(a.width() == 1)
|
||||||
|
return a;
|
||||||
|
return add(Fn::reduce_xor, Sort(1), {a});
|
||||||
|
}
|
||||||
|
Node equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::equal, Sort(1), {a, b}); }
|
||||||
|
Node not_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::not_equal, Sort(1), {a, b}); }
|
||||||
|
Node signed_greater_than(Node a, Node b) { check_basic_binary(a, b); return add(Fn::signed_greater_than, Sort(1), {a, b}); }
|
||||||
|
Node signed_greater_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::signed_greater_equal, Sort(1), {a, b}); }
|
||||||
|
Node unsigned_greater_than(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_greater_than, Sort(1), {a, b}); }
|
||||||
|
Node unsigned_greater_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_greater_equal, Sort(1), {a, b}); }
|
||||||
|
Node logical_shift_left(Node a, Node b) { check_shift(a, b); return add(Fn::logical_shift_left, a.sort(), {a, b}); }
|
||||||
|
Node logical_shift_right(Node a, Node b) { check_shift(a, b); return add(Fn::logical_shift_right, a.sort(), {a, b}); }
|
||||||
|
Node arithmetic_shift_right(Node a, Node b) { check_shift(a, b); return add(Fn::arithmetic_shift_right, a.sort(), {a, b}); }
|
||||||
|
Node mux(Node a, Node b, Node s) {
|
||||||
|
log_assert(a.sort().is_signal() && a.sort() == b.sort() && s.sort() == Sort(1));
|
||||||
|
return add(Fn::mux, a.sort(), {a, b, s});
|
||||||
|
}
|
||||||
|
Node memory_read(Node mem, Node addr) {
|
||||||
|
log_assert(mem.sort().is_memory() && addr.sort().is_signal() && mem.sort().addr_width() == addr.sort().width());
|
||||||
|
return add(Fn::memory_read, Sort(mem.sort().data_width()), {mem, addr});
|
||||||
|
}
|
||||||
|
Node memory_write(Node mem, Node addr, Node data) {
|
||||||
|
log_assert(mem.sort().is_memory() && addr.sort().is_signal() && data.sort().is_signal() &&
|
||||||
|
mem.sort().addr_width() == addr.sort().width() && mem.sort().data_width() == data.sort().width());
|
||||||
|
return add(Fn::memory_write, mem.sort(), {mem, addr, data});
|
||||||
|
}
|
||||||
|
Node constant(RTLIL::Const value) {
|
||||||
|
return add(IR::NodeData(Fn::constant, std::move(value)), Sort(value.size()), {});
|
||||||
|
}
|
||||||
|
Node create_pending(int width) {
|
||||||
|
return add(Fn::buf, Sort(width), {});
|
||||||
|
}
|
||||||
|
void update_pending(Node node, Node value) {
|
||||||
|
log_assert(node._ref.function() == Fn::buf && node._ref.size() == 0);
|
||||||
|
log_assert(node.sort() == value.sort());
|
||||||
|
mutate(node).append_arg(value._ref);
|
||||||
|
}
|
||||||
|
void add_input(IdString name, int width) {
|
||||||
|
auto [it, inserted] = _ir._input_sorts.emplace(name, Sort(width));
|
||||||
|
if (!inserted) log_error("input `%s` was re-defined", name.c_str());
|
||||||
|
}
|
||||||
|
void add_output(IdString name, int width) {
|
||||||
|
auto [it, inserted] = _ir._output_sorts.emplace(name, Sort(width));
|
||||||
|
if (!inserted) log_error("output `%s` was re-defined", name.c_str());
|
||||||
|
}
|
||||||
|
void add_state(IdString name, Sort sort) {
|
||||||
|
auto [it, inserted] = _ir._state_sorts.emplace(name, sort);
|
||||||
|
if (!inserted) log_error("state `%s` was re-defined", name.c_str());
|
||||||
|
}
|
||||||
|
Node input(IdString name) {
|
||||||
|
return add(IR::NodeData(Fn::input, name), Sort(_ir._input_sorts.at(name)), {});
|
||||||
|
}
|
||||||
|
Node current_state(IdString name) {
|
||||||
|
return add(IR::NodeData(Fn::state, name), Sort(_ir._state_sorts.at(name)), {});
|
||||||
|
}
|
||||||
|
void set_output(IdString output, Node value) {
|
||||||
|
log_assert(_ir._output_sorts.at(output) == value.sort());
|
||||||
|
mutate(value).assign_key({output, false});
|
||||||
|
}
|
||||||
|
void set_initial_state(IdString state, RTLIL::Const value) {
|
||||||
|
Sort &sort = _ir._state_sorts.at(state);
|
||||||
|
value.extu(sort.width());
|
||||||
|
_ir._initial_state_signal.emplace(state, std::move(value));
|
||||||
|
}
|
||||||
|
void set_initial_state(IdString state, MemContents value) {
|
||||||
|
log_assert(Sort(value.addr_width(), value.data_width()) == _ir._state_sorts.at(state));
|
||||||
|
_ir._initial_state_memory.emplace(state, std::move(value));
|
||||||
|
}
|
||||||
|
void set_next_state(IdString state, Node value) {
|
||||||
|
log_assert(_ir._state_sorts.at(state) == value.sort());
|
||||||
|
mutate(value).assign_key({state, true});
|
||||||
|
}
|
||||||
|
void suggest_name(Node node, IdString name) {
|
||||||
|
mutate(node).sparse_attr() = name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
inline Factory IR::factory() { return Factory(*this); }
|
||||||
|
template<class Id> class Scope {
|
||||||
|
protected:
|
||||||
|
char substitution_character = '_';
|
||||||
|
virtual bool is_character_legal(char) = 0;
|
||||||
|
private:
|
||||||
|
pool<std::string> _used_names;
|
||||||
|
dict<Id, std::string> _by_id;
|
||||||
|
public:
|
||||||
|
void reserve(std::string name) {
|
||||||
|
_used_names.insert(std::move(name));
|
||||||
|
}
|
||||||
|
std::string unique_name(IdString suggestion) {
|
||||||
|
std::string str = RTLIL::unescape_id(suggestion);
|
||||||
|
for(size_t i = 0; i < str.size(); i++)
|
||||||
|
if(!is_character_legal(str[i]))
|
||||||
|
str[i] = substitution_character;
|
||||||
|
if(_used_names.count(str) == 0) {
|
||||||
|
_used_names.insert(str);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
for (int idx = 0 ; ; idx++){
|
||||||
|
std::string suffixed = str + "_" + std::to_string(idx);
|
||||||
|
if(_used_names.count(suffixed) == 0) {
|
||||||
|
_used_names.insert(suffixed);
|
||||||
|
return suffixed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::string operator()(Id id, IdString suggestion) {
|
||||||
|
auto it = _by_id.find(id);
|
||||||
|
if(it != _by_id.end())
|
||||||
|
return it->second;
|
||||||
|
std::string str = unique_name(suggestion);
|
||||||
|
_by_id.insert({id, str});
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
class Writer {
|
||||||
|
std::ostream *os;
|
||||||
|
void print_impl(const char *fmt, vector<std::function<void()>>& fns);
|
||||||
|
public:
|
||||||
|
Writer(std::ostream &os) : os(&os) {}
|
||||||
|
template<class T> Writer& operator <<(T&& arg) { *os << std::forward<T>(arg); return *this; }
|
||||||
|
template<typename... Args>
|
||||||
|
void print(const char *fmt, Args&&... args)
|
||||||
|
{
|
||||||
|
vector<std::function<void()>> fns { [&]() { *this << args; }... };
|
||||||
|
print_impl(fmt, fns);
|
||||||
|
}
|
||||||
|
template<typename Fn, typename... Args>
|
||||||
|
void print_with(Fn fn, const char *fmt, Args&&... args)
|
||||||
|
{
|
||||||
|
vector<std::function<void()>> fns { [&]() {
|
||||||
|
if constexpr (std::is_invocable_v<Fn, Args>)
|
||||||
|
*this << fn(args);
|
||||||
|
else
|
||||||
|
*this << args; }...
|
||||||
|
};
|
||||||
|
print_impl(fmt, fns);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
YOSYS_NAMESPACE_END
|
YOSYS_NAMESPACE_END
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,575 +0,0 @@
|
||||||
/*
|
|
||||||
* yosys -- Yosys Open SYnthesis Suite
|
|
||||||
*
|
|
||||||
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef FUNCTIONALIR_H
|
|
||||||
#define FUNCTIONALIR_H
|
|
||||||
|
|
||||||
#include "kernel/yosys.h"
|
|
||||||
#include "kernel/functional.h"
|
|
||||||
#include "kernel/drivertools.h"
|
|
||||||
#include "kernel/mem.h"
|
|
||||||
#include "kernel/topo_scc.h"
|
|
||||||
|
|
||||||
USING_YOSYS_NAMESPACE
|
|
||||||
YOSYS_NAMESPACE_BEGIN
|
|
||||||
|
|
||||||
class FunctionalIR {
|
|
||||||
public:
|
|
||||||
// each function is documented with a short pseudocode declaration or definition
|
|
||||||
// standard C/Verilog operators are used to describe the result
|
|
||||||
//
|
|
||||||
// the types used in this are:
|
|
||||||
// - bit[N]: a bitvector of N bits
|
|
||||||
// bit[N] can be indicated as signed or unsigned. this is not tracked by the functional backend
|
|
||||||
// but is meant to indicate how the value is interpreted
|
|
||||||
// if a bit[N] is marked as neither signed nor unsigned, this means the result should be valid with *either* interpretation
|
|
||||||
// - memory[N, M]: a memory with N address and M data bits
|
|
||||||
// - int: C++ int
|
|
||||||
// - Const[N]: yosys RTLIL::Const (with size() == N)
|
|
||||||
// - IdString: yosys IdString
|
|
||||||
// - any: used in documentation to indicate that the type is unconstrained
|
|
||||||
//
|
|
||||||
// nodes in the functional backend are either of type bit[N] or memory[N,M] (for some N, M: int)
|
|
||||||
// additionally, they can carry a constant of type int, Const[N] or IdString
|
|
||||||
// each node has a 'sort' field that stores the type of the node
|
|
||||||
// slice, zero_extend, sign_extend use the type field to store out_width
|
|
||||||
enum class Fn {
|
|
||||||
// invalid() = known-invalid/shouldn't happen value
|
|
||||||
// TODO: maybe remove this and use e.g. std::optional instead?
|
|
||||||
invalid,
|
|
||||||
// buf(a: any): any = a
|
|
||||||
// no-op operation
|
|
||||||
// when constructing the compute graph we generate invalid buf() nodes as a placeholder
|
|
||||||
// and later insert the argument
|
|
||||||
buf,
|
|
||||||
// slice(a: bit[in_width], offset: int, out_width: int): bit[out_width] = a[offset +: out_width]
|
|
||||||
// required: offset + out_width <= in_width
|
|
||||||
slice,
|
|
||||||
// zero_extend(a: unsigned bit[in_width], out_width: int): unsigned bit[out_width] = a (zero extended)
|
|
||||||
// required: out_width > in_width
|
|
||||||
zero_extend,
|
|
||||||
// sign_extend(a: signed bit[in_width], out_width: int): signed bit[out_width] = a (sign extended)
|
|
||||||
// required: out_width > in_width
|
|
||||||
sign_extend,
|
|
||||||
// concat(a: bit[N], b: bit[M]): bit[N+M] = {b, a} (verilog syntax)
|
|
||||||
// concatenates two bitvectors, with a in the least significant position and b in the more significant position
|
|
||||||
concat,
|
|
||||||
// add(a: bit[N], b: bit[N]): bit[N] = a + b
|
|
||||||
add,
|
|
||||||
// sub(a: bit[N], b: bit[N]): bit[N] = a - b
|
|
||||||
sub,
|
|
||||||
// mul(a: bit[N], b: bit[N]): bit[N] = a * b
|
|
||||||
mul,
|
|
||||||
// unsigned_div(a: unsigned bit[N], b: unsigned bit[N]): bit[N] = a / b
|
|
||||||
unsigned_div,
|
|
||||||
// unsigned_mod(a: signed bit[N], b: signed bit[N]): bit[N] = a % b
|
|
||||||
unsigned_mod,
|
|
||||||
// bitwise_and(a: bit[N], b: bit[N]): bit[N] = a & b
|
|
||||||
bitwise_and,
|
|
||||||
// bitwise_or(a: bit[N], b: bit[N]): bit[N] = a | b
|
|
||||||
bitwise_or,
|
|
||||||
// bitwise_xor(a: bit[N], b: bit[N]): bit[N] = a ^ b
|
|
||||||
bitwise_xor,
|
|
||||||
// bitwise_not(a: bit[N]): bit[N] = ~a
|
|
||||||
bitwise_not,
|
|
||||||
// reduce_and(a: bit[N]): bit[1] = &a
|
|
||||||
reduce_and,
|
|
||||||
// reduce_or(a: bit[N]): bit[1] = |a
|
|
||||||
reduce_or,
|
|
||||||
// reduce_xor(a: bit[N]): bit[1] = ^a
|
|
||||||
reduce_xor,
|
|
||||||
// unary_minus(a: bit[N]): bit[N] = -a
|
|
||||||
unary_minus,
|
|
||||||
// equal(a: bit[N], b: bit[N]): bit[1] = (a == b)
|
|
||||||
equal,
|
|
||||||
// not_equal(a: bit[N], b: bit[N]): bit[1] = (a != b)
|
|
||||||
not_equal,
|
|
||||||
// signed_greater_than(a: signed bit[N], b: signed bit[N]): bit[1] = (a > b)
|
|
||||||
signed_greater_than,
|
|
||||||
// signed_greater_equal(a: signed bit[N], b: signed bit[N]): bit[1] = (a >= b)
|
|
||||||
signed_greater_equal,
|
|
||||||
// unsigned_greater_than(a: unsigned bit[N], b: unsigned bit[N]): bit[1] = (a > b)
|
|
||||||
unsigned_greater_than,
|
|
||||||
// unsigned_greater_equal(a: unsigned bit[N], b: unsigned bit[N]): bit[1] = (a >= b)
|
|
||||||
unsigned_greater_equal,
|
|
||||||
// logical_shift_left(a: bit[N], b: unsigned bit[M]): bit[N] = a << b
|
|
||||||
// required: M == clog2(N)
|
|
||||||
logical_shift_left,
|
|
||||||
// logical_shift_right(a: unsigned bit[N], b: unsigned bit[M]): unsigned bit[N] = a >> b
|
|
||||||
// required: M == clog2(N)
|
|
||||||
logical_shift_right,
|
|
||||||
// arithmetic_shift_right(a: signed bit[N], b: unsigned bit[M]): signed bit[N] = a >> b
|
|
||||||
// required: M == clog2(N)
|
|
||||||
arithmetic_shift_right,
|
|
||||||
// mux(a: bit[N], b: bit[N], s: bit[1]): bit[N] = s ? b : a
|
|
||||||
mux,
|
|
||||||
// constant(a: Const[N]): bit[N] = a
|
|
||||||
constant,
|
|
||||||
// input(a: IdString): any
|
|
||||||
// returns the current value of the input with the specified name
|
|
||||||
input,
|
|
||||||
// state(a: IdString): any
|
|
||||||
// returns the current value of the state variable with the specified name
|
|
||||||
state,
|
|
||||||
// memory_read(memory: memory[addr_width, data_width], addr: bit[addr_width]): bit[data_width] = memory[addr]
|
|
||||||
memory_read,
|
|
||||||
// memory_write(memory: memory[addr_width, data_width], addr: bit[addr_width], data: bit[data_width]): memory[addr_width, data_width]
|
|
||||||
// returns a copy of `memory` but with the value at `addr` changed to `data`
|
|
||||||
memory_write
|
|
||||||
};
|
|
||||||
// returns the name of a FunctionalIR::Fn value, as a string literal
|
|
||||||
static const char *fn_to_string(Fn);
|
|
||||||
// FunctionalIR::Sort represents the sort or type of a node
|
|
||||||
// currently the only two types are signal/bit and memory
|
|
||||||
class Sort {
|
|
||||||
std::variant<int, std::pair<int, int>> _v;
|
|
||||||
public:
|
|
||||||
explicit Sort(int width) : _v(width) { }
|
|
||||||
Sort(int addr_width, int data_width) : _v(std::make_pair(addr_width, data_width)) { }
|
|
||||||
bool is_signal() const { return _v.index() == 0; }
|
|
||||||
bool is_memory() const { return _v.index() == 1; }
|
|
||||||
// returns the width of a bitvector type, errors out for other types
|
|
||||||
int width() const { return std::get<0>(_v); }
|
|
||||||
// returns the address width of a bitvector type, errors out for other types
|
|
||||||
int addr_width() const { return std::get<1>(_v).first; }
|
|
||||||
// returns the data width of a bitvector type, errors out for other types
|
|
||||||
int data_width() const { return std::get<1>(_v).second; }
|
|
||||||
bool operator==(Sort const& other) const { return _v == other._v; }
|
|
||||||
unsigned int hash() const { return mkhash(_v); }
|
|
||||||
};
|
|
||||||
private:
|
|
||||||
// one NodeData is stored per Node, containing the function and non-node arguments
|
|
||||||
// note that NodeData is deduplicated by ComputeGraph
|
|
||||||
class NodeData {
|
|
||||||
Fn _fn;
|
|
||||||
std::variant<
|
|
||||||
std::monostate,
|
|
||||||
RTLIL::Const,
|
|
||||||
IdString,
|
|
||||||
int
|
|
||||||
> _extra;
|
|
||||||
public:
|
|
||||||
NodeData() : _fn(Fn::invalid) {}
|
|
||||||
NodeData(Fn fn) : _fn(fn) {}
|
|
||||||
template<class T> NodeData(Fn fn, T &&extra) : _fn(fn), _extra(std::forward<T>(extra)) {}
|
|
||||||
Fn fn() const { return _fn; }
|
|
||||||
const RTLIL::Const &as_const() const { return std::get<RTLIL::Const>(_extra); }
|
|
||||||
IdString as_idstring() const { return std::get<IdString>(_extra); }
|
|
||||||
int as_int() const { return std::get<int>(_extra); }
|
|
||||||
int hash() const {
|
|
||||||
return mkhash((unsigned int) _fn, mkhash(_extra));
|
|
||||||
}
|
|
||||||
bool operator==(NodeData const &other) const {
|
|
||||||
return _fn == other._fn && _extra == other._extra;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Attr contains all the information about a note that should not be deduplicated
|
|
||||||
struct Attr {
|
|
||||||
Sort sort;
|
|
||||||
};
|
|
||||||
// our specialised version of ComputeGraph
|
|
||||||
// the sparse_attr IdString stores a naming suggestion, retrieved with name()
|
|
||||||
// the key is currently used to identify the nodes that represent output and next state values
|
|
||||||
// the bool is true for next state values
|
|
||||||
using Graph = ComputeGraph<NodeData, Attr, IdString, std::pair<IdString, bool>>;
|
|
||||||
Graph _graph;
|
|
||||||
dict<IdString, Sort> _input_sorts;
|
|
||||||
dict<IdString, Sort> _output_sorts;
|
|
||||||
dict<IdString, Sort> _state_sorts;
|
|
||||||
dict<IdString, RTLIL::Const> _initial_state_signal;
|
|
||||||
dict<IdString, MemContents> _initial_state_memory;
|
|
||||||
public:
|
|
||||||
class Factory;
|
|
||||||
// Node is an immutable reference to a FunctionalIR node
|
|
||||||
class Node {
|
|
||||||
friend class Factory;
|
|
||||||
friend class FunctionalIR;
|
|
||||||
Graph::ConstRef _ref;
|
|
||||||
explicit Node(Graph::ConstRef ref) : _ref(ref) { }
|
|
||||||
explicit operator Graph::ConstRef() { return _ref; }
|
|
||||||
public:
|
|
||||||
// the node's index. may change if nodes are added or removed
|
|
||||||
int id() const { return _ref.index(); }
|
|
||||||
// a name suggestion for the node, which need not be unique
|
|
||||||
IdString name() const {
|
|
||||||
if(_ref.has_sparse_attr())
|
|
||||||
return _ref.sparse_attr();
|
|
||||||
else
|
|
||||||
return std::string("\\n") + std::to_string(id());
|
|
||||||
}
|
|
||||||
Fn fn() const { return _ref.function().fn(); }
|
|
||||||
Sort sort() const { return _ref.attr().sort; }
|
|
||||||
// returns the width of a bitvector node, errors out for other nodes
|
|
||||||
int width() const { return sort().width(); }
|
|
||||||
size_t arg_count() const { return _ref.size(); }
|
|
||||||
Node arg(int n) const { return Node(_ref.arg(n)); }
|
|
||||||
// visit calls the appropriate visitor method depending on the type of the node
|
|
||||||
template<class Visitor> auto visit(Visitor v) const
|
|
||||||
{
|
|
||||||
// currently templated but could be switched to AbstractVisitor &
|
|
||||||
switch(_ref.function().fn()) {
|
|
||||||
case Fn::invalid: log_error("invalid node in visit"); break;
|
|
||||||
case Fn::buf: return v.buf(*this, arg(0)); break;
|
|
||||||
case Fn::slice: return v.slice(*this, arg(0), _ref.function().as_int(), sort().width()); break;
|
|
||||||
case Fn::zero_extend: return v.zero_extend(*this, arg(0), width()); break;
|
|
||||||
case Fn::sign_extend: return v.sign_extend(*this, arg(0), width()); break;
|
|
||||||
case Fn::concat: return v.concat(*this, arg(0), arg(1)); break;
|
|
||||||
case Fn::add: return v.add(*this, arg(0), arg(1)); break;
|
|
||||||
case Fn::sub: return v.sub(*this, arg(0), arg(1)); break;
|
|
||||||
case Fn::mul: return v.mul(*this, arg(0), arg(1)); break;
|
|
||||||
case Fn::unsigned_div: return v.unsigned_div(*this, arg(0), arg(1)); break;
|
|
||||||
case Fn::unsigned_mod: return v.unsigned_mod(*this, arg(0), arg(1)); break;
|
|
||||||
case Fn::bitwise_and: return v.bitwise_and(*this, arg(0), arg(1)); break;
|
|
||||||
case Fn::bitwise_or: return v.bitwise_or(*this, arg(0), arg(1)); break;
|
|
||||||
case Fn::bitwise_xor: return v.bitwise_xor(*this, arg(0), arg(1)); break;
|
|
||||||
case Fn::bitwise_not: return v.bitwise_not(*this, arg(0)); break;
|
|
||||||
case Fn::unary_minus: return v.unary_minus(*this, arg(0)); break;
|
|
||||||
case Fn::reduce_and: return v.reduce_and(*this, arg(0)); break;
|
|
||||||
case Fn::reduce_or: return v.reduce_or(*this, arg(0)); break;
|
|
||||||
case Fn::reduce_xor: return v.reduce_xor(*this, arg(0)); break;
|
|
||||||
case Fn::equal: return v.equal(*this, arg(0), arg(1)); break;
|
|
||||||
case Fn::not_equal: return v.not_equal(*this, arg(0), arg(1)); break;
|
|
||||||
case Fn::signed_greater_than: return v.signed_greater_than(*this, arg(0), arg(1)); break;
|
|
||||||
case Fn::signed_greater_equal: return v.signed_greater_equal(*this, arg(0), arg(1)); break;
|
|
||||||
case Fn::unsigned_greater_than: return v.unsigned_greater_than(*this, arg(0), arg(1)); break;
|
|
||||||
case Fn::unsigned_greater_equal: return v.unsigned_greater_equal(*this, arg(0), arg(1)); break;
|
|
||||||
case Fn::logical_shift_left: return v.logical_shift_left(*this, arg(0), arg(1)); break;
|
|
||||||
case Fn::logical_shift_right: return v.logical_shift_right(*this, arg(0), arg(1)); break;
|
|
||||||
case Fn::arithmetic_shift_right: return v.arithmetic_shift_right(*this, arg(0), arg(1)); break;
|
|
||||||
case Fn::mux: return v.mux(*this, arg(0), arg(1), arg(2)); break;
|
|
||||||
case Fn::constant: return v.constant(*this, _ref.function().as_const()); break;
|
|
||||||
case Fn::input: return v.input(*this, _ref.function().as_idstring()); break;
|
|
||||||
case Fn::state: return v.state(*this, _ref.function().as_idstring()); break;
|
|
||||||
case Fn::memory_read: return v.memory_read(*this, arg(0), arg(1)); break;
|
|
||||||
case Fn::memory_write: return v.memory_write(*this, arg(0), arg(1), arg(2)); break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::string to_string();
|
|
||||||
std::string to_string(std::function<std::string(Node)>);
|
|
||||||
};
|
|
||||||
// AbstractVisitor provides an abstract base class for visitors
|
|
||||||
template<class T> struct AbstractVisitor {
|
|
||||||
virtual T buf(Node self, Node n) = 0;
|
|
||||||
virtual T slice(Node self, Node a, int offset, int out_width) = 0;
|
|
||||||
virtual T zero_extend(Node self, Node a, int out_width) = 0;
|
|
||||||
virtual T sign_extend(Node self, Node a, int out_width) = 0;
|
|
||||||
virtual T concat(Node self, Node a, Node b) = 0;
|
|
||||||
virtual T add(Node self, Node a, Node b) = 0;
|
|
||||||
virtual T sub(Node self, Node a, Node b) = 0;
|
|
||||||
virtual T mul(Node self, Node a, Node b) = 0;
|
|
||||||
virtual T unsigned_div(Node self, Node a, Node b) = 0;
|
|
||||||
virtual T unsigned_mod(Node self, Node a, Node b) = 0;
|
|
||||||
virtual T bitwise_and(Node self, Node a, Node b) = 0;
|
|
||||||
virtual T bitwise_or(Node self, Node a, Node b) = 0;
|
|
||||||
virtual T bitwise_xor(Node self, Node a, Node b) = 0;
|
|
||||||
virtual T bitwise_not(Node self, Node a) = 0;
|
|
||||||
virtual T unary_minus(Node self, Node a) = 0;
|
|
||||||
virtual T reduce_and(Node self, Node a) = 0;
|
|
||||||
virtual T reduce_or(Node self, Node a) = 0;
|
|
||||||
virtual T reduce_xor(Node self, Node a) = 0;
|
|
||||||
virtual T equal(Node self, Node a, Node b) = 0;
|
|
||||||
virtual T not_equal(Node self, Node a, Node b) = 0;
|
|
||||||
virtual T signed_greater_than(Node self, Node a, Node b) = 0;
|
|
||||||
virtual T signed_greater_equal(Node self, Node a, Node b) = 0;
|
|
||||||
virtual T unsigned_greater_than(Node self, Node a, Node b) = 0;
|
|
||||||
virtual T unsigned_greater_equal(Node self, Node a, Node b) = 0;
|
|
||||||
virtual T logical_shift_left(Node self, Node a, Node b) = 0;
|
|
||||||
virtual T logical_shift_right(Node self, Node a, Node b) = 0;
|
|
||||||
virtual T arithmetic_shift_right(Node self, Node a, Node b) = 0;
|
|
||||||
virtual T mux(Node self, Node a, Node b, Node s) = 0;
|
|
||||||
virtual T constant(Node self, RTLIL::Const const & value) = 0;
|
|
||||||
virtual T input(Node self, IdString name) = 0;
|
|
||||||
virtual T state(Node self, IdString name) = 0;
|
|
||||||
virtual T memory_read(Node self, Node mem, Node addr) = 0;
|
|
||||||
virtual T memory_write(Node self, Node mem, Node addr, Node data) = 0;
|
|
||||||
};
|
|
||||||
// DefaultVisitor provides defaults for all visitor methods which just calls default_handler
|
|
||||||
template<class T> struct DefaultVisitor : public AbstractVisitor<T> {
|
|
||||||
virtual T default_handler(Node self) = 0;
|
|
||||||
T buf(Node self, Node) override { return default_handler(self); }
|
|
||||||
T slice(Node self, Node, int, int) override { return default_handler(self); }
|
|
||||||
T zero_extend(Node self, Node, int) override { return default_handler(self); }
|
|
||||||
T sign_extend(Node self, Node, int) override { return default_handler(self); }
|
|
||||||
T concat(Node self, Node, Node) override { return default_handler(self); }
|
|
||||||
T add(Node self, Node, Node) override { return default_handler(self); }
|
|
||||||
T sub(Node self, Node, Node) override { return default_handler(self); }
|
|
||||||
T mul(Node self, Node, Node) override { return default_handler(self); }
|
|
||||||
T unsigned_div(Node self, Node, Node) override { return default_handler(self); }
|
|
||||||
T unsigned_mod(Node self, Node, Node) override { return default_handler(self); }
|
|
||||||
T bitwise_and(Node self, Node, Node) override { return default_handler(self); }
|
|
||||||
T bitwise_or(Node self, Node, Node) override { return default_handler(self); }
|
|
||||||
T bitwise_xor(Node self, Node, Node) override { return default_handler(self); }
|
|
||||||
T bitwise_not(Node self, Node) override { return default_handler(self); }
|
|
||||||
T unary_minus(Node self, Node) override { return default_handler(self); }
|
|
||||||
T reduce_and(Node self, Node) override { return default_handler(self); }
|
|
||||||
T reduce_or(Node self, Node) override { return default_handler(self); }
|
|
||||||
T reduce_xor(Node self, Node) override { return default_handler(self); }
|
|
||||||
T equal(Node self, Node, Node) override { return default_handler(self); }
|
|
||||||
T not_equal(Node self, Node, Node) override { return default_handler(self); }
|
|
||||||
T signed_greater_than(Node self, Node, Node) override { return default_handler(self); }
|
|
||||||
T signed_greater_equal(Node self, Node, Node) override { return default_handler(self); }
|
|
||||||
T unsigned_greater_than(Node self, Node, Node) override { return default_handler(self); }
|
|
||||||
T unsigned_greater_equal(Node self, Node, Node) override { return default_handler(self); }
|
|
||||||
T logical_shift_left(Node self, Node, Node) override { return default_handler(self); }
|
|
||||||
T logical_shift_right(Node self, Node, Node) override { return default_handler(self); }
|
|
||||||
T arithmetic_shift_right(Node self, Node, Node) override { return default_handler(self); }
|
|
||||||
T mux(Node self, Node, Node, Node) override { return default_handler(self); }
|
|
||||||
T constant(Node self, RTLIL::Const const &) override { return default_handler(self); }
|
|
||||||
T input(Node self, IdString) override { return default_handler(self); }
|
|
||||||
T state(Node self, IdString) override { return default_handler(self); }
|
|
||||||
T memory_read(Node self, Node, Node) override { return default_handler(self); }
|
|
||||||
T memory_write(Node self, Node, Node, Node) override { return default_handler(self); }
|
|
||||||
};
|
|
||||||
// a factory is used to modify a FunctionalIR. it creates new nodes and allows for some modification of existing nodes.
|
|
||||||
class Factory {
|
|
||||||
FunctionalIR &_ir;
|
|
||||||
friend class FunctionalIR;
|
|
||||||
explicit Factory(FunctionalIR &ir) : _ir(ir) {}
|
|
||||||
Node add(NodeData &&fn, Sort &&sort, std::initializer_list<Node> args) {
|
|
||||||
log_assert(!sort.is_signal() || sort.width() > 0);
|
|
||||||
log_assert(!sort.is_memory() || sort.addr_width() > 0 && sort.data_width() > 0);
|
|
||||||
Graph::Ref ref = _ir._graph.add(std::move(fn), {std::move(sort)});
|
|
||||||
for (auto arg : args)
|
|
||||||
ref.append_arg(Graph::ConstRef(arg));
|
|
||||||
return Node(ref);
|
|
||||||
}
|
|
||||||
Graph::Ref mutate(Node n) {
|
|
||||||
return _ir._graph[n._ref.index()];
|
|
||||||
}
|
|
||||||
void check_basic_binary(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && a.sort() == b.sort()); }
|
|
||||||
void check_shift(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && b.sort().is_signal() && b.width() == ceil_log2(a.width())); }
|
|
||||||
void check_unary(Node const &a) { log_assert(a.sort().is_signal()); }
|
|
||||||
public:
|
|
||||||
Node slice(Node a, int offset, int out_width) {
|
|
||||||
log_assert(a.sort().is_signal() && offset + out_width <= a.sort().width());
|
|
||||||
if(offset == 0 && out_width == a.width())
|
|
||||||
return a;
|
|
||||||
return add(NodeData(Fn::slice, offset), Sort(out_width), {a});
|
|
||||||
}
|
|
||||||
// extend will either extend or truncate the provided value to reach the desired width
|
|
||||||
Node extend(Node a, int out_width, bool is_signed) {
|
|
||||||
int in_width = a.sort().width();
|
|
||||||
log_assert(a.sort().is_signal());
|
|
||||||
if(in_width == out_width)
|
|
||||||
return a;
|
|
||||||
if(in_width > out_width)
|
|
||||||
return slice(a, 0, out_width);
|
|
||||||
if(is_signed)
|
|
||||||
return add(Fn::sign_extend, Sort(out_width), {a});
|
|
||||||
else
|
|
||||||
return add(Fn::zero_extend, Sort(out_width), {a});
|
|
||||||
}
|
|
||||||
Node concat(Node a, Node b) {
|
|
||||||
log_assert(a.sort().is_signal() && b.sort().is_signal());
|
|
||||||
return add(Fn::concat, Sort(a.sort().width() + b.sort().width()), {a, b});
|
|
||||||
}
|
|
||||||
Node add(Node a, Node b) { check_basic_binary(a, b); return add(Fn::add, a.sort(), {a, b}); }
|
|
||||||
Node sub(Node a, Node b) { check_basic_binary(a, b); return add(Fn::sub, a.sort(), {a, b}); }
|
|
||||||
Node mul(Node a, Node b) { check_basic_binary(a, b); return add(Fn::mul, a.sort(), {a, b}); }
|
|
||||||
Node unsigned_div(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_div, a.sort(), {a, b}); }
|
|
||||||
Node unsigned_mod(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_mod, a.sort(), {a, b}); }
|
|
||||||
Node bitwise_and(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_and, a.sort(), {a, b}); }
|
|
||||||
Node bitwise_or(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_or, a.sort(), {a, b}); }
|
|
||||||
Node bitwise_xor(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_xor, a.sort(), {a, b}); }
|
|
||||||
Node bitwise_not(Node a) { check_unary(a); return add(Fn::bitwise_not, a.sort(), {a}); }
|
|
||||||
Node unary_minus(Node a) { check_unary(a); return add(Fn::unary_minus, a.sort(), {a}); }
|
|
||||||
Node reduce_and(Node a) {
|
|
||||||
check_unary(a);
|
|
||||||
if(a.width() == 1)
|
|
||||||
return a;
|
|
||||||
return add(Fn::reduce_and, Sort(1), {a});
|
|
||||||
}
|
|
||||||
Node reduce_or(Node a) {
|
|
||||||
check_unary(a);
|
|
||||||
if(a.width() == 1)
|
|
||||||
return a;
|
|
||||||
return add(Fn::reduce_or, Sort(1), {a});
|
|
||||||
}
|
|
||||||
Node reduce_xor(Node a) {
|
|
||||||
check_unary(a);
|
|
||||||
if(a.width() == 1)
|
|
||||||
return a;
|
|
||||||
return add(Fn::reduce_xor, Sort(1), {a});
|
|
||||||
}
|
|
||||||
Node equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::equal, Sort(1), {a, b}); }
|
|
||||||
Node not_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::not_equal, Sort(1), {a, b}); }
|
|
||||||
Node signed_greater_than(Node a, Node b) { check_basic_binary(a, b); return add(Fn::signed_greater_than, Sort(1), {a, b}); }
|
|
||||||
Node signed_greater_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::signed_greater_equal, Sort(1), {a, b}); }
|
|
||||||
Node unsigned_greater_than(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_greater_than, Sort(1), {a, b}); }
|
|
||||||
Node unsigned_greater_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_greater_equal, Sort(1), {a, b}); }
|
|
||||||
Node logical_shift_left(Node a, Node b) { check_shift(a, b); return add(Fn::logical_shift_left, a.sort(), {a, b}); }
|
|
||||||
Node logical_shift_right(Node a, Node b) { check_shift(a, b); return add(Fn::logical_shift_right, a.sort(), {a, b}); }
|
|
||||||
Node arithmetic_shift_right(Node a, Node b) { check_shift(a, b); return add(Fn::arithmetic_shift_right, a.sort(), {a, b}); }
|
|
||||||
Node mux(Node a, Node b, Node s) {
|
|
||||||
log_assert(a.sort().is_signal() && a.sort() == b.sort() && s.sort() == Sort(1));
|
|
||||||
return add(Fn::mux, a.sort(), {a, b, s});
|
|
||||||
}
|
|
||||||
Node memory_read(Node mem, Node addr) {
|
|
||||||
log_assert(mem.sort().is_memory() && addr.sort().is_signal() && mem.sort().addr_width() == addr.sort().width());
|
|
||||||
return add(Fn::memory_read, Sort(mem.sort().data_width()), {mem, addr});
|
|
||||||
}
|
|
||||||
Node memory_write(Node mem, Node addr, Node data) {
|
|
||||||
log_assert(mem.sort().is_memory() && addr.sort().is_signal() && data.sort().is_signal() &&
|
|
||||||
mem.sort().addr_width() == addr.sort().width() && mem.sort().data_width() == data.sort().width());
|
|
||||||
return add(Fn::memory_write, mem.sort(), {mem, addr, data});
|
|
||||||
}
|
|
||||||
Node constant(RTLIL::Const value) {
|
|
||||||
return add(NodeData(Fn::constant, std::move(value)), Sort(value.size()), {});
|
|
||||||
}
|
|
||||||
Node create_pending(int width) {
|
|
||||||
return add(Fn::buf, Sort(width), {});
|
|
||||||
}
|
|
||||||
void update_pending(Node node, Node value) {
|
|
||||||
log_assert(node._ref.function() == Fn::buf && node._ref.size() == 0);
|
|
||||||
log_assert(node.sort() == value.sort());
|
|
||||||
mutate(node).append_arg(value._ref);
|
|
||||||
}
|
|
||||||
void add_input(IdString name, int width) {
|
|
||||||
auto [it, inserted] = _ir._input_sorts.emplace(name, Sort(width));
|
|
||||||
if (!inserted) log_error("input `%s` was re-defined", name.c_str());
|
|
||||||
}
|
|
||||||
void add_output(IdString name, int width) {
|
|
||||||
auto [it, inserted] = _ir._output_sorts.emplace(name, Sort(width));
|
|
||||||
if (!inserted) log_error("output `%s` was re-defined", name.c_str());
|
|
||||||
}
|
|
||||||
void add_state(IdString name, Sort sort) {
|
|
||||||
auto [it, inserted] = _ir._state_sorts.emplace(name, sort);
|
|
||||||
if (!inserted) log_error("state `%s` was re-defined", name.c_str());
|
|
||||||
}
|
|
||||||
Node input(IdString name) {
|
|
||||||
return add(NodeData(Fn::input, name), Sort(_ir._input_sorts.at(name)), {});
|
|
||||||
}
|
|
||||||
Node current_state(IdString name) {
|
|
||||||
return add(NodeData(Fn::state, name), Sort(_ir._state_sorts.at(name)), {});
|
|
||||||
}
|
|
||||||
void set_output(IdString output, Node value) {
|
|
||||||
log_assert(_ir._output_sorts.at(output) == value.sort());
|
|
||||||
mutate(value).assign_key({output, false});
|
|
||||||
}
|
|
||||||
void set_initial_state(IdString state, RTLIL::Const value) {
|
|
||||||
Sort &sort = _ir._state_sorts.at(state);
|
|
||||||
value.extu(sort.width());
|
|
||||||
_ir._initial_state_signal.emplace(state, std::move(value));
|
|
||||||
}
|
|
||||||
void set_initial_state(IdString state, MemContents value) {
|
|
||||||
log_assert(Sort(value.addr_width(), value.data_width()) == _ir._state_sorts.at(state));
|
|
||||||
_ir._initial_state_memory.emplace(state, std::move(value));
|
|
||||||
}
|
|
||||||
void set_next_state(IdString state, Node value) {
|
|
||||||
log_assert(_ir._state_sorts.at(state) == value.sort());
|
|
||||||
mutate(value).assign_key({state, true});
|
|
||||||
}
|
|
||||||
void suggest_name(Node node, IdString name) {
|
|
||||||
mutate(node).sparse_attr() = name;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
static FunctionalIR from_module(Module *module);
|
|
||||||
Factory factory() { return Factory(*this); }
|
|
||||||
int size() const { return _graph.size(); }
|
|
||||||
Node operator[](int i) { return Node(_graph[i]); }
|
|
||||||
void topological_sort();
|
|
||||||
void forward_buf();
|
|
||||||
dict<IdString, Sort> inputs() const { return _input_sorts; }
|
|
||||||
dict<IdString, Sort> outputs() const { return _output_sorts; }
|
|
||||||
dict<IdString, Sort> state() const { return _state_sorts; }
|
|
||||||
RTLIL::Const const &get_initial_state_signal(IdString name) { return _initial_state_signal.at(name); }
|
|
||||||
MemContents const &get_initial_state_memory(IdString name) { return _initial_state_memory.at(name); }
|
|
||||||
Node get_output_node(IdString name) { return Node(_graph({name, false})); }
|
|
||||||
Node get_state_next_node(IdString name) { return Node(_graph({name, true})); }
|
|
||||||
class Iterator {
|
|
||||||
friend class FunctionalIR;
|
|
||||||
FunctionalIR *_ir;
|
|
||||||
int _index;
|
|
||||||
Iterator(FunctionalIR *ir, int index) : _ir(ir), _index(index) {}
|
|
||||||
public:
|
|
||||||
Node operator*() { return Node(_ir->_graph[_index]); }
|
|
||||||
Iterator &operator++() { _index++; return *this; }
|
|
||||||
bool operator!=(Iterator const &other) const { return _index != other._index; }
|
|
||||||
};
|
|
||||||
Iterator begin() { return Iterator(this, 0); }
|
|
||||||
Iterator end() { return Iterator(this, _graph.size()); }
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace FunctionalTools {
|
|
||||||
template<class Id> class Scope {
|
|
||||||
protected:
|
|
||||||
char substitution_character = '_';
|
|
||||||
virtual bool is_character_legal(char) = 0;
|
|
||||||
private:
|
|
||||||
pool<std::string> _used_names;
|
|
||||||
dict<Id, std::string> _by_id;
|
|
||||||
public:
|
|
||||||
void reserve(std::string name) {
|
|
||||||
_used_names.insert(std::move(name));
|
|
||||||
}
|
|
||||||
std::string unique_name(IdString suggestion) {
|
|
||||||
std::string str = RTLIL::unescape_id(suggestion);
|
|
||||||
for(size_t i = 0; i < str.size(); i++)
|
|
||||||
if(!is_character_legal(str[i]))
|
|
||||||
str[i] = substitution_character;
|
|
||||||
if(_used_names.count(str) == 0) {
|
|
||||||
_used_names.insert(str);
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
for (int idx = 0 ; ; idx++){
|
|
||||||
std::string suffixed = str + "_" + std::to_string(idx);
|
|
||||||
if(_used_names.count(suffixed) == 0) {
|
|
||||||
_used_names.insert(suffixed);
|
|
||||||
return suffixed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::string operator()(Id id, IdString suggestion) {
|
|
||||||
auto it = _by_id.find(id);
|
|
||||||
if(it != _by_id.end())
|
|
||||||
return it->second;
|
|
||||||
std::string str = unique_name(suggestion);
|
|
||||||
_by_id.insert({id, str});
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
class Writer {
|
|
||||||
std::ostream *os;
|
|
||||||
void print_impl(const char *fmt, vector<std::function<void()>>& fns);
|
|
||||||
public:
|
|
||||||
Writer(std::ostream &os) : os(&os) {}
|
|
||||||
template<class T> Writer& operator <<(T&& arg) { *os << std::forward<T>(arg); return *this; }
|
|
||||||
template<typename... Args>
|
|
||||||
void print(const char *fmt, Args&&... args)
|
|
||||||
{
|
|
||||||
vector<std::function<void()>> fns { [&]() { *this << args; }... };
|
|
||||||
print_impl(fmt, fns);
|
|
||||||
}
|
|
||||||
template<typename Fn, typename... Args>
|
|
||||||
void print_with(Fn fn, const char *fmt, Args&&... args)
|
|
||||||
{
|
|
||||||
vector<std::function<void()>> fns { [&]() {
|
|
||||||
if constexpr (std::is_invocable_v<Fn, Args>)
|
|
||||||
*this << fn(args);
|
|
||||||
else
|
|
||||||
*this << args; }...
|
|
||||||
};
|
|
||||||
print_impl(fmt, fns);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
YOSYS_NAMESPACE_END
|
|
||||||
|
|
||||||
#endif
|
|
11
kernel/mem.h
11
kernel/mem.h
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
#include "kernel/yosys.h"
|
#include "kernel/yosys.h"
|
||||||
#include "kernel/ffinit.h"
|
#include "kernel/ffinit.h"
|
||||||
|
#include "kernel/utils.h"
|
||||||
|
|
||||||
YOSYS_NAMESPACE_BEGIN
|
YOSYS_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
@ -224,15 +225,6 @@ struct Mem : RTLIL::AttrObject {
|
||||||
Mem(Module *module, IdString memid, int width, int start_offset, int size) : module(module), memid(memid), packed(false), mem(nullptr), cell(nullptr), width(width), start_offset(start_offset), size(size) {}
|
Mem(Module *module, IdString memid, int width, int start_offset, int size) : module(module), memid(memid), packed(false), mem(nullptr), cell(nullptr), width(width), start_offset(start_offset), size(size) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// this class is used for implementing operator-> on iterators that return values rather than references
|
|
||||||
// it's necessary because in C++ operator-> is called recursively until a raw pointer is obtained
|
|
||||||
template<class T>
|
|
||||||
struct arrow_proxy {
|
|
||||||
T v;
|
|
||||||
explicit arrow_proxy(T const & v) : v(v) {}
|
|
||||||
T* operator->() { return &v; }
|
|
||||||
};
|
|
||||||
|
|
||||||
// MemContents efficiently represents the contents of a potentially sparse memory by storing only those segments that are actually defined
|
// MemContents efficiently represents the contents of a potentially sparse memory by storing only those segments that are actually defined
|
||||||
class MemContents {
|
class MemContents {
|
||||||
public:
|
public:
|
||||||
|
@ -303,6 +295,7 @@ public:
|
||||||
reference operator *() const { return range(_memory->_data_width, _addr, _memory->_values.at(_addr)); }
|
reference operator *() const { return range(_memory->_data_width, _addr, _memory->_values.at(_addr)); }
|
||||||
pointer operator->() const { return arrow_proxy<range>(**this); }
|
pointer operator->() const { return arrow_proxy<range>(**this); }
|
||||||
bool operator !=(iterator const &other) const { return _memory != other._memory || _addr != other._addr; }
|
bool operator !=(iterator const &other) const { return _memory != other._memory || _addr != other._addr; }
|
||||||
|
bool operator ==(iterator const &other) const { return !(*this != other); }
|
||||||
iterator &operator++();
|
iterator &operator++();
|
||||||
};
|
};
|
||||||
MemContents(int addr_width, int data_width, RTLIL::Const default_value)
|
MemContents(int addr_width, int data_width, RTLIL::Const default_value)
|
||||||
|
|
|
@ -253,6 +253,15 @@ template <typename T, typename C = std::less<T>, typename OPS = hash_ops<T>> cla
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// this class is used for implementing operator-> on iterators that return values rather than references
|
||||||
|
// it's necessary because in C++ operator-> is called recursively until a raw pointer is obtained
|
||||||
|
template<class T>
|
||||||
|
struct arrow_proxy {
|
||||||
|
T v;
|
||||||
|
explicit arrow_proxy(T const & v) : v(v) {}
|
||||||
|
T* operator->() { return &v; }
|
||||||
|
};
|
||||||
|
|
||||||
YOSYS_NAMESPACE_END
|
YOSYS_NAMESPACE_END
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#include "kernel/yosys.h"
|
#include "kernel/yosys.h"
|
||||||
#include "kernel/drivertools.h"
|
#include "kernel/drivertools.h"
|
||||||
#include "kernel/topo_scc.h"
|
#include "kernel/topo_scc.h"
|
||||||
#include "kernel/functional.h"
|
#include "kernel/compute_graph.h"
|
||||||
|
|
||||||
USING_YOSYS_NAMESPACE
|
USING_YOSYS_NAMESPACE
|
||||||
PRIVATE_NAMESPACE_BEGIN
|
PRIVATE_NAMESPACE_BEGIN
|
||||||
|
|
Loading…
Reference in New Issue