mirror of https://github.com/YosysHQ/yosys.git
commit
b20df72e1e
|
@ -39,6 +39,7 @@ jobs:
|
|||
echo "ENABLE_VERIFIC_LIBERTY := 1" >> Makefile.conf
|
||||
echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 1" >> Makefile.conf
|
||||
echo "ENABLE_CCACHE := 1" >> Makefile.conf
|
||||
echo "ENABLE_FUNCTIONAL_TESTS := 1" >> Makefile.conf
|
||||
make -j${{ env.procs }} ENABLE_LTO=1
|
||||
|
||||
- name: Install Yosys
|
||||
|
|
19
Makefile
19
Makefile
|
@ -38,6 +38,7 @@ ENABLE_LTO := 0
|
|||
ENABLE_CCACHE := 0
|
||||
# sccache is not always a drop-in replacement for ccache in practice
|
||||
ENABLE_SCCACHE := 0
|
||||
ENABLE_FUNCTIONAL_TESTS := 0
|
||||
LINK_CURSES := 0
|
||||
LINK_TERMCAP := 0
|
||||
LINK_ABC := 0
|
||||
|
@ -598,6 +599,7 @@ $(eval $(call add_include_file,kernel/celltypes.h))
|
|||
$(eval $(call add_include_file,kernel/consteval.h))
|
||||
$(eval $(call add_include_file,kernel/constids.inc))
|
||||
$(eval $(call add_include_file,kernel/cost.h))
|
||||
$(eval $(call add_include_file,kernel/drivertools.h))
|
||||
$(eval $(call add_include_file,kernel/ff.h))
|
||||
$(eval $(call add_include_file,kernel/ffinit.h))
|
||||
$(eval $(call add_include_file,kernel/ffmerge.h))
|
||||
|
@ -616,6 +618,7 @@ $(eval $(call add_include_file,kernel/register.h))
|
|||
$(eval $(call add_include_file,kernel/rtlil.h))
|
||||
$(eval $(call add_include_file,kernel/satgen.h))
|
||||
$(eval $(call add_include_file,kernel/scopeinfo.h))
|
||||
$(eval $(call add_include_file,kernel/sexpr.h))
|
||||
$(eval $(call add_include_file,kernel/sigtools.h))
|
||||
$(eval $(call add_include_file,kernel/timinginfo.h))
|
||||
$(eval $(call add_include_file,kernel/utils.h))
|
||||
|
@ -637,7 +640,8 @@ $(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/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
|
||||
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/functional.o
|
||||
ifeq ($(ENABLE_ZLIB),1)
|
||||
OBJS += kernel/fstdata.o
|
||||
endif
|
||||
|
@ -889,6 +893,9 @@ endif
|
|||
+cd tests/xprop && bash run-test.sh $(SEEDOPT)
|
||||
+cd tests/fmt && bash run-test.sh
|
||||
+cd tests/cxxrtl && bash run-test.sh
|
||||
ifeq ($(ENABLE_FUNCTIONAL_TESTS),1)
|
||||
+cd tests/functional && bash run-test.sh
|
||||
endif
|
||||
@echo ""
|
||||
@echo " Passed \"make test\"."
|
||||
@echo ""
|
||||
|
@ -1049,6 +1056,16 @@ coverage:
|
|||
lcov --capture -d . --no-external -o coverage.info
|
||||
genhtml coverage.info --output-directory coverage_html
|
||||
|
||||
clean_coverage:
|
||||
find . -name "*.gcda" -type f -delete
|
||||
|
||||
FUNC_KERNEL := functional.cc functional.h sexpr.cc sexpr.h compute_graph.h
|
||||
FUNC_INCLUDES := $(addprefix --include *,functional/* $(FUNC_KERNEL))
|
||||
coverage_functional:
|
||||
rm -rf coverage.info coverage_html
|
||||
lcov --capture -d backends/functional -d kernel $(FUNC_INCLUDES) --no-external -o coverage.info
|
||||
genhtml coverage.info --output-directory coverage_html
|
||||
|
||||
qtcreator:
|
||||
echo "$(CXXFLAGS)" | grep -o '\-D[^ ]*' | tr ' ' '\n' | sed 's/-D/#define /' | sed 's/=/ /'> qtcreator.config
|
||||
{ for file in $(basename $(OBJS)); do \
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
OBJS += backends/functional/cxx.o
|
||||
OBJS += backends/functional/smtlib.o
|
||||
OBJS += backends/functional/smtlib_rosette.o
|
||||
OBJS += backends/functional/test_generic.o
|
|
@ -0,0 +1,275 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
||||
* Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "kernel/yosys.h"
|
||||
#include "kernel/functional.h"
|
||||
#include <ctype.h>
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
const char *reserved_keywords[] = {
|
||||
"alignas","alignof","and","and_eq","asm","atomic_cancel","atomic_commit",
|
||||
"atomic_noexcept","auto","bitand","bitor","bool","break","case",
|
||||
"catch","char","char16_t","char32_t","char8_t","class","co_await",
|
||||
"co_return","co_yield","compl","concept","const","const_cast","consteval",
|
||||
"constexpr","constinit","continue","decltype","default","delete",
|
||||
"do","double","dynamic_cast","else","enum","explicit","export",
|
||||
"extern","false","float","for","friend","goto","if","inline",
|
||||
"int","long","mutable","namespace","new","noexcept","not","not_eq",
|
||||
"nullptr","operator","or","or_eq","private","protected","public",
|
||||
"reflexpr","register","reinterpret_cast","requires","return","short",
|
||||
"signed","sizeof","static","static_log_assert","static_cast","struct",
|
||||
"switch","synchronized","template","this","thread_local","throw",
|
||||
"true","try","typedef","typeid","typename","union","unsigned",
|
||||
"using","virtual","void","volatile","wchar_t","while","xor","xor_eq",
|
||||
nullptr
|
||||
};
|
||||
|
||||
template<typename Id> struct CxxScope : public Functional::Scope<Id> {
|
||||
CxxScope() {
|
||||
for(const char **p = reserved_keywords; *p != nullptr; p++)
|
||||
this->reserve(*p);
|
||||
}
|
||||
bool is_character_legal(char c, int index) override {
|
||||
return isascii(c) && (isalpha(c) || (isdigit(c) && index > 0) || c == '_' || c == '$');
|
||||
}
|
||||
};
|
||||
|
||||
struct CxxType {
|
||||
Functional::Sort sort;
|
||||
CxxType(Functional::Sort sort) : sort(sort) {}
|
||||
std::string to_string() const {
|
||||
if(sort.is_memory()) {
|
||||
return stringf("Memory<%d, %d>", sort.addr_width(), sort.data_width());
|
||||
} else if(sort.is_signal()) {
|
||||
return stringf("Signal<%d>", sort.width());
|
||||
} else {
|
||||
log_error("unknown sort");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using CxxWriter = Functional::Writer;
|
||||
|
||||
struct CxxStruct {
|
||||
std::string name;
|
||||
dict<IdString, CxxType> types;
|
||||
CxxScope<IdString> scope;
|
||||
CxxStruct(std::string name) : name(name)
|
||||
{
|
||||
scope.reserve("fn");
|
||||
scope.reserve("visit");
|
||||
}
|
||||
void insert(IdString name, CxxType type) {
|
||||
scope(name, name);
|
||||
types.insert({name, type});
|
||||
}
|
||||
void print(CxxWriter &f) {
|
||||
f.print("\tstruct {} {{\n", name);
|
||||
for (auto p : types) {
|
||||
f.print("\t\t{} {};\n", p.second.to_string(), scope(p.first, p.first));
|
||||
}
|
||||
f.print("\n\t\ttemplate <typename T> void visit(T &&fn) {{\n");
|
||||
for (auto p : types) {
|
||||
f.print("\t\t\tfn(\"{}\", {});\n", RTLIL::unescape_id(p.first), scope(p.first, p.first));
|
||||
}
|
||||
f.print("\t\t}}\n");
|
||||
f.print("\t}};\n\n");
|
||||
};
|
||||
std::string operator[](IdString field) {
|
||||
return scope(field, field);
|
||||
}
|
||||
};
|
||||
|
||||
std::string cxx_const(RTLIL::Const const &value) {
|
||||
std::stringstream ss;
|
||||
ss << "Signal<" << value.size() << ">(" << std::hex << std::showbase;
|
||||
if(value.size() > 32) ss << "{";
|
||||
for(int i = 0; i < value.size(); i += 32) {
|
||||
if(i > 0) ss << ", ";
|
||||
ss << value.extract(i, 32).as_int();
|
||||
}
|
||||
if(value.size() > 32) ss << "}";
|
||||
ss << ")";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
template<class NodePrinter> struct CxxPrintVisitor : public Functional::AbstractVisitor<void> {
|
||||
using Node = Functional::Node;
|
||||
CxxWriter &f;
|
||||
NodePrinter np;
|
||||
CxxStruct &input_struct;
|
||||
CxxStruct &state_struct;
|
||||
CxxPrintVisitor(CxxWriter &f, NodePrinter np, CxxStruct &input_struct, CxxStruct &state_struct) : f(f), np(np), input_struct(input_struct), state_struct(state_struct) { }
|
||||
template<typename... Args> void print(const char *fmt, Args&&... args) {
|
||||
f.print_with(np, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
void buf(Node, Node n) override { print("{}", n); }
|
||||
void slice(Node, Node a, int offset, int out_width) override { print("{0}.slice<{2}>({1})", a, offset, out_width); }
|
||||
void zero_extend(Node, Node a, int out_width) override { print("{}.zero_extend<{}>()", a, out_width); }
|
||||
void sign_extend(Node, Node a, int out_width) override { print("{}.sign_extend<{}>()", a, out_width); }
|
||||
void concat(Node, Node a, Node b) override { print("{}.concat({})", a, b); }
|
||||
void add(Node, Node a, Node b) override { print("{} + {}", a, b); }
|
||||
void sub(Node, Node a, Node b) override { print("{} - {}", a, b); }
|
||||
void mul(Node, Node a, Node b) override { print("{} * {}", a, b); }
|
||||
void unsigned_div(Node, Node a, Node b) override { print("{} / {}", a, b); }
|
||||
void unsigned_mod(Node, Node a, Node b) override { print("{} % {}", a, b); }
|
||||
void bitwise_and(Node, Node a, Node b) override { print("{} & {}", a, b); }
|
||||
void bitwise_or(Node, Node a, Node b) override { print("{} | {}", a, b); }
|
||||
void bitwise_xor(Node, Node a, Node b) override { print("{} ^ {}", a, b); }
|
||||
void bitwise_not(Node, Node a) override { print("~{}", a); }
|
||||
void unary_minus(Node, Node a) override { print("-{}", a); }
|
||||
void reduce_and(Node, Node a) override { print("{}.all()", a); }
|
||||
void reduce_or(Node, Node a) override { print("{}.any()", a); }
|
||||
void reduce_xor(Node, Node a) override { print("{}.parity()", a); }
|
||||
void equal(Node, Node a, Node b) override { print("{} == {}", a, b); }
|
||||
void not_equal(Node, Node a, Node b) override { print("{} != {}", a, b); }
|
||||
void signed_greater_than(Node, Node a, Node b) override { print("{}.signed_greater_than({})", a, b); }
|
||||
void signed_greater_equal(Node, Node a, Node b) override { print("{}.signed_greater_equal({})", a, b); }
|
||||
void unsigned_greater_than(Node, Node a, Node b) override { print("{} > {}", a, b); }
|
||||
void unsigned_greater_equal(Node, Node a, Node b) override { print("{} >= {}", a, b); }
|
||||
void logical_shift_left(Node, Node a, Node b) override { print("{} << {}", a, b); }
|
||||
void logical_shift_right(Node, Node a, Node b) override { print("{} >> {}", a, b); }
|
||||
void arithmetic_shift_right(Node, Node a, Node b) override { print("{}.arithmetic_shift_right({})", a, b); }
|
||||
void mux(Node, Node a, Node b, Node s) override { print("{2}.any() ? {1} : {0}", a, b, s); }
|
||||
void constant(Node, RTLIL::Const const & value) override { print("{}", cxx_const(value)); }
|
||||
void input(Node, IdString name, IdString kind) override { log_assert(kind == ID($input)); print("input.{}", input_struct[name]); }
|
||||
void state(Node, IdString name, IdString kind) override { log_assert(kind == ID($state)); print("current_state.{}", state_struct[name]); }
|
||||
void memory_read(Node, Node mem, Node addr) override { print("{}.read({})", mem, addr); }
|
||||
void memory_write(Node, Node mem, Node addr, Node data) override { print("{}.write({}, {})", mem, addr, data); }
|
||||
};
|
||||
|
||||
bool equal_def(RTLIL::Const const &a, RTLIL::Const const &b) {
|
||||
if(a.size() != b.size()) return false;
|
||||
for(int i = 0; i < a.size(); i++)
|
||||
if((a[i] == State::S1) != (b[i] == State::S1))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct CxxModule {
|
||||
Functional::IR ir;
|
||||
CxxStruct input_struct, output_struct, state_struct;
|
||||
std::string module_name;
|
||||
|
||||
explicit CxxModule(Module *module) :
|
||||
ir(Functional::IR::from_module(module)),
|
||||
input_struct("Inputs"),
|
||||
output_struct("Outputs"),
|
||||
state_struct("State")
|
||||
{
|
||||
for (auto input : ir.inputs())
|
||||
input_struct.insert(input->name, input->sort);
|
||||
for (auto output : ir.outputs())
|
||||
output_struct.insert(output->name, output->sort);
|
||||
for (auto state : ir.states())
|
||||
state_struct.insert(state->name, state->sort);
|
||||
module_name = CxxScope<int>().unique_name(module->name);
|
||||
}
|
||||
void write_header(CxxWriter &f) {
|
||||
f.print("#include \"sim.h\"\n\n");
|
||||
}
|
||||
void write_struct_def(CxxWriter &f) {
|
||||
f.print("struct {} {{\n", module_name);
|
||||
input_struct.print(f);
|
||||
output_struct.print(f);
|
||||
state_struct.print(f);
|
||||
f.print("\tstatic void eval(Inputs const &, Outputs &, State const &, State &);\n");
|
||||
f.print("\tstatic void initialize(State &);\n");
|
||||
f.print("}};\n\n");
|
||||
}
|
||||
void write_initial_def(CxxWriter &f) {
|
||||
f.print("void {0}::initialize({0}::State &state)\n{{\n", module_name);
|
||||
for (auto state : ir.states()) {
|
||||
if (state->sort.is_signal())
|
||||
f.print("\tstate.{} = {};\n", state_struct[state->name], cxx_const(state->initial_value_signal()));
|
||||
else if (state->sort.is_memory()) {
|
||||
f.print("\t{{\n");
|
||||
f.print("\t\tstd::array<Signal<{}>, {}> mem;\n", state->sort.data_width(), 1<<state->sort.addr_width());
|
||||
const auto &contents = state->initial_value_memory();
|
||||
f.print("\t\tmem.fill({});\n", cxx_const(contents.default_value()));
|
||||
for(auto range : contents)
|
||||
for(auto addr = range.base(); addr < range.limit(); addr++)
|
||||
if(!equal_def(range[addr], contents.default_value()))
|
||||
f.print("\t\tmem[{}] = {};\n", addr, cxx_const(range[addr]));
|
||||
f.print("\t\tstate.{} = mem;\n", state_struct[state->name]);
|
||||
f.print("\t}}\n");
|
||||
}
|
||||
}
|
||||
f.print("}}\n\n");
|
||||
}
|
||||
void write_eval_def(CxxWriter &f) {
|
||||
f.print("void {0}::eval({0}::Inputs const &input, {0}::Outputs &output, {0}::State const ¤t_state, {0}::State &next_state)\n{{\n", module_name);
|
||||
CxxScope<int> locals;
|
||||
locals.reserve("input");
|
||||
locals.reserve("output");
|
||||
locals.reserve("current_state");
|
||||
locals.reserve("next_state");
|
||||
auto node_name = [&](Functional::Node n) { return locals(n.id(), n.name()); };
|
||||
CxxPrintVisitor printVisitor(f, node_name, input_struct, state_struct);
|
||||
for (auto node : ir) {
|
||||
f.print("\t{} {} = ", CxxType(node.sort()).to_string(), node_name(node));
|
||||
node.visit(printVisitor);
|
||||
f.print(";\n");
|
||||
}
|
||||
for (auto state : ir.states())
|
||||
f.print("\tnext_state.{} = {};\n", state_struct[state->name], node_name(state->next_value()));
|
||||
for (auto output : ir.outputs())
|
||||
f.print("\toutput.{} = {};\n", output_struct[output->name], node_name(output->value()));
|
||||
f.print("}}\n\n");
|
||||
}
|
||||
};
|
||||
|
||||
struct FunctionalCxxBackend : public Backend
|
||||
{
|
||||
FunctionalCxxBackend() : Backend("functional_cxx", "convert design to C++ using the functional backend") {}
|
||||
|
||||
void help() override
|
||||
{
|
||||
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
||||
log("\n");
|
||||
}
|
||||
|
||||
void printCxx(std::ostream &stream, std::string, Module *module)
|
||||
{
|
||||
CxxWriter f(stream);
|
||||
CxxModule mod(module);
|
||||
mod.write_header(f);
|
||||
mod.write_struct_def(f);
|
||||
mod.write_eval_def(f);
|
||||
mod.write_initial_def(f);
|
||||
}
|
||||
|
||||
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
|
||||
{
|
||||
log_header(design, "Executing Functional C++ backend.\n");
|
||||
|
||||
size_t argidx = 1;
|
||||
extra_args(f, filename, args, argidx, design);
|
||||
|
||||
for (auto module : design->selected_modules()) {
|
||||
log("Dumping module `%s'.\n", module->name.c_str());
|
||||
printCxx(*f, filename, module);
|
||||
}
|
||||
}
|
||||
} FunctionalCxxBackend;
|
||||
|
||||
PRIVATE_NAMESPACE_END
|
|
@ -0,0 +1,418 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
||||
* Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC
|
||||
*
|
||||
* 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 SIM_H
|
||||
#define SIM_H
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
template<size_t n>
|
||||
class Signal {
|
||||
template<size_t m> friend class Signal;
|
||||
std::array<bool, n> _bits;
|
||||
public:
|
||||
Signal() { }
|
||||
Signal(uint32_t val)
|
||||
{
|
||||
for(size_t i = 0; i < n; i++)
|
||||
if(i < 32)
|
||||
_bits[i] = val & (1<<i);
|
||||
else
|
||||
_bits[i] = false;
|
||||
}
|
||||
|
||||
Signal(std::initializer_list<uint32_t> vals)
|
||||
{
|
||||
size_t k, i;
|
||||
|
||||
k = 0;
|
||||
for (auto val : vals) {
|
||||
for(i = 0; i < 32; i++)
|
||||
if(i + k < n)
|
||||
_bits[i + k] = val & (1<<i);
|
||||
k += 32;
|
||||
}
|
||||
for(; k < n; k++)
|
||||
_bits[k] = false;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static Signal from_array(T vals)
|
||||
{
|
||||
size_t k, i;
|
||||
Signal ret;
|
||||
|
||||
k = 0;
|
||||
for (auto val : vals) {
|
||||
for(i = 0; i < 32; i++)
|
||||
if(i + k < n)
|
||||
ret._bits[i + k] = val & (1<<i);
|
||||
k += 32;
|
||||
}
|
||||
for(; k < n; k++)
|
||||
ret._bits[k] = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static Signal from_signed(int32_t val)
|
||||
{
|
||||
Signal<n> ret;
|
||||
for(size_t i = 0; i < n; i++)
|
||||
if(i < 32)
|
||||
ret._bits[i] = val & (1<<i);
|
||||
else
|
||||
ret._bits[i] = val < 0;
|
||||
return ret;
|
||||
}
|
||||
static Signal repeat(bool b)
|
||||
{
|
||||
Signal<n> ret;
|
||||
for(size_t i = 0; i < n; i++)
|
||||
ret._bits[i] = b;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int size() const { return n; }
|
||||
bool operator[](int i) const { assert(n >= 0 && i < n); return _bits[i]; }
|
||||
|
||||
template<size_t m>
|
||||
Signal<m> slice(size_t offset) const
|
||||
{
|
||||
Signal<m> ret;
|
||||
|
||||
assert(offset + m <= n);
|
||||
std::copy(_bits.begin() + offset, _bits.begin() + offset + m, ret._bits.begin());
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool any() const
|
||||
{
|
||||
for(int i = 0; i < n; i++)
|
||||
if(_bits[i])
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool all() const
|
||||
{
|
||||
for(int i = 0; i < n; i++)
|
||||
if(!_bits[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool parity() const
|
||||
{
|
||||
bool result = false;
|
||||
for(int i = 0; i < n; i++)
|
||||
result ^= _bits[i];
|
||||
return result;
|
||||
}
|
||||
|
||||
bool sign() const { return _bits[n-1]; }
|
||||
|
||||
template<typename T>
|
||||
T as_numeric() const
|
||||
{
|
||||
T ret = 0;
|
||||
for(size_t i = 0; i < std::min<size_t>(sizeof(T) * 8, n); i++)
|
||||
if(_bits[i])
|
||||
ret |= ((T)1)<<i;
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T as_numeric_clamped() const
|
||||
{
|
||||
for(size_t i = sizeof(T) * 8; i < n; i++)
|
||||
if(_bits[i])
|
||||
return ~((T)0);
|
||||
return as_numeric<T>();
|
||||
}
|
||||
|
||||
uint32_t as_int() const { return as_numeric<uint32_t>(); }
|
||||
|
||||
private:
|
||||
std::string as_string_p2(int b) const {
|
||||
std::string ret;
|
||||
for(int i = (n - 1) - (n - 1) % b; i >= 0; i -= b)
|
||||
ret += "0123456789abcdef"[(*this >> Signal<32>(i)).as_int() & ((1<<b)-1)];
|
||||
return ret;
|
||||
}
|
||||
std::string as_string_b10() const {
|
||||
std::string ret;
|
||||
if(n < 4) return std::string() + (char)('0' + as_int());
|
||||
Signal<n> t = *this;
|
||||
Signal<n> b = 10;
|
||||
do{
|
||||
ret += (char)('0' + (t % b).as_int());
|
||||
t = t / b;
|
||||
}while(t.any());
|
||||
std::reverse(ret.begin(), ret.end());
|
||||
return ret;
|
||||
}
|
||||
public:
|
||||
std::string as_string(int base = 16, bool showbase = true) const {
|
||||
std::string ret;
|
||||
if(showbase) {
|
||||
ret += std::to_string(n);
|
||||
switch(base) {
|
||||
case 2: ret += "'b"; break;
|
||||
case 8: ret += "'o"; break;
|
||||
case 10: ret += "'d"; break;
|
||||
case 16: ret += "'h"; break;
|
||||
default: assert(0);
|
||||
}
|
||||
}
|
||||
switch(base) {
|
||||
case 2: return ret + as_string_p2(1);
|
||||
case 8: return ret + as_string_p2(3);
|
||||
case 10: return ret + as_string_b10();
|
||||
case 16: return ret + as_string_p2(4);
|
||||
default: assert(0);
|
||||
}
|
||||
}
|
||||
friend std::ostream &operator << (std::ostream &os, Signal<n> const &s) { return os << s.as_string(); }
|
||||
|
||||
Signal<n> operator ~() const
|
||||
{
|
||||
Signal<n> ret;
|
||||
for(size_t i = 0; i < n; i++)
|
||||
ret._bits[i] = !_bits[i];
|
||||
return ret;
|
||||
}
|
||||
|
||||
Signal<n> operator -() const
|
||||
{
|
||||
Signal<n> ret;
|
||||
int x = 1;
|
||||
for(size_t i = 0; i < n; i++) {
|
||||
x += (int)!_bits[i];
|
||||
ret._bits[i] = (x & 1) != 0;
|
||||
x >>= 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Signal<n> operator +(Signal<n> const &b) const
|
||||
{
|
||||
Signal<n> ret;
|
||||
int x = 0;
|
||||
for(size_t i = 0; i < n; i++){
|
||||
x += (int)_bits[i] + (int)b._bits[i];
|
||||
ret._bits[i] = x & 1;
|
||||
x >>= 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Signal<n> operator -(Signal<n> const &b) const
|
||||
{
|
||||
Signal<n> ret;
|
||||
int x = 1;
|
||||
for(size_t i = 0; i < n; i++){
|
||||
x += (int)_bits[i] + (int)!b._bits[i];
|
||||
ret._bits[i] = x & 1;
|
||||
x >>= 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Signal<n> operator *(Signal<n> const &b) const
|
||||
{
|
||||
Signal<n> ret;
|
||||
int x = 0;
|
||||
for(size_t i = 0; i < n; i++){
|
||||
for(size_t j = 0; j <= i; j++)
|
||||
x += (int)_bits[j] & (int)b._bits[i-j];
|
||||
ret._bits[i] = x & 1;
|
||||
x >>= 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
Signal<n> divmod(Signal<n> const &b, bool modulo) const
|
||||
{
|
||||
if(!b.any()) return 0;
|
||||
Signal<n> q = 0;
|
||||
Signal<n> r = 0;
|
||||
for(size_t i = n; i-- != 0; ){
|
||||
r = r << Signal<1>(1);
|
||||
r._bits[0] = _bits[i];
|
||||
if(r >= b){
|
||||
r = r - b;
|
||||
q._bits[i] = true;
|
||||
}
|
||||
}
|
||||
return modulo ? r : q;
|
||||
}
|
||||
public:
|
||||
|
||||
Signal<n> operator /(Signal<n> const &b) const { return divmod(b, false); }
|
||||
Signal<n> operator %(Signal<n> const &b) const { return divmod(b, true); }
|
||||
|
||||
bool operator ==(Signal<n> const &b) const
|
||||
{
|
||||
for(size_t i = 0; i < n; i++)
|
||||
if(_bits[i] != b._bits[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator >=(Signal<n> const &b) const
|
||||
{
|
||||
for(size_t i = n; i-- != 0; )
|
||||
if(_bits[i] != b._bits[i])
|
||||
return _bits[i];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator >(Signal<n> const &b) const
|
||||
{
|
||||
for(size_t i = n; i-- != 0; )
|
||||
if(_bits[i] != b._bits[i])
|
||||
return _bits[i];
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator !=(Signal<n> const &b) const { return !(*this == b); }
|
||||
bool operator <=(Signal<n> const &b) const { return b <= *this; }
|
||||
bool operator <(Signal<n> const &b) const { return b < *this; }
|
||||
|
||||
bool signed_greater_than(Signal<n> const &b) const
|
||||
{
|
||||
if(_bits[n-1] != b._bits[n-1])
|
||||
return b._bits[n-1];
|
||||
return *this > b;
|
||||
}
|
||||
|
||||
bool signed_greater_equal(Signal<n> const &b) const
|
||||
{
|
||||
if(_bits[n-1] != b._bits[n-1])
|
||||
return b._bits[n-1];
|
||||
return *this >= b;
|
||||
}
|
||||
|
||||
Signal<n> operator &(Signal<n> const &b) const
|
||||
{
|
||||
Signal<n> ret;
|
||||
for(size_t i = 0; i < n; i++)
|
||||
ret._bits[i] = _bits[i] && b._bits[i];
|
||||
return ret;
|
||||
}
|
||||
|
||||
Signal<n> operator |(Signal<n> const &b) const
|
||||
{
|
||||
Signal<n> ret;
|
||||
for(size_t i = 0; i < n; i++)
|
||||
ret._bits[i] = _bits[i] || b._bits[i];
|
||||
return ret;
|
||||
}
|
||||
|
||||
Signal<n> operator ^(Signal<n> const &b) const
|
||||
{
|
||||
Signal<n> ret;
|
||||
for(size_t i = 0; i < n; i++)
|
||||
ret._bits[i] = _bits[i] != b._bits[i];
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<size_t nb>
|
||||
Signal<n> operator <<(Signal<nb> const &b) const
|
||||
{
|
||||
Signal<n> ret = 0;
|
||||
size_t amount = b.template as_numeric_clamped<size_t>();
|
||||
if(amount < n)
|
||||
std::copy(_bits.begin(), _bits.begin() + (n - amount), ret._bits.begin() + amount);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<size_t nb>
|
||||
Signal<n> operator >>(Signal<nb> const &b) const
|
||||
{
|
||||
Signal<n> ret = 0;
|
||||
size_t amount = b.template as_numeric_clamped<size_t>();
|
||||
if(amount < n)
|
||||
std::copy(_bits.begin() + amount, _bits.end(), ret._bits.begin());
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<size_t nb>
|
||||
Signal<n> arithmetic_shift_right(Signal<nb> const &b) const
|
||||
{
|
||||
Signal<n> ret = Signal::repeat(sign());
|
||||
size_t amount = b.template as_numeric_clamped<size_t>();
|
||||
if(amount < n)
|
||||
std::copy(_bits.begin() + amount, _bits.end(), ret._bits.begin());
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<size_t m>
|
||||
Signal<n+m> concat(Signal<m> const& b) const
|
||||
{
|
||||
Signal<n + m> ret;
|
||||
std::copy(_bits.begin(), _bits.end(), ret._bits.begin());
|
||||
std::copy(b._bits.begin(), b._bits.end(), ret._bits.begin() + n);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<size_t m>
|
||||
Signal<m> zero_extend() const
|
||||
{
|
||||
assert(m >= n);
|
||||
Signal<m> ret = 0;
|
||||
std::copy(_bits.begin(), _bits.end(), ret._bits.begin());
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<size_t m>
|
||||
Signal<m> sign_extend() const
|
||||
{
|
||||
assert(m >= n);
|
||||
Signal<m> ret = Signal<m>::repeat(sign());
|
||||
std::copy(_bits.begin(), _bits.end(), ret._bits.begin());
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
template<size_t a, size_t d>
|
||||
class Memory {
|
||||
std::array<Signal<d>, 1<<a> _contents;
|
||||
public:
|
||||
Memory() {}
|
||||
Memory(std::array<Signal<d>, 1<<a> const &contents) : _contents(contents) {}
|
||||
Signal<d> read(Signal<a> addr) const
|
||||
{
|
||||
return _contents[addr.template as_numeric<size_t>()];
|
||||
}
|
||||
Memory write(Signal<a> addr, Signal<d> data) const
|
||||
{
|
||||
Memory ret = *this;
|
||||
ret._contents[addr.template as_numeric<size_t>()] = data;
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,295 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
||||
* Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "kernel/functional.h"
|
||||
#include "kernel/yosys.h"
|
||||
#include "kernel/sexpr.h"
|
||||
#include <ctype.h>
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
using SExprUtil::list;
|
||||
|
||||
const char *reserved_keywords[] = {
|
||||
// reserved keywords from the smtlib spec
|
||||
"BINARY", "DECIMAL", "HEXADECIMAL", "NUMERAL", "STRING", "_", "!", "as", "let", "exists", "forall", "match", "par",
|
||||
"assert", "check-sat", "check-sat-assuming", "declare-const", "declare-datatype", "declare-datatypes",
|
||||
"declare-fun", "declare-sort", "define-fun", "define-fun-rec", "define-funs-rec", "define-sort",
|
||||
"exit", "get-assertions", "symbol", "sort", "get-assignment", "get-info", "get-model",
|
||||
"get-option", "get-proof", "get-unsat-assumptions", "get-unsat-core", "get-value",
|
||||
"pop", "push", "reset", "reset-assertions", "set-info", "set-logic", "set-option",
|
||||
|
||||
// reserved for our own purposes
|
||||
"pair", "Pair", "first", "second",
|
||||
"inputs", "state",
|
||||
nullptr
|
||||
};
|
||||
|
||||
struct SmtScope : public Functional::Scope<int> {
|
||||
SmtScope() {
|
||||
for(const char **p = reserved_keywords; *p != nullptr; p++)
|
||||
reserve(*p);
|
||||
}
|
||||
bool is_character_legal(char c, int index) override {
|
||||
return isascii(c) && (isalpha(c) || (isdigit(c) && index > 0) || strchr("~!@$%^&*_-+=<>.?/", c));
|
||||
}
|
||||
};
|
||||
|
||||
struct SmtSort {
|
||||
Functional::Sort sort;
|
||||
SmtSort(Functional::Sort sort) : sort(sort) {}
|
||||
SExpr to_sexpr() const {
|
||||
if(sort.is_memory()) {
|
||||
return list("Array", list("_", "BitVec", sort.addr_width()), list("_", "BitVec", sort.data_width()));
|
||||
} else if(sort.is_signal()) {
|
||||
return list("_", "BitVec", sort.width());
|
||||
} else {
|
||||
log_error("unknown sort");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SmtStruct {
|
||||
struct Field {
|
||||
SmtSort sort;
|
||||
std::string accessor;
|
||||
};
|
||||
idict<IdString> field_names;
|
||||
vector<Field> fields;
|
||||
SmtScope &scope;
|
||||
public:
|
||||
std::string name;
|
||||
SmtStruct(std::string name, SmtScope &scope) : scope(scope), name(name) {}
|
||||
void insert(IdString field_name, SmtSort sort) {
|
||||
field_names(field_name);
|
||||
auto accessor = scope.unique_name("\\" + name + "_" + RTLIL::unescape_id(field_name));
|
||||
fields.emplace_back(Field{sort, accessor});
|
||||
}
|
||||
void write_definition(SExprWriter &w) {
|
||||
w.open(list("declare-datatype", name));
|
||||
w.open(list());
|
||||
w.open(list(name));
|
||||
for(const auto &field : fields)
|
||||
w << list(field.accessor, field.sort.to_sexpr());
|
||||
w.close(3);
|
||||
}
|
||||
template<typename Fn> void write_value(SExprWriter &w, Fn fn) {
|
||||
if(field_names.empty()) {
|
||||
// Zero-argument constructors in SMTLIB must not be called as functions.
|
||||
w << name;
|
||||
} else {
|
||||
w.open(list(name));
|
||||
for(auto field_name : field_names) {
|
||||
w << fn(field_name);
|
||||
w.comment(RTLIL::unescape_id(field_name), true);
|
||||
}
|
||||
w.close();
|
||||
}
|
||||
}
|
||||
SExpr access(SExpr record, IdString name) {
|
||||
size_t i = field_names.at(name);
|
||||
return list(fields[i].accessor, std::move(record));
|
||||
}
|
||||
};
|
||||
|
||||
std::string smt_const(RTLIL::Const const &c) {
|
||||
std::string s = "#b";
|
||||
for(int i = c.size(); i-- > 0; )
|
||||
s += c[i] == State::S1 ? '1' : '0';
|
||||
return s;
|
||||
}
|
||||
|
||||
struct SmtPrintVisitor : public Functional::AbstractVisitor<SExpr> {
|
||||
using Node = Functional::Node;
|
||||
std::function<SExpr(Node)> n;
|
||||
SmtStruct &input_struct;
|
||||
SmtStruct &state_struct;
|
||||
|
||||
SmtPrintVisitor(SmtStruct &input_struct, SmtStruct &state_struct) : input_struct(input_struct), state_struct(state_struct) {}
|
||||
|
||||
SExpr from_bool(SExpr &&arg) {
|
||||
return list("ite", std::move(arg), "#b1", "#b0");
|
||||
}
|
||||
SExpr to_bool(SExpr &&arg) {
|
||||
return list("=", std::move(arg), "#b1");
|
||||
}
|
||||
SExpr extract(SExpr &&arg, int offset, int out_width = 1) {
|
||||
return list(list("_", "extract", offset + out_width - 1, offset), std::move(arg));
|
||||
}
|
||||
|
||||
SExpr buf(Node, Node a) override { return n(a); }
|
||||
SExpr slice(Node, Node a, int offset, int out_width) override { return extract(n(a), offset, out_width); }
|
||||
SExpr zero_extend(Node, Node a, int out_width) override { return list(list("_", "zero_extend", out_width - a.width()), n(a)); }
|
||||
SExpr sign_extend(Node, Node a, int out_width) override { return list(list("_", "sign_extend", out_width - a.width()), n(a)); }
|
||||
SExpr concat(Node, Node a, Node b) override { return list("concat", n(b), n(a)); }
|
||||
SExpr add(Node, Node a, Node b) override { return list("bvadd", n(a), n(b)); }
|
||||
SExpr sub(Node, Node a, Node b) override { return list("bvsub", n(a), n(b)); }
|
||||
SExpr mul(Node, Node a, Node b) override { return list("bvmul", n(a), n(b)); }
|
||||
SExpr unsigned_div(Node, Node a, Node b) override { return list("bvudiv", n(a), n(b)); }
|
||||
SExpr unsigned_mod(Node, Node a, Node b) override { return list("bvurem", n(a), n(b)); }
|
||||
SExpr bitwise_and(Node, Node a, Node b) override { return list("bvand", n(a), n(b)); }
|
||||
SExpr bitwise_or(Node, Node a, Node b) override { return list("bvor", n(a), n(b)); }
|
||||
SExpr bitwise_xor(Node, Node a, Node b) override { return list("bvxor", n(a), n(b)); }
|
||||
SExpr bitwise_not(Node, Node a) override { return list("bvnot", n(a)); }
|
||||
SExpr unary_minus(Node, Node a) override { return list("bvneg", n(a)); }
|
||||
SExpr reduce_and(Node, Node a) override { return from_bool(list("=", n(a), smt_const(RTLIL::Const(State::S1, a.width())))); }
|
||||
SExpr reduce_or(Node, Node a) override { return from_bool(list("distinct", n(a), smt_const(RTLIL::Const(State::S0, a.width())))); }
|
||||
SExpr reduce_xor(Node, Node a) override {
|
||||
vector<SExpr> s { "bvxor" };
|
||||
for(int i = 0; i < a.width(); i++)
|
||||
s.push_back(extract(n(a), i));
|
||||
return s;
|
||||
}
|
||||
SExpr equal(Node, Node a, Node b) override { return from_bool(list("=", n(a), n(b))); }
|
||||
SExpr not_equal(Node, Node a, Node b) override { return from_bool(list("distinct", n(a), n(b))); }
|
||||
SExpr signed_greater_than(Node, Node a, Node b) override { return from_bool(list("bvsgt", n(a), n(b))); }
|
||||
SExpr signed_greater_equal(Node, Node a, Node b) override { return from_bool(list("bvsge", n(a), n(b))); }
|
||||
SExpr unsigned_greater_than(Node, Node a, Node b) override { return from_bool(list("bvugt", n(a), n(b))); }
|
||||
SExpr unsigned_greater_equal(Node, Node a, Node b) override { return from_bool(list("bvuge", n(a), n(b))); }
|
||||
|
||||
SExpr extend(SExpr &&a, int in_width, int out_width) {
|
||||
if(in_width < out_width)
|
||||
return list(list("_", "zero_extend", out_width - in_width), std::move(a));
|
||||
else
|
||||
return std::move(a);
|
||||
}
|
||||
SExpr logical_shift_left(Node, Node a, Node b) override { return list("bvshl", n(a), extend(n(b), b.width(), a.width())); }
|
||||
SExpr logical_shift_right(Node, Node a, Node b) override { return list("bvlshr", n(a), extend(n(b), b.width(), a.width())); }
|
||||
SExpr arithmetic_shift_right(Node, Node a, Node b) override { return list("bvashr", n(a), extend(n(b), b.width(), a.width())); }
|
||||
SExpr mux(Node, Node a, Node b, Node s) override { return list("ite", to_bool(n(s)), n(b), n(a)); }
|
||||
SExpr constant(Node, RTLIL::Const const &value) override { return smt_const(value); }
|
||||
SExpr memory_read(Node, Node mem, Node addr) override { return list("select", n(mem), n(addr)); }
|
||||
SExpr memory_write(Node, Node mem, Node addr, Node data) override { return list("store", n(mem), n(addr), n(data)); }
|
||||
|
||||
SExpr input(Node, IdString name, IdString kind) override { log_assert(kind == ID($input)); return input_struct.access("inputs", name); }
|
||||
SExpr state(Node, IdString name, IdString kind) override { log_assert(kind == ID($state)); return state_struct.access("state", name); }
|
||||
};
|
||||
|
||||
struct SmtModule {
|
||||
Functional::IR ir;
|
||||
SmtScope scope;
|
||||
std::string name;
|
||||
|
||||
SmtStruct input_struct;
|
||||
SmtStruct output_struct;
|
||||
SmtStruct state_struct;
|
||||
|
||||
SmtModule(Module *module)
|
||||
: ir(Functional::IR::from_module(module))
|
||||
, scope()
|
||||
, name(scope.unique_name(module->name))
|
||||
, input_struct(scope.unique_name(module->name.str() + "_Inputs"), scope)
|
||||
, output_struct(scope.unique_name(module->name.str() + "_Outputs"), scope)
|
||||
, state_struct(scope.unique_name(module->name.str() + "_State"), scope)
|
||||
{
|
||||
scope.reserve(name + "-initial");
|
||||
for (auto input : ir.inputs())
|
||||
input_struct.insert(input->name, input->sort);
|
||||
for (auto output : ir.outputs())
|
||||
output_struct.insert(output->name, output->sort);
|
||||
for (auto state : ir.states())
|
||||
state_struct.insert(state->name, state->sort);
|
||||
}
|
||||
|
||||
void write_eval(SExprWriter &w)
|
||||
{
|
||||
w.push();
|
||||
w.open(list("define-fun", name,
|
||||
list(list("inputs", input_struct.name),
|
||||
list("state", state_struct.name)),
|
||||
list("Pair", output_struct.name, state_struct.name)));
|
||||
auto inlined = [&](Functional::Node n) {
|
||||
return n.fn() == Functional::Fn::constant;
|
||||
};
|
||||
SmtPrintVisitor visitor(input_struct, state_struct);
|
||||
auto node_to_sexpr = [&](Functional::Node n) -> SExpr {
|
||||
if(inlined(n))
|
||||
return n.visit(visitor);
|
||||
else
|
||||
return scope(n.id(), n.name());
|
||||
};
|
||||
visitor.n = node_to_sexpr;
|
||||
for(auto n : ir)
|
||||
if(!inlined(n)) {
|
||||
w.open(list("let", list(list(node_to_sexpr(n), n.visit(visitor)))), false);
|
||||
w.comment(SmtSort(n.sort()).to_sexpr().to_string(), true);
|
||||
}
|
||||
w.open(list("pair"));
|
||||
output_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.output(name).value()); });
|
||||
state_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.state(name).next_value()); });
|
||||
w.pop();
|
||||
}
|
||||
|
||||
void write_initial(SExprWriter &w)
|
||||
{
|
||||
std::string initial = name + "-initial";
|
||||
w << list("declare-const", initial, state_struct.name);
|
||||
for (auto state : ir.states()) {
|
||||
if(state->sort.is_signal())
|
||||
w << list("assert", list("=", state_struct.access(initial, state->name), smt_const(state->initial_value_signal())));
|
||||
else if(state->sort.is_memory()) {
|
||||
const auto &contents = state->initial_value_memory();
|
||||
for(int i = 0; i < 1<<state->sort.addr_width(); i++) {
|
||||
auto addr = smt_const(RTLIL::Const(i, state->sort.addr_width()));
|
||||
w << list("assert", list("=", list("select", state_struct.access(initial, state->name), addr), smt_const(contents[i])));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void write(std::ostream &out)
|
||||
{
|
||||
SExprWriter w(out);
|
||||
|
||||
input_struct.write_definition(w);
|
||||
output_struct.write_definition(w);
|
||||
state_struct.write_definition(w);
|
||||
|
||||
w << list("declare-datatypes",
|
||||
list(list("Pair", 2)),
|
||||
list(list("par", list("X", "Y"), list(list("pair", list("first", "X"), list("second", "Y"))))));
|
||||
|
||||
write_eval(w);
|
||||
write_initial(w);
|
||||
}
|
||||
};
|
||||
|
||||
struct FunctionalSmtBackend : public Backend {
|
||||
FunctionalSmtBackend() : Backend("functional_smt2", "Generate SMT-LIB from Functional IR") {}
|
||||
|
||||
void help() override { log("\nFunctional SMT Backend.\n\n"); }
|
||||
|
||||
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
|
||||
{
|
||||
log_header(design, "Executing Functional SMT Backend.\n");
|
||||
|
||||
size_t argidx = 1;
|
||||
extra_args(f, filename, args, argidx, design);
|
||||
|
||||
for (auto module : design->selected_modules()) {
|
||||
log("Processing module `%s`.\n", module->name.c_str());
|
||||
SmtModule smt(module);
|
||||
smt.write(*f);
|
||||
}
|
||||
}
|
||||
} FunctionalSmtBackend;
|
||||
|
||||
PRIVATE_NAMESPACE_END
|
|
@ -0,0 +1,308 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
||||
* Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "kernel/functional.h"
|
||||
#include "kernel/yosys.h"
|
||||
#include "kernel/sexpr.h"
|
||||
#include <ctype.h>
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
using SExprUtil::list;
|
||||
|
||||
const char *reserved_keywords[] = {
|
||||
// reserved keywords from the racket spec
|
||||
"struct", "lambda", "values", "extract", "concat", "bv", "let", "define", "cons", "list", "read", "write",
|
||||
"stream", "error", "raise", "exit", "for", "begin", "when", "unless", "module", "require", "provide", "apply",
|
||||
"if", "cond", "even", "odd", "any", "and", "or", "match", "command-line", "ffi-lib", "thread", "kill", "sync",
|
||||
"future", "touch", "subprocess", "make-custodian", "custodian-shutdown-all", "current-custodian", "make", "tcp",
|
||||
"connect", "prepare", "malloc", "free", "_fun", "_cprocedure", "build", "path", "file", "peek", "bytes",
|
||||
"flush", "with", "lexer", "parser", "syntax", "interface", "send", "make-object", "new", "instantiate",
|
||||
"define-generics", "set",
|
||||
|
||||
// reserved for our own purposes
|
||||
"inputs", "state", "name",
|
||||
nullptr
|
||||
};
|
||||
|
||||
struct SmtrScope : public Functional::Scope<int> {
|
||||
SmtrScope() {
|
||||
for(const char **p = reserved_keywords; *p != nullptr; p++)
|
||||
reserve(*p);
|
||||
}
|
||||
bool is_character_legal(char c, int index) override {
|
||||
return isascii(c) && (isalpha(c) || (isdigit(c) && index > 0) || strchr("@$%^&_+=.", c));
|
||||
}
|
||||
};
|
||||
|
||||
struct SmtrSort {
|
||||
Functional::Sort sort;
|
||||
SmtrSort(Functional::Sort sort) : sort(sort) {}
|
||||
SExpr to_sexpr() const {
|
||||
if(sort.is_memory()) {
|
||||
return list("list", list("bitvector", sort.addr_width()), list("bitvector", sort.data_width()));
|
||||
} else if(sort.is_signal()) {
|
||||
return list("bitvector", sort.width());
|
||||
} else {
|
||||
log_error("unknown sort");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SmtrStruct {
|
||||
struct Field {
|
||||
SmtrSort sort;
|
||||
std::string accessor;
|
||||
std::string name;
|
||||
};
|
||||
idict<IdString> field_names;
|
||||
vector<Field> fields;
|
||||
SmtrScope &global_scope;
|
||||
SmtrScope local_scope;
|
||||
public:
|
||||
std::string name;
|
||||
SmtrStruct(std::string name, SmtrScope &scope) : global_scope(scope), local_scope(), name(name) {}
|
||||
void insert(IdString field_name, SmtrSort sort) {
|
||||
field_names(field_name);
|
||||
auto base_name = local_scope.unique_name(field_name);
|
||||
auto accessor = name + "-" + base_name;
|
||||
global_scope.reserve(accessor);
|
||||
fields.emplace_back(Field{sort, accessor, base_name});
|
||||
}
|
||||
void write_definition(SExprWriter &w) {
|
||||
vector<SExpr> field_list;
|
||||
for(const auto &field : fields) {
|
||||
field_list.emplace_back(field.name);
|
||||
}
|
||||
w.push();
|
||||
w.open(list("struct", name, field_list, "#:transparent"));
|
||||
if (field_names.size()) {
|
||||
for (const auto &field : fields) {
|
||||
auto bv_type = field.sort.to_sexpr();
|
||||
w.comment(field.name + " " + bv_type.to_string());
|
||||
}
|
||||
}
|
||||
w.pop();
|
||||
}
|
||||
template<typename Fn> void write_value(SExprWriter &w, Fn fn) {
|
||||
w.open(list(name));
|
||||
for(auto field_name : field_names) {
|
||||
w << fn(field_name);
|
||||
w.comment(RTLIL::unescape_id(field_name), true);
|
||||
}
|
||||
w.close();
|
||||
}
|
||||
SExpr access(SExpr record, IdString name) {
|
||||
size_t i = field_names.at(name);
|
||||
return list(fields[i].accessor, std::move(record));
|
||||
}
|
||||
};
|
||||
|
||||
std::string smt_const(RTLIL::Const const &c) {
|
||||
std::string s = "#b";
|
||||
for(int i = c.size(); i-- > 0; )
|
||||
s += c[i] == State::S1 ? '1' : '0';
|
||||
return s;
|
||||
}
|
||||
|
||||
struct SmtrPrintVisitor : public Functional::AbstractVisitor<SExpr> {
|
||||
using Node = Functional::Node;
|
||||
std::function<SExpr(Node)> n;
|
||||
SmtrStruct &input_struct;
|
||||
SmtrStruct &state_struct;
|
||||
|
||||
SmtrPrintVisitor(SmtrStruct &input_struct, SmtrStruct &state_struct) : input_struct(input_struct), state_struct(state_struct) {}
|
||||
|
||||
SExpr from_bool(SExpr &&arg) {
|
||||
return list("bool->bitvector", std::move(arg));
|
||||
}
|
||||
SExpr to_bool(SExpr &&arg) {
|
||||
return list("bitvector->bool", std::move(arg));
|
||||
}
|
||||
SExpr to_list(SExpr &&arg) {
|
||||
return list("bitvector->bits", std::move(arg));
|
||||
}
|
||||
|
||||
SExpr buf(Node, Node a) override { return n(a); }
|
||||
SExpr slice(Node, Node a, int offset, int out_width) override { return list("extract", offset + out_width - 1, offset, n(a)); }
|
||||
SExpr zero_extend(Node, Node a, int out_width) override { return list("zero-extend", n(a), list("bitvector", out_width)); }
|
||||
SExpr sign_extend(Node, Node a, int out_width) override { return list("sign-extend", n(a), list("bitvector", out_width)); }
|
||||
SExpr concat(Node, Node a, Node b) override { return list("concat", n(b), n(a)); }
|
||||
SExpr add(Node, Node a, Node b) override { return list("bvadd", n(a), n(b)); }
|
||||
SExpr sub(Node, Node a, Node b) override { return list("bvsub", n(a), n(b)); }
|
||||
SExpr mul(Node, Node a, Node b) override { return list("bvmul", n(a), n(b)); }
|
||||
SExpr unsigned_div(Node, Node a, Node b) override { return list("bvudiv", n(a), n(b)); }
|
||||
SExpr unsigned_mod(Node, Node a, Node b) override { return list("bvurem", n(a), n(b)); }
|
||||
SExpr bitwise_and(Node, Node a, Node b) override { return list("bvand", n(a), n(b)); }
|
||||
SExpr bitwise_or(Node, Node a, Node b) override { return list("bvor", n(a), n(b)); }
|
||||
SExpr bitwise_xor(Node, Node a, Node b) override { return list("bvxor", n(a), n(b)); }
|
||||
SExpr bitwise_not(Node, Node a) override { return list("bvnot", n(a)); }
|
||||
SExpr unary_minus(Node, Node a) override { return list("bvneg", n(a)); }
|
||||
SExpr reduce_and(Node, Node a) override { return list("apply", "bvand", to_list(n(a))); }
|
||||
SExpr reduce_or(Node, Node a) override { return list("apply", "bvor", to_list(n(a))); }
|
||||
SExpr reduce_xor(Node, Node a) override { return list("apply", "bvxor", to_list(n(a))); }
|
||||
SExpr equal(Node, Node a, Node b) override { return from_bool(list("bveq", n(a), n(b))); }
|
||||
SExpr not_equal(Node, Node a, Node b) override { return from_bool(list("not", list("bveq", n(a), n(b)))); }
|
||||
SExpr signed_greater_than(Node, Node a, Node b) override { return from_bool(list("bvsgt", n(a), n(b))); }
|
||||
SExpr signed_greater_equal(Node, Node a, Node b) override { return from_bool(list("bvsge", n(a), n(b))); }
|
||||
SExpr unsigned_greater_than(Node, Node a, Node b) override { return from_bool(list("bvugt", n(a), n(b))); }
|
||||
SExpr unsigned_greater_equal(Node, Node a, Node b) override { return from_bool(list("bvuge", n(a), n(b))); }
|
||||
|
||||
SExpr extend(SExpr &&a, int in_width, int out_width) {
|
||||
if(in_width < out_width)
|
||||
return list("zero-extend", std::move(a), list("bitvector", out_width));
|
||||
else
|
||||
return std::move(a);
|
||||
}
|
||||
SExpr logical_shift_left(Node, Node a, Node b) override { return list("bvshl", n(a), extend(n(b), b.width(), a.width())); }
|
||||
SExpr logical_shift_right(Node, Node a, Node b) override { return list("bvlshr", n(a), extend(n(b), b.width(), a.width())); }
|
||||
SExpr arithmetic_shift_right(Node, Node a, Node b) override { return list("bvashr", n(a), extend(n(b), b.width(), a.width())); }
|
||||
SExpr mux(Node, Node a, Node b, Node s) override { return list("if", to_bool(n(s)), n(b), n(a)); }
|
||||
SExpr constant(Node, RTLIL::Const const& value) override { return list("bv", smt_const(value), value.size()); }
|
||||
SExpr memory_read(Node, Node mem, Node addr) override { return list("list-ref-bv", n(mem), n(addr)); }
|
||||
SExpr memory_write(Node, Node mem, Node addr, Node data) override { return list("list-set-bv", n(mem), n(addr), n(data)); }
|
||||
|
||||
SExpr input(Node, IdString name, IdString kind) override { log_assert(kind == ID($input)); return input_struct.access("inputs", name); }
|
||||
SExpr state(Node, IdString name, IdString kind) override { log_assert(kind == ID($state)); return state_struct.access("state", name); }
|
||||
};
|
||||
|
||||
struct SmtrModule {
|
||||
Functional::IR ir;
|
||||
SmtrScope scope;
|
||||
std::string name;
|
||||
|
||||
SmtrStruct input_struct;
|
||||
SmtrStruct output_struct;
|
||||
SmtrStruct state_struct;
|
||||
|
||||
SmtrModule(Module *module)
|
||||
: ir(Functional::IR::from_module(module))
|
||||
, scope()
|
||||
, name(scope.unique_name(module->name))
|
||||
, input_struct(scope.unique_name(module->name.str() + "_Inputs"), scope)
|
||||
, output_struct(scope.unique_name(module->name.str() + "_Outputs"), scope)
|
||||
, state_struct(scope.unique_name(module->name.str() + "_State"), scope)
|
||||
{
|
||||
scope.reserve(name + "_initial");
|
||||
for (auto input : ir.inputs())
|
||||
input_struct.insert(input->name, input->sort);
|
||||
for (auto output : ir.outputs())
|
||||
output_struct.insert(output->name, output->sort);
|
||||
for (auto state : ir.states())
|
||||
state_struct.insert(state->name, state->sort);
|
||||
}
|
||||
|
||||
void write(std::ostream &out)
|
||||
{
|
||||
SExprWriter w(out);
|
||||
|
||||
input_struct.write_definition(w);
|
||||
output_struct.write_definition(w);
|
||||
state_struct.write_definition(w);
|
||||
|
||||
w.push();
|
||||
w.open(list("define", list(name, "inputs", "state")));
|
||||
auto inlined = [&](Functional::Node n) {
|
||||
return n.fn() == Functional::Fn::constant;
|
||||
};
|
||||
SmtrPrintVisitor visitor(input_struct, state_struct);
|
||||
auto node_to_sexpr = [&](Functional::Node n) -> SExpr {
|
||||
if(inlined(n))
|
||||
return n.visit(visitor);
|
||||
else
|
||||
return scope(n.id(), n.name());
|
||||
};
|
||||
visitor.n = node_to_sexpr;
|
||||
for(auto n : ir)
|
||||
if(!inlined(n)) {
|
||||
w.open(list("let", list(list(node_to_sexpr(n), n.visit(visitor)))), false);
|
||||
w.comment(SmtrSort(n.sort()).to_sexpr().to_string(), true);
|
||||
}
|
||||
w.open(list("cons"));
|
||||
output_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.output(name).value()); });
|
||||
state_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.state(name).next_value()); });
|
||||
w.pop();
|
||||
|
||||
w.push();
|
||||
auto initial = name + "_initial";
|
||||
w.open(list("define", initial));
|
||||
w.open(list(state_struct.name));
|
||||
for (auto state : ir.states()) {
|
||||
if (state->sort.is_signal())
|
||||
w << list("bv", smt_const(state->initial_value_signal()), state->sort.width());
|
||||
else if (state->sort.is_memory()) {
|
||||
const auto &contents = state->initial_value_memory();
|
||||
w.open(list("list"));
|
||||
for(int i = 0; i < 1<<state->sort.addr_width(); i++) {
|
||||
w << list("bv", smt_const(contents[i]), state->sort.data_width());
|
||||
}
|
||||
w.close();
|
||||
}
|
||||
}
|
||||
w.pop();
|
||||
}
|
||||
};
|
||||
|
||||
struct FunctionalSmtrBackend : public Backend {
|
||||
FunctionalSmtrBackend() : Backend("functional_rosette", "Generate Rosette compatible Racket from Functional IR") {}
|
||||
|
||||
void help() override {
|
||||
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
||||
log("\n");
|
||||
log(" write_functional_rosette [options] [selection] [filename]\n");
|
||||
log("\n");
|
||||
log("Functional Rosette Backend.\n");
|
||||
log("\n");
|
||||
log(" -provides\n");
|
||||
log(" include 'provide' statement(s) for loading output as a module\n");
|
||||
log("\n");
|
||||
}
|
||||
|
||||
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
|
||||
{
|
||||
auto provides = false;
|
||||
|
||||
log_header(design, "Executing Functional Rosette Backend.\n");
|
||||
|
||||
size_t argidx;
|
||||
for (argidx = 1; argidx < args.size(); argidx++)
|
||||
{
|
||||
if (args[argidx] == "-provides")
|
||||
provides = true;
|
||||
else
|
||||
break;
|
||||
}
|
||||
extra_args(f, filename, args, argidx);
|
||||
|
||||
*f << "#lang rosette/safe\n";
|
||||
if (provides) {
|
||||
*f << "(provide (all-defined-out))\n";
|
||||
}
|
||||
|
||||
for (auto module : design->selected_modules()) {
|
||||
log("Processing module `%s`.\n", module->name.c_str());
|
||||
SmtrModule smtr(module);
|
||||
smtr.write(*f);
|
||||
}
|
||||
}
|
||||
} FunctionalSmtrBackend;
|
||||
|
||||
PRIVATE_NAMESPACE_END
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
||||
* Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "kernel/yosys.h"
|
||||
#include "kernel/functional.h"
|
||||
#include <random>
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
struct MemContentsTest {
|
||||
int addr_width, data_width;
|
||||
MemContents state;
|
||||
using addr_t = MemContents::addr_t;
|
||||
std::map<addr_t, RTLIL::Const> reference;
|
||||
MemContentsTest(int addr_width, int data_width) : addr_width(addr_width), data_width(data_width), state(addr_width, data_width, RTLIL::Const(State::S0, data_width)) {}
|
||||
void check() {
|
||||
state.check();
|
||||
for(auto addr = 0; addr < (1<<addr_width); addr++) {
|
||||
auto it = reference.find(addr);
|
||||
if(it != reference.end()) {
|
||||
if(state.count_range(addr, addr + 1) != 1) goto error;
|
||||
if(it->second != state[addr]) goto error;
|
||||
} else {
|
||||
if(state.count_range(addr, addr + 1) != 0) goto error;
|
||||
}
|
||||
}
|
||||
return;
|
||||
error:
|
||||
printf("FAIL\n");
|
||||
int digits = (data_width + 3) / 4;
|
||||
|
||||
for(auto addr = 0; addr < (1<<addr_width); addr++) {
|
||||
if(addr % 8 == 0) printf("%.8x ", addr);
|
||||
auto it = reference.find(addr);
|
||||
bool ref_def = it != reference.end();
|
||||
RTLIL::Const ref_value = ref_def ? it->second : state.default_value();
|
||||
std::string ref_string = stringf("%.*x", digits, ref_value.as_int());
|
||||
bool sta_def = state.count_range(addr, addr + 1) == 1;
|
||||
RTLIL::Const sta_value = state[addr];
|
||||
std::string sta_string = stringf("%.*x", digits, sta_value.as_int());
|
||||
if(ref_def && sta_def) {
|
||||
if(ref_value == sta_value) printf("%s%s", ref_string.c_str(), string(digits, ' ').c_str());
|
||||
else printf("%s%s", ref_string.c_str(), sta_string.c_str());
|
||||
} else if(ref_def) {
|
||||
printf("%s%s", ref_string.c_str(), string(digits, 'M').c_str());
|
||||
} else if(sta_def) {
|
||||
printf("%s%s", sta_string.c_str(), string(digits, 'X').c_str());
|
||||
} else {
|
||||
printf("%s", string(2*digits, ' ').c_str());
|
||||
}
|
||||
printf(" ");
|
||||
if(addr % 8 == 7) printf("\n");
|
||||
}
|
||||
printf("\n");
|
||||
//log_abort();
|
||||
}
|
||||
void clear_range(addr_t begin_addr, addr_t end_addr) {
|
||||
for(auto addr = begin_addr; addr != end_addr; addr++)
|
||||
reference.erase(addr);
|
||||
state.clear_range(begin_addr, end_addr);
|
||||
check();
|
||||
}
|
||||
void insert_concatenated(addr_t addr, RTLIL::Const const &values) {
|
||||
addr_t words = ((addr_t) values.size() + data_width - 1) / data_width;
|
||||
for(addr_t i = 0; i < words; i++) {
|
||||
reference.erase(addr + i);
|
||||
reference.emplace(addr + i, values.extract(i * data_width, data_width));
|
||||
}
|
||||
state.insert_concatenated(addr, values);
|
||||
check();
|
||||
}
|
||||
template<typename Rnd> void run(Rnd &rnd, int n) {
|
||||
std::uniform_int_distribution<addr_t> addr_dist(0, (1<<addr_width) - 1);
|
||||
std::poisson_distribution<addr_t> length_dist(10);
|
||||
std::uniform_int_distribution<uint64_t> data_dist(0, ((uint64_t)1<<data_width) - 1);
|
||||
while(n-- > 0) {
|
||||
addr_t low = addr_dist(rnd);
|
||||
//addr_t length = std::min((1<<addr_width) - low, length_dist(rnd));
|
||||
//addr_t high = low + length - 1;
|
||||
addr_t high = addr_dist(rnd);
|
||||
if(low > high) std::swap(low, high);
|
||||
if((rnd() & 7) == 0) {
|
||||
log_debug("clear %.2x to %.2x\n", (int)low, (int)high);
|
||||
clear_range(low, high + 1);
|
||||
} else {
|
||||
log_debug("insert %.2x to %.2x\n", (int)low, (int)high);
|
||||
RTLIL::Const values;
|
||||
for(addr_t addr = low; addr <= high; addr++) {
|
||||
RTLIL::Const word(data_dist(rnd), data_width);
|
||||
values.bits.insert(values.bits.end(), word.bits.begin(), word.bits.end());
|
||||
}
|
||||
insert_concatenated(low, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct FunctionalTestGeneric : public Pass
|
||||
{
|
||||
FunctionalTestGeneric() : Pass("test_generic", "test the generic compute graph") {}
|
||||
|
||||
void help() override
|
||||
{
|
||||
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
||||
log("\n");
|
||||
}
|
||||
|
||||
void execute(std::vector<std::string> args, RTLIL::Design *design) override
|
||||
{
|
||||
log_header(design, "Executing Test Generic.\n");
|
||||
|
||||
size_t argidx = 1;
|
||||
extra_args(args, argidx, design);
|
||||
/*
|
||||
MemContentsTest test(8, 16);
|
||||
|
||||
std::random_device seed_dev;
|
||||
std::mt19937 rnd(23); //seed_dev());
|
||||
test.run(rnd, 1000);
|
||||
*/
|
||||
|
||||
for (auto module : design->selected_modules()) {
|
||||
log("Dumping module `%s'.\n", module->name.c_str());
|
||||
auto fir = Functional::IR::from_module(module);
|
||||
for(auto node : fir)
|
||||
std::cout << RTLIL::unescape_id(node.name()) << " = " << node.to_string([](auto n) { return RTLIL::unescape_id(n.name()); }) << "\n";
|
||||
for(auto output : fir.all_outputs())
|
||||
std::cout << RTLIL::unescape_id(output->kind) << " " << RTLIL::unescape_id(output->name) << " = " << RTLIL::unescape_id(output->value().name()) << "\n";
|
||||
for(auto state : fir.all_states())
|
||||
std::cout << RTLIL::unescape_id(state->kind) << " " << RTLIL::unescape_id(state->name) << " = " << RTLIL::unescape_id(state->next_value().name()) << "\n";
|
||||
}
|
||||
}
|
||||
} FunctionalCxxBackend;
|
||||
|
||||
PRIVATE_NAMESPACE_END
|
|
@ -0,0 +1,94 @@
|
|||
Writing a new backend using FunctionalIR
|
||||
===========================================
|
||||
|
||||
To simplify the writing of backends for functional languages or similar targets, Yosys provides an alternative intermediate representation called FunctionalIR which maps more directly on those targets.
|
||||
|
||||
FunctionalIR represents the design as a function ``(inputs, current_state) -> (outputs, next_state)``.
|
||||
This function is broken down into a series of assignments to variables.
|
||||
Each assignment is a simple operation, such as an addition.
|
||||
Complex operations are broken up into multiple steps.
|
||||
For example, an RTLIL addition will be translated into a sign/zero extension of the inputs, followed by an addition.
|
||||
|
||||
Like SSA form, each variable is assigned to exactly once.
|
||||
We can thus treat variables and assignments as equivalent and, since this is a graph-like representation, those variables are also called "nodes".
|
||||
Unlike RTLIL's cells and wires representation, this representation is strictly ordered (topologically sorted) with definitions preceding their use.
|
||||
|
||||
Every node has a "sort" (the FunctionalIR term for what might otherwise be called a "type"). The sorts available are
|
||||
|
||||
- ``bit[n]`` for an ``n``-bit bitvector, and
|
||||
- ``memory[n,m]`` for an immutable array of ``2**n`` values of sort ``bit[m]``.
|
||||
|
||||
In terms of actual code, Yosys provides a class ``Functional::IR`` that represents a design in FunctionalIR.
|
||||
``Functional::IR::from_module`` generates an instance from an RTLIL module.
|
||||
The entire design is stored as a whole in an internal data structure.
|
||||
To access the design, the ``Functional::Node`` class provides a reference to a particular node in the design.
|
||||
The ``Functional::IR`` class supports the syntax ``for(auto node : ir)`` to iterate over every node.
|
||||
|
||||
``Functional::IR`` also keeps track of inputs, outputs and states.
|
||||
By a "state" we mean a pair of a "current state" input and a "next state" output.
|
||||
One such pair is created for every register and for every memory.
|
||||
Every input, output and state has a name (equal to their name in RTLIL), a sort and a kind.
|
||||
The kind field usually remains as the default value ``$input``, ``$output`` or ``$state``, however some RTLIL cells such as ``$assert`` or ``$anyseq`` generate auxiliary inputs/outputs/states that are given a different kind to distinguish them from ordinary RTLIL inputs/outputs/states.
|
||||
|
||||
- To access an individual input/output/state, use ``ir.input(name, kind)``, ``ir.output(name, kind)`` or ``ir.state(name, kind)``. ``kind`` defaults to the default kind.
|
||||
- To iterate over all inputs/outputs/states of a certain kind, methods ``ir.inputs``, ``ir.outputs``, ``ir.states`` are provided. Their argument defaults to the default kinds mentioned.
|
||||
- To iterate over inputs/outputs/states of any kind, use ``ir.all_inputs``, ``ir.all_outputs`` and ``ir.all_states``.
|
||||
- Outputs have a node that indicate the value of the output, this can be retrieved via ``output.value()``.
|
||||
- States have a node that indicate the next value of the state, this can be retrieved via ``state.next_value()``.
|
||||
They also have an initial value that is accessed as either ``state.initial_value_signal()`` or ``state.initial_value_memory()``, depending on their sort.
|
||||
|
||||
Each node has a "function", which defines its operation (for a complete list of functions and a specification of their operation, see ``functional.h``).
|
||||
Functions are represented as an enum ``Functional::Fn`` and the function field can be accessed as ``node.fn()``.
|
||||
Since the most common operation is a switch over the function that also accesses the arguments, the ``Node`` class provides a method ``visit`` that implements the visitor pattern.
|
||||
For example, for an addition node ``node`` with arguments ``n1`` and ``n2``, ``node.visit(visitor)`` would call ``visitor.add(node, n1, n2)``.
|
||||
Thus typically one would implement a class with a method for every function.
|
||||
Visitors should inherit from either ``Functional::AbstractVisitor<ReturnType>`` or ``Functional::DefaultVisitor<ReturnType>``.
|
||||
The former will produce a compiler error if a case is unhandled, the latter will call ``default_handler(node)`` instead.
|
||||
Visitor methods should be marked as ``override`` to provide compiler errors if the arguments are wrong.
|
||||
|
||||
Utility classes
|
||||
-----------------
|
||||
|
||||
``functional.h`` also provides utility classes that are independent of the main FunctionalIR representation but are likely to be useful for backends.
|
||||
|
||||
``Functional::Writer`` provides a simple formatting class that wraps a ``std::ostream`` and provides the following methods:
|
||||
|
||||
- ``writer << value`` wraps ``os << value``.
|
||||
- ``writer.print(fmt, value0, value1, value2, ...)`` replaces ``{0}``, ``{1}``, ``{2}``, etc in the string ``fmt`` with ``value0``, ``value1``, ``value2``, resp.
|
||||
Each value is formatted using ``os << value``.
|
||||
It is also possible to write ``{}`` to refer to one past the last index, i.e. ``{1} {} {} {7} {}`` is equivalent to ``{1} {2} {3} {7} {8}``.
|
||||
- ``writer.print_with(fn, fmt, value0, value1, value2, ...)`` functions much the same as ``print`` but it uses ``os << fn(value)`` to print each value and falls back to ``os << value`` if ``fn(value)`` is not legal.
|
||||
|
||||
``Functional::Scope`` keeps track of variable names in a target language.
|
||||
It is used to translate between different sets of legal characters and to avoid accidentally re-defining identifiers.
|
||||
Users should derive a class from ``Scope`` and supply the following:
|
||||
|
||||
- ``Scope<Id>`` takes a template argument that specifies a type that's used to uniquely distinguish variables.
|
||||
Typically this would be ``int`` (if variables are used for ``Functional::IR`` nodes) or ``IdString``.
|
||||
- The derived class should provide a constructor that calls ``reserve`` for every reserved word in the target language.
|
||||
- A method ``bool is_legal_character(char c, int index)`` has to be provided that returns ``true`` iff ``c`` is legal in an identifier at position ``index``.
|
||||
|
||||
Given an instance ``scope`` of the derived class, the following methods are then available:
|
||||
|
||||
- ``scope.reserve(std::string name)`` marks the given name as being in-use
|
||||
- ``scope.unique_name(IdString suggestion)`` generates a previously unused name and attempts to make it similar to ``suggestion``.
|
||||
- ``scope(Id id, IdString suggestion)`` functions similar to ``unique_name``, except that multiple calls with the same ``id`` are guaranteed to retrieve the same name (independent of ``suggestion``).
|
||||
|
||||
``sexpr.h`` provides classes that represent and pretty-print s-expressions.
|
||||
S-expressions can be constructed with ``SExpr::list``, for example ``SExpr expr = SExpr::list("add", "x", SExpr::list("mul", "y", "z"))`` represents ``(add x (mul y z))``
|
||||
(by adding ``using SExprUtil::list`` to the top of the file, ``list`` can be used as shorthand for ``SExpr::list``).
|
||||
For prettyprinting, ``SExprWriter`` wraps an ``std::ostream`` and provides the following methods:
|
||||
|
||||
- ``writer << sexpr`` writes the provided expression to the output, breaking long lines and adding appropriate indentation.
|
||||
- ``writer.open(sexpr)`` is similar to ``writer << sexpr`` but will omit the last closing parenthesis.
|
||||
Further arguments can then be added separately with ``<<`` or ``open``.
|
||||
This allows for printing large s-expressions without needing to construct the whole expression in memory first.
|
||||
- ``writer.open(sexpr, false)`` is similar to ``writer.open(sexpr)`` but further arguments will not be indented.
|
||||
This is used to avoid unlimited indentation on structures with unlimited nesting.
|
||||
- ``writer.close(n = 1)`` closes the last ``n`` open s-expressions.
|
||||
- ``writer.push()`` and ``writer.pop()`` are used to automatically close s-expressions.
|
||||
``writer.pop()`` closes all s-expressions opened since the last call to ``writer.push()``.
|
||||
- ``writer.comment(string)`` writes a comment on a separate-line.
|
||||
``writer.comment(string, true)`` appends a comment to the last printed s-expression.
|
||||
- ``writer.flush()`` flushes any buffering and should be called before any direct access to the underlying ``std::ostream``. It does not close unclosed parentheses.
|
||||
- The destructor calls ``flush`` but also closes all unclosed parentheses.
|
|
@ -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
|
|
@ -0,0 +1,949 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "kernel/drivertools.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
DriveBit::DriveBit(SigBit const &bit)
|
||||
{
|
||||
if (bit.is_wire())
|
||||
*this = DriveBitWire(bit.wire, bit.offset);
|
||||
else
|
||||
*this = bit.data;
|
||||
}
|
||||
|
||||
void DriveBit::merge(DriveBit const &other)
|
||||
{
|
||||
if (other.type_ == DriveType::NONE)
|
||||
return;
|
||||
if (type_ == DriveType::NONE) {
|
||||
*this = other;
|
||||
return;
|
||||
}
|
||||
if (type_ != DriveType::MULTIPLE) {
|
||||
DriveBitMultiple multi(std::move(*this));
|
||||
*this = std::move(multi);
|
||||
}
|
||||
multiple().merge(other);
|
||||
}
|
||||
|
||||
|
||||
void DriveBitMultiple::merge(DriveBit const &single)
|
||||
{
|
||||
if (single.type() == DriveType::NONE)
|
||||
return;
|
||||
if (single.type() == DriveType::MULTIPLE) {
|
||||
merge(single.multiple());
|
||||
return;
|
||||
}
|
||||
multiple_.emplace(single);
|
||||
}
|
||||
|
||||
void DriveBitMultiple::merge(DriveBit &&single)
|
||||
{
|
||||
if (single.type() == DriveType::NONE)
|
||||
return;
|
||||
if (single.type() == DriveType::MULTIPLE) {
|
||||
merge(std::move(single.multiple()));
|
||||
return;
|
||||
}
|
||||
multiple_.emplace(std::move(single));
|
||||
}
|
||||
|
||||
DriveBitMultiple DriveChunkMultiple::operator[](int i) const
|
||||
{
|
||||
DriveBitMultiple result;
|
||||
for (auto const &single : multiple_)
|
||||
result.merge(single[i]);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool DriveChunkWire::can_append(DriveBitWire const &bit) const
|
||||
{
|
||||
return bit.wire == wire && bit.offset == offset + width;
|
||||
}
|
||||
|
||||
bool DriveChunkWire::try_append(DriveBitWire const &bit)
|
||||
{
|
||||
if (!can_append(bit))
|
||||
return false;
|
||||
width += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DriveChunkWire::try_append(DriveChunkWire const &chunk)
|
||||
{
|
||||
if (chunk.wire != wire || chunk.offset != offset + width)
|
||||
return false;
|
||||
width += chunk.width;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DriveChunkPort::can_append(DriveBitPort const &bit) const
|
||||
{
|
||||
return bit.cell == cell && bit.port == port && bit.offset == offset + width;
|
||||
}
|
||||
|
||||
bool DriveChunkPort::try_append(DriveBitPort const &bit)
|
||||
{
|
||||
if (!can_append(bit))
|
||||
return false;
|
||||
width += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DriveChunkPort::try_append(DriveChunkPort const &chunk)
|
||||
{
|
||||
if (chunk.cell != cell || chunk.port != port || chunk.offset != offset + width)
|
||||
return false;
|
||||
width += chunk.width;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DriveChunkMarker::can_append(DriveBitMarker const &bit) const
|
||||
{
|
||||
return bit.marker == marker && bit.offset == offset + width;
|
||||
}
|
||||
|
||||
bool DriveChunkMarker::try_append(DriveBitMarker const &bit)
|
||||
{
|
||||
if (!can_append(bit))
|
||||
return false;
|
||||
width += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DriveChunkMarker::try_append(DriveChunkMarker const &chunk)
|
||||
{
|
||||
if (chunk.marker != marker || chunk.offset != offset + width)
|
||||
return false;
|
||||
width += chunk.width;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool DriveChunkMultiple::can_append(DriveBitMultiple const &bit) const
|
||||
{
|
||||
if (bit.multiple().size() != multiple_.size())
|
||||
return false;
|
||||
|
||||
int const_drivers = 0;
|
||||
for (DriveChunk const &single : multiple_)
|
||||
if (single.is_constant())
|
||||
const_drivers += 1;
|
||||
|
||||
if (const_drivers > 1)
|
||||
return false;
|
||||
|
||||
for (DriveBit const &single : bit.multiple())
|
||||
if (single.is_constant())
|
||||
const_drivers -= 1;
|
||||
|
||||
if (const_drivers != 0)
|
||||
return false;
|
||||
|
||||
for (DriveChunk const &single : multiple_)
|
||||
{
|
||||
switch (single.type())
|
||||
{
|
||||
case DriveType::CONSTANT: {
|
||||
} break;
|
||||
case DriveType::WIRE: {
|
||||
auto const &wire = single.wire();
|
||||
DriveBit next = DriveBitWire(wire.wire, wire.offset + wire.width);
|
||||
if (!bit.multiple().count(next))
|
||||
return false;
|
||||
} break;
|
||||
case DriveType::PORT: {
|
||||
auto const &port = single.port();
|
||||
DriveBit next = DriveBitPort(port.cell, port.port, port.offset + port.width);
|
||||
if (!bit.multiple().count(next))
|
||||
return false;
|
||||
} break;
|
||||
case DriveType::MARKER: {
|
||||
auto const &marker = single.marker();
|
||||
DriveBit next = DriveBitMarker(marker.marker, marker.offset + marker.width);
|
||||
if (!bit.multiple().count(next))
|
||||
return false;
|
||||
} break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DriveChunkMultiple::can_append(DriveChunkMultiple const &chunk) const
|
||||
{
|
||||
if (chunk.multiple().size() != multiple_.size())
|
||||
return false;
|
||||
|
||||
int const_drivers = 0;
|
||||
for (DriveChunk const &single : multiple_)
|
||||
if (single.is_constant())
|
||||
const_drivers += 1;
|
||||
|
||||
if (const_drivers > 1)
|
||||
return false;
|
||||
|
||||
for (DriveChunk const &single : chunk.multiple())
|
||||
if (single.is_constant())
|
||||
const_drivers -= 1;
|
||||
|
||||
if (const_drivers != 0)
|
||||
return false;
|
||||
|
||||
for (DriveChunk const &single : multiple_)
|
||||
{
|
||||
switch (single.type())
|
||||
{
|
||||
case DriveType::CONSTANT: {
|
||||
} break;
|
||||
case DriveType::WIRE: {
|
||||
auto const &wire = single.wire();
|
||||
DriveChunk next = DriveChunkWire(wire.wire, wire.offset + wire.width, chunk.size());
|
||||
if (!chunk.multiple().count(next))
|
||||
return false;
|
||||
} break;
|
||||
case DriveType::PORT: {
|
||||
auto const &port = single.port();
|
||||
DriveChunk next = DriveChunkPort(port.cell, port.port, port.offset + port.width, chunk.size());
|
||||
if (!chunk.multiple().count(next))
|
||||
return false;
|
||||
} break;
|
||||
case DriveType::MARKER: {
|
||||
auto const &marker = single.marker();
|
||||
DriveChunk next = DriveChunkMarker(marker.marker, marker.offset + marker.width, chunk.size());
|
||||
if (!chunk.multiple().count(next))
|
||||
return false;
|
||||
} break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DriveChunkMultiple::try_append(DriveBitMultiple const &bit)
|
||||
{
|
||||
if (!can_append(bit))
|
||||
return false;
|
||||
width_ += 1;
|
||||
State constant;
|
||||
|
||||
for (DriveBit const &single : bit.multiple())
|
||||
if (single.is_constant())
|
||||
constant = single.constant();
|
||||
|
||||
for (DriveChunk &single : multiple_)
|
||||
{
|
||||
switch (single.type())
|
||||
{
|
||||
case DriveType::CONSTANT: {
|
||||
single.constant().bits.push_back(constant);
|
||||
} break;
|
||||
case DriveType::WIRE: {
|
||||
single.wire().width += 1;
|
||||
} break;
|
||||
case DriveType::PORT: {
|
||||
single.port().width += 1;
|
||||
} break;
|
||||
case DriveType::MARKER: {
|
||||
single.marker().width += 1;
|
||||
} break;
|
||||
default:
|
||||
log_abort();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DriveChunkMultiple::try_append(DriveChunkMultiple const &chunk)
|
||||
{
|
||||
if (!can_append(chunk))
|
||||
return false;
|
||||
int width = chunk.size();
|
||||
width_ += width;
|
||||
Const constant;
|
||||
|
||||
for (DriveChunk const &single : chunk.multiple())
|
||||
if (single.is_constant())
|
||||
constant = single.constant();
|
||||
|
||||
for (DriveChunk &single : multiple_)
|
||||
{
|
||||
switch (single.type())
|
||||
{
|
||||
case DriveType::CONSTANT: {
|
||||
auto &bits = single.constant().bits;
|
||||
bits.insert(bits.end(), constant.bits.begin(), constant.bits.end());
|
||||
} break;
|
||||
case DriveType::WIRE: {
|
||||
single.wire().width += width;
|
||||
} break;
|
||||
case DriveType::PORT: {
|
||||
single.port().width += width;
|
||||
} break;
|
||||
case DriveType::MARKER: {
|
||||
single.marker().width += width;
|
||||
} break;
|
||||
default:
|
||||
log_abort();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DriveChunk::can_append(DriveBit const &bit) const
|
||||
{
|
||||
if (size() == 0)
|
||||
return true;
|
||||
if (bit.type() != type_)
|
||||
return false;
|
||||
switch (type_)
|
||||
{
|
||||
case DriveType::NONE:
|
||||
return true;
|
||||
case DriveType::CONSTANT:
|
||||
return true;
|
||||
case DriveType::WIRE:
|
||||
return wire_.can_append(bit.wire());
|
||||
case DriveType::PORT:
|
||||
return port_.can_append(bit.port());
|
||||
case DriveType::MULTIPLE:
|
||||
return multiple_.can_append(bit.multiple());
|
||||
default:
|
||||
log_abort();
|
||||
}
|
||||
}
|
||||
|
||||
bool DriveChunk::try_append(DriveBit const &bit)
|
||||
{
|
||||
if (size() == 0)
|
||||
*this = bit;
|
||||
if (bit.type() != type_)
|
||||
return false;
|
||||
switch (type_)
|
||||
{
|
||||
case DriveType::NONE:
|
||||
none_ += 1;
|
||||
return true;
|
||||
case DriveType::CONSTANT:
|
||||
constant_.bits.push_back(bit.constant());
|
||||
return true;
|
||||
case DriveType::WIRE:
|
||||
return wire_.try_append(bit.wire());
|
||||
case DriveType::PORT:
|
||||
return port_.try_append(bit.port());
|
||||
case DriveType::MULTIPLE:
|
||||
return multiple_.try_append(bit.multiple());
|
||||
default:
|
||||
log_abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool DriveChunk::try_append(DriveChunk const &chunk)
|
||||
{
|
||||
if (size() == 0)
|
||||
*this = chunk;
|
||||
if (chunk.type_ != type_)
|
||||
return false;
|
||||
switch (type_)
|
||||
{
|
||||
case DriveType::NONE:
|
||||
none_ += chunk.none_;
|
||||
return true;
|
||||
case DriveType::CONSTANT:
|
||||
constant_.bits.insert(constant_.bits.end(), chunk.constant_.bits.begin(), chunk.constant_.bits.end());
|
||||
return true;
|
||||
case DriveType::WIRE:
|
||||
return wire_.try_append(chunk.wire());
|
||||
case DriveType::PORT:
|
||||
return port_.try_append(chunk.port());
|
||||
case DriveType::MARKER:
|
||||
return marker_.try_append(chunk.marker());
|
||||
case DriveType::MULTIPLE:
|
||||
return multiple_.try_append(chunk.multiple());
|
||||
}
|
||||
log_abort();
|
||||
}
|
||||
|
||||
void DriveSpec::append(DriveBit const &bit)
|
||||
{
|
||||
hash_ = 0;
|
||||
if (!packed()) {
|
||||
bits_.push_back(bit);
|
||||
width_ += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (chunks_.empty() || !chunks_.back().try_append(bit))
|
||||
chunks_.emplace_back(bit);
|
||||
width_ += 1;
|
||||
}
|
||||
|
||||
void DriveSpec::append(DriveChunk const &chunk)
|
||||
{
|
||||
hash_ = 0;
|
||||
pack();
|
||||
if (chunks_.empty() || !chunks_.back().try_append(chunk))
|
||||
chunks_.emplace_back(chunk);
|
||||
width_ += chunk.size();
|
||||
}
|
||||
|
||||
void DriveSpec::pack() const {
|
||||
if (bits_.empty())
|
||||
return;
|
||||
std::vector<DriveBit> bits(std::move(bits_));
|
||||
for (auto &bit : bits)
|
||||
if (chunks_.empty() || !chunks_.back().try_append(bit))
|
||||
chunks_.emplace_back(std::move(bit));
|
||||
}
|
||||
|
||||
void DriveSpec::unpack() const {
|
||||
if (chunks_.empty())
|
||||
return;
|
||||
for (auto &chunk : chunks_)
|
||||
{
|
||||
for (int i = 0, width = chunk.size(); i != width; ++i)
|
||||
{
|
||||
bits_.emplace_back(chunk[i]);
|
||||
}
|
||||
}
|
||||
chunks_.clear();
|
||||
}
|
||||
|
||||
void DriveSpec::compute_width()
|
||||
{
|
||||
width_ = 0;
|
||||
for (auto const &chunk : chunks_)
|
||||
width_ += chunk.size();
|
||||
}
|
||||
|
||||
void DriverMap::DriveBitGraph::add_edge(DriveBitId src, DriveBitId dst)
|
||||
{
|
||||
if (first_edges.emplace(src, dst).first->second == dst)
|
||||
return;
|
||||
if (second_edges.emplace(src, dst).first->second == dst)
|
||||
return;
|
||||
more_edges[src].emplace(dst);
|
||||
}
|
||||
|
||||
DriverMap::DriveBitId DriverMap::DriveBitGraph::pop_edge(DriveBitId src)
|
||||
{
|
||||
// TODO unused I think?
|
||||
auto found_more = more_edges.find(src);
|
||||
if (found_more != more_edges.end()) {
|
||||
auto result = found_more->second.pop();
|
||||
if (found_more->second.empty())
|
||||
more_edges.erase(found_more);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto found_second = second_edges.find(src);
|
||||
if (found_second != second_edges.end()) {
|
||||
auto result = found_second->second;
|
||||
second_edges.erase(found_second);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto found_first = first_edges.find(src);
|
||||
if (found_first != first_edges.end()) {
|
||||
auto result = found_first->second;
|
||||
first_edges.erase(found_first);
|
||||
return result;
|
||||
}
|
||||
|
||||
return DriveBitId();
|
||||
}
|
||||
|
||||
void DriverMap::DriveBitGraph::clear(DriveBitId src)
|
||||
{
|
||||
first_edges.erase(src);
|
||||
second_edges.erase(src);
|
||||
more_edges.erase(src);
|
||||
}
|
||||
|
||||
bool DriverMap::DriveBitGraph::contains(DriveBitId src)
|
||||
{
|
||||
return first_edges.count(src);
|
||||
}
|
||||
|
||||
int DriverMap::DriveBitGraph::count(DriveBitId src)
|
||||
{
|
||||
if (!first_edges.count(src))
|
||||
return 0;
|
||||
if (!second_edges.count(src))
|
||||
return 1;
|
||||
auto found = more_edges.find(src);
|
||||
if (found == more_edges.end())
|
||||
return 2;
|
||||
return GetSize(found->second) + 2;
|
||||
}
|
||||
|
||||
DriverMap::DriveBitId DriverMap::DriveBitGraph::at(DriveBitId src, int index)
|
||||
{
|
||||
if (index == 0)
|
||||
return first_edges.at(src);
|
||||
else if (index == 1)
|
||||
return second_edges.at(src);
|
||||
else
|
||||
return *more_edges.at(src).element(index - 2);
|
||||
}
|
||||
|
||||
|
||||
DriverMap::BitMode DriverMap::bit_mode(DriveBit const &bit)
|
||||
{
|
||||
switch (bit.type())
|
||||
{
|
||||
case DriveType::NONE:
|
||||
return BitMode::NONE;
|
||||
case DriveType::CONSTANT:
|
||||
// TODO how to handle Sx here?
|
||||
return bit.constant() == State::Sz ? BitMode::NONE : BitMode::DRIVER;
|
||||
case DriveType::WIRE: {
|
||||
auto const &wire = bit.wire();
|
||||
bool driver = wire.wire->port_input;
|
||||
bool driven = wire.wire->port_output;
|
||||
|
||||
if (driver && !driven)
|
||||
return BitMode::DRIVER;
|
||||
else if (driven && !driver)
|
||||
return BitMode::DRIVEN;
|
||||
else if (driver && driven)
|
||||
return BitMode::TRISTATE;
|
||||
else
|
||||
return keep_wire(bit.wire().wire) ? BitMode::KEEP : BitMode::NONE;
|
||||
}
|
||||
case DriveType::PORT: {
|
||||
auto const &port = bit.port();
|
||||
bool driver = celltypes.cell_output(port.cell->type, port.port);
|
||||
bool driven = celltypes.cell_input(port.cell->type, port.port);
|
||||
if (driver && !driven)
|
||||
return BitMode::DRIVER;
|
||||
else if (driven && !driver)
|
||||
return BitMode::DRIVEN_UNIQUE;
|
||||
else
|
||||
return BitMode::TRISTATE;
|
||||
}
|
||||
case DriveType::MARKER: {
|
||||
// TODO user supplied classification
|
||||
log_abort();
|
||||
}
|
||||
default:
|
||||
log_abort();
|
||||
}
|
||||
}
|
||||
|
||||
DriverMap::DriveBitId DriverMap::id_from_drive_bit(DriveBit const &bit)
|
||||
{
|
||||
switch (bit.type())
|
||||
{
|
||||
case DriveType::NONE:
|
||||
return -1;
|
||||
case DriveType::CONSTANT:
|
||||
return (int)bit.constant();
|
||||
case DriveType::WIRE: {
|
||||
auto const &wire_bit = bit.wire();
|
||||
int offset = next_offset;
|
||||
auto insertion = wire_offsets.emplace(wire_bit.wire, offset);
|
||||
if (insertion.second) {
|
||||
if (wire_bit.wire->width == 1) {
|
||||
log_assert(wire_bit.offset == 0);
|
||||
isolated_drive_bits.emplace(offset, bit);
|
||||
} else
|
||||
drive_bits.emplace(offset, DriveBitWire(wire_bit.wire, 0));
|
||||
next_offset += wire_bit.wire->width;
|
||||
}
|
||||
return insertion.first->second.id + wire_bit.offset;
|
||||
}
|
||||
case DriveType::PORT: {
|
||||
auto const &port_bit = bit.port();
|
||||
auto key = std::make_pair(port_bit.cell, port_bit.port);
|
||||
int offset = next_offset;
|
||||
auto insertion = port_offsets.emplace(key, offset);
|
||||
if (insertion.second) {
|
||||
int width = port_bit.cell->connections().at(port_bit.port).size();
|
||||
if (width == 1 && offset == 0) {
|
||||
log_assert(port_bit.offset == 0);
|
||||
isolated_drive_bits.emplace(offset, bit);
|
||||
} else
|
||||
drive_bits.emplace(offset, DriveBitPort(port_bit.cell, port_bit.port, 0));
|
||||
next_offset += width;
|
||||
}
|
||||
return insertion.first->second.id + port_bit.offset;
|
||||
}
|
||||
default:
|
||||
log_assert(false && "unsupported DriveType in DriverMap");
|
||||
}
|
||||
log_abort();
|
||||
}
|
||||
|
||||
DriveBit DriverMap::drive_bit_from_id(DriveBitId id)
|
||||
{
|
||||
auto found_isolated = isolated_drive_bits.find(id);
|
||||
if (found_isolated != isolated_drive_bits.end())
|
||||
return found_isolated->second;
|
||||
|
||||
auto found = drive_bits.upper_bound(id);
|
||||
if (found == drive_bits.begin()) {
|
||||
return id < 0 ? DriveBit() : DriveBit((State) id.id);
|
||||
}
|
||||
--found;
|
||||
DriveBit result = found->second;
|
||||
if (result.is_wire()) {
|
||||
result.wire().offset += id.id - found->first.id;
|
||||
} else {
|
||||
log_assert(result.is_port());
|
||||
result.port().offset += id.id - found->first.id;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void DriverMap::connect_directed_merge(DriveBitId driven_id, DriveBitId driver_id)
|
||||
{
|
||||
if (driven_id == driver_id)
|
||||
return;
|
||||
|
||||
same_driver.merge(driven_id, driver_id);
|
||||
|
||||
for (int i = 0, end = connected_drivers.count(driven_id); i != end; ++i)
|
||||
connected_drivers.add_edge(driver_id, connected_drivers.at(driven_id, i));
|
||||
|
||||
connected_drivers.clear(driven_id);
|
||||
|
||||
for (int i = 0, end = connected_undirected.count(driven_id); i != end; ++i)
|
||||
connected_undirected.add_edge(driver_id, connected_undirected.at(driven_id, i));
|
||||
|
||||
connected_undirected.clear(driven_id);
|
||||
}
|
||||
|
||||
void DriverMap::connect_directed_buffer(DriveBitId driven_id, DriveBitId driver_id)
|
||||
{
|
||||
connected_drivers.add_edge(driven_id, driver_id);
|
||||
}
|
||||
|
||||
void DriverMap::connect_undirected(DriveBitId a_id, DriveBitId b_id)
|
||||
{
|
||||
connected_undirected.add_edge(a_id, b_id);
|
||||
connected_undirected.add_edge(b_id, a_id);
|
||||
}
|
||||
|
||||
void DriverMap::add(Module *module)
|
||||
{
|
||||
for (auto const &conn : module->connections())
|
||||
add(conn.first, conn.second);
|
||||
|
||||
for (auto cell : module->cells())
|
||||
for (auto const &conn : cell->connections())
|
||||
add_port(cell, conn.first, conn.second);
|
||||
}
|
||||
|
||||
// Add a single bit connection to the driver map.
|
||||
void DriverMap::add(DriveBit const &a, DriveBit const &b)
|
||||
{
|
||||
DriveBitId a_id = id_from_drive_bit(a);
|
||||
DriveBitId b_id = id_from_drive_bit(b);
|
||||
|
||||
DriveBitId orig_a_id = a_id;
|
||||
DriveBitId orig_b_id = b_id;
|
||||
|
||||
a_id = same_driver.find(a_id);
|
||||
b_id = same_driver.find(b_id);
|
||||
|
||||
if (a_id == b_id)
|
||||
return;
|
||||
|
||||
BitMode a_mode = bit_mode(orig_a_id == a_id ? a : drive_bit_from_id(a_id));
|
||||
BitMode b_mode = bit_mode(orig_b_id == b_id ? b : drive_bit_from_id(b_id));
|
||||
|
||||
// If either bit is just a wire that we don't need to keep, merge and
|
||||
// use the other end as representative bit.
|
||||
if (a_mode == BitMode::NONE && !(b_mode == BitMode::DRIVEN_UNIQUE || b_mode == BitMode::DRIVEN))
|
||||
connect_directed_merge(a_id, b_id);
|
||||
else if (b_mode == BitMode::NONE && !(a_mode == BitMode::DRIVEN_UNIQUE || a_mode == BitMode::DRIVEN))
|
||||
connect_directed_merge(b_id, a_id);
|
||||
// If either bit requires a driven value and has a unique driver, merge
|
||||
// and use the other end as representative bit.
|
||||
else if (a_mode == BitMode::DRIVEN_UNIQUE && !(b_mode == BitMode::DRIVEN_UNIQUE || b_mode == BitMode::DRIVEN))
|
||||
connect_directed_buffer(a_id, b_id);
|
||||
else if (b_mode == BitMode::DRIVEN_UNIQUE && !(a_mode == BitMode::DRIVEN_UNIQUE || a_mode == BitMode::DRIVEN))
|
||||
connect_directed_buffer(b_id, a_id);
|
||||
// If either bit only drives a value, store a directed connection from
|
||||
// it to the other bit.
|
||||
else if (a_mode == BitMode::DRIVER)
|
||||
connect_directed_buffer(b_id, a_id);
|
||||
else if (b_mode == BitMode::DRIVER)
|
||||
connect_directed_buffer(a_id, b_id);
|
||||
// Otherwise we store an undirected connection which we will resolve
|
||||
// during querying.
|
||||
else
|
||||
connect_undirected(a_id, b_id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Specialized version that avoids unpacking
|
||||
void DriverMap::add(SigSpec const &a, SigSpec const &b)
|
||||
{
|
||||
log_assert(a.size() == b.size());
|
||||
auto const &a_chunks = a.chunks();
|
||||
auto const &b_chunks = b.chunks();
|
||||
|
||||
auto a_chunk = a_chunks.begin();
|
||||
auto a_end = a_chunks.end();
|
||||
int a_offset = 0;
|
||||
|
||||
auto b_chunk = b_chunks.begin();
|
||||
int b_offset = 0;
|
||||
|
||||
SigChunk tmp_a, tmp_b;
|
||||
while (a_chunk != a_end) {
|
||||
int a_width = a_chunk->width - a_offset;
|
||||
if (a_width == 0) {
|
||||
a_offset = 0;
|
||||
++a_chunk;
|
||||
continue;
|
||||
}
|
||||
int b_width = b_chunk->width - b_offset;
|
||||
if (b_width == 0) {
|
||||
b_offset = 0;
|
||||
++b_chunk;
|
||||
continue;
|
||||
}
|
||||
int width = std::min(a_width, b_width);
|
||||
log_assert(width > 0);
|
||||
|
||||
SigChunk const &a_subchunk =
|
||||
a_offset == 0 && a_width == width ? *a_chunk : a_chunk->extract(a_offset, width);
|
||||
SigChunk const &b_subchunk =
|
||||
b_offset == 0 && b_width == width ? *b_chunk : b_chunk->extract(b_offset, width);
|
||||
|
||||
add(a_subchunk, b_subchunk);
|
||||
|
||||
a_offset += width;
|
||||
b_offset += width;
|
||||
}
|
||||
}
|
||||
|
||||
void DriverMap::add_port(Cell *cell, IdString const &port, SigSpec const &b)
|
||||
{
|
||||
int offset = 0;
|
||||
for (auto const &chunk : b.chunks()) {
|
||||
add(chunk, DriveChunkPort(cell, port, offset, chunk.width));
|
||||
offset += chunk.size();
|
||||
}
|
||||
}
|
||||
|
||||
void DriverMap::orient_undirected(DriveBitId id)
|
||||
{
|
||||
pool<DriveBitId> &seen = orient_undirected_seen;
|
||||
pool<DriveBitId> &drivers = orient_undirected_drivers;
|
||||
dict<DriveBitId, int> &distance = orient_undirected_distance;
|
||||
seen.clear();
|
||||
drivers.clear();
|
||||
|
||||
seen.emplace(id);
|
||||
|
||||
for (int pos = 0; pos < GetSize(seen); ++pos) {
|
||||
DriveBitId current = *seen.element(seen.size() - 1 - pos);
|
||||
DriveBit bit = drive_bit_from_id(current);
|
||||
|
||||
BitMode mode = bit_mode(bit);
|
||||
|
||||
if (mode == BitMode::DRIVER || mode == BitMode::TRISTATE)
|
||||
drivers.emplace(current);
|
||||
|
||||
if (connected_drivers.contains(current))
|
||||
drivers.emplace(current);
|
||||
|
||||
int undirected_driver_count = connected_undirected.count(current);
|
||||
|
||||
for (int i = 0; i != undirected_driver_count; ++i)
|
||||
seen.emplace(same_driver.find(connected_undirected.at(current, i)));
|
||||
}
|
||||
|
||||
if (drivers.empty())
|
||||
for (auto seen_id : seen)
|
||||
drivers.emplace(seen_id);
|
||||
|
||||
for (auto driver : drivers)
|
||||
{
|
||||
distance.clear();
|
||||
distance.emplace(driver, 0);
|
||||
|
||||
for (int pos = 0; pos < GetSize(distance); ++pos) {
|
||||
auto current_it = distance.element(distance.size() - 1 - pos);
|
||||
|
||||
DriveBitId current = current_it->first;
|
||||
int undirected_driver_count = connected_undirected.count(current);
|
||||
|
||||
for (int i = 0; i != undirected_driver_count; ++i)
|
||||
{
|
||||
DriveBitId next = same_driver.find(connected_undirected.at(current, i));
|
||||
auto emplaced = distance.emplace(next, current_it->second + 1);
|
||||
if (emplaced.first->second == current_it->second + 1)
|
||||
connected_oriented.add_edge(next, current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto seen_id : seen)
|
||||
oriented_present.emplace(seen_id);
|
||||
}
|
||||
|
||||
DriveBit DriverMap::operator()(DriveBit const &bit)
|
||||
{
|
||||
if (bit.type() == DriveType::MARKER || bit.type() == DriveType::NONE)
|
||||
return bit;
|
||||
if (bit.type() == DriveType::MULTIPLE)
|
||||
{
|
||||
DriveBit result;
|
||||
for (auto const &inner : bit.multiple().multiple())
|
||||
result.merge((*this)(inner));
|
||||
return result;
|
||||
}
|
||||
|
||||
DriveBitId bit_id = id_from_drive_bit(bit);
|
||||
|
||||
DriveBitId bit_repr_id = same_driver.find(bit_id);
|
||||
|
||||
DriveBit bit_repr = drive_bit_from_id(bit_repr_id);
|
||||
|
||||
BitMode mode = bit_mode(bit_repr);
|
||||
|
||||
if (mode == BitMode::KEEP && bit_repr_id != bit_id)
|
||||
return bit_repr;
|
||||
|
||||
int implicit_driver_count = connected_drivers.count(bit_repr_id);
|
||||
if (connected_undirected.contains(bit_repr_id) && !oriented_present.count(bit_repr_id))
|
||||
orient_undirected(bit_repr_id);
|
||||
|
||||
DriveBit driver;
|
||||
|
||||
if (mode == BitMode::DRIVER || mode == BitMode::TRISTATE)
|
||||
driver = bit_repr;
|
||||
|
||||
for (int i = 0; i != implicit_driver_count; ++i)
|
||||
driver.merge(drive_bit_from_id(connected_drivers.at(bit_repr_id, i)));
|
||||
|
||||
int oriented_driver_count = connected_oriented.count(bit_repr_id);
|
||||
for (int i = 0; i != oriented_driver_count; ++i)
|
||||
driver.merge(drive_bit_from_id(connected_oriented.at(bit_repr_id, i)));
|
||||
|
||||
return driver;
|
||||
}
|
||||
|
||||
DriveSpec DriverMap::operator()(DriveSpec spec)
|
||||
{
|
||||
DriveSpec result;
|
||||
|
||||
for (int i = 0, width = spec.size(); i != width; ++i)
|
||||
result.append((*this)(spec[i]));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const char *log_signal(DriveChunkWire const &chunk)
|
||||
{
|
||||
const char *id = log_id(chunk.wire->name);
|
||||
if (chunk.is_whole())
|
||||
return id;
|
||||
if (chunk.width == 1)
|
||||
return log_str(stringf("%s [%d]", id, chunk.offset));
|
||||
return log_str(stringf("%s [%d:%d]", id, chunk.offset + chunk.width - 1, chunk.offset));
|
||||
}
|
||||
|
||||
|
||||
const char *log_signal(DriveChunkPort const &chunk)
|
||||
{
|
||||
const char *cell_id = log_id(chunk.cell->name);
|
||||
const char *port_id = log_id(chunk.port);
|
||||
if (chunk.is_whole())
|
||||
return log_str(stringf("%s <%s>", cell_id, port_id));
|
||||
if (chunk.width == 1)
|
||||
return log_str(stringf("%s <%s> [%d]", cell_id, port_id, chunk.offset));
|
||||
return log_str(stringf("%s <%s> [%d:%d]", cell_id, port_id, chunk.offset + chunk.width - 1, chunk.offset));
|
||||
}
|
||||
|
||||
const char *log_signal(DriveChunkMarker const &chunk)
|
||||
{
|
||||
if (chunk.width == 1)
|
||||
return log_str(stringf("<marker %d> [%d]", chunk.marker, chunk.offset));
|
||||
return log_str(stringf("<marker %d> [%d:%d]", chunk.marker, chunk.offset + chunk.width - 1, chunk.offset));
|
||||
}
|
||||
|
||||
const char *log_signal(DriveChunk const &chunk)
|
||||
{
|
||||
switch (chunk.type())
|
||||
{
|
||||
case DriveType::NONE:
|
||||
return log_str(stringf("<none x%d>", chunk.size()));
|
||||
case DriveType::CONSTANT:
|
||||
return log_const(chunk.constant());
|
||||
case DriveType::WIRE:
|
||||
return log_signal(chunk.wire());
|
||||
case DriveType::PORT:
|
||||
return log_signal(chunk.port());
|
||||
case DriveType::MARKER:
|
||||
return log_signal(chunk.marker());
|
||||
case DriveType::MULTIPLE: {
|
||||
std::string str = "<multiple";
|
||||
const char *sep = " ";
|
||||
for (auto const &single : chunk.multiple().multiple()) {
|
||||
str += sep;
|
||||
sep = ", ";
|
||||
str += log_signal(single);
|
||||
}
|
||||
str += ">";
|
||||
return log_str(str);
|
||||
}
|
||||
default:
|
||||
log_abort();
|
||||
}
|
||||
}
|
||||
|
||||
const char *log_signal(DriveSpec const &spec)
|
||||
{
|
||||
auto &chunks = spec.chunks();
|
||||
if (chunks.empty())
|
||||
return "{}";
|
||||
if (chunks.size() == 1)
|
||||
return log_signal(chunks[0]);
|
||||
|
||||
std::string str;
|
||||
const char *sep = "{ ";
|
||||
|
||||
for (auto i = chunks.rbegin(), end = chunks.rend(); i != end; ++i)
|
||||
{
|
||||
str += sep;
|
||||
sep = " ";
|
||||
str += log_signal(*i);
|
||||
}
|
||||
str += " }";
|
||||
|
||||
return log_str(str);
|
||||
}
|
||||
|
||||
YOSYS_NAMESPACE_END
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,853 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
||||
* Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "kernel/functional.h"
|
||||
#include "kernel/topo_scc.h"
|
||||
#include "ff.h"
|
||||
#include "ffinit.h"
|
||||
#include <deque>
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
namespace Functional {
|
||||
|
||||
const char *fn_to_string(Fn fn) {
|
||||
switch(fn) {
|
||||
case Fn::invalid: return "invalid";
|
||||
case Fn::buf: return "buf";
|
||||
case Fn::slice: return "slice";
|
||||
case Fn::zero_extend: return "zero_extend";
|
||||
case Fn::sign_extend: return "sign_extend";
|
||||
case Fn::concat: return "concat";
|
||||
case Fn::add: return "add";
|
||||
case Fn::sub: return "sub";
|
||||
case Fn::mul: return "mul";
|
||||
case Fn::unsigned_div: return "unsigned_div";
|
||||
case Fn::unsigned_mod: return "unsigned_mod";
|
||||
case Fn::bitwise_and: return "bitwise_and";
|
||||
case Fn::bitwise_or: return "bitwise_or";
|
||||
case Fn::bitwise_xor: return "bitwise_xor";
|
||||
case Fn::bitwise_not: return "bitwise_not";
|
||||
case Fn::reduce_and: return "reduce_and";
|
||||
case Fn::reduce_or: return "reduce_or";
|
||||
case Fn::reduce_xor: return "reduce_xor";
|
||||
case Fn::unary_minus: return "unary_minus";
|
||||
case Fn::equal: return "equal";
|
||||
case Fn::not_equal: return "not_equal";
|
||||
case Fn::signed_greater_than: return "signed_greater_than";
|
||||
case Fn::signed_greater_equal: return "signed_greater_equal";
|
||||
case Fn::unsigned_greater_than: return "unsigned_greater_than";
|
||||
case Fn::unsigned_greater_equal: return "unsigned_greater_equal";
|
||||
case Fn::logical_shift_left: return "logical_shift_left";
|
||||
case Fn::logical_shift_right: return "logical_shift_right";
|
||||
case Fn::arithmetic_shift_right: return "arithmetic_shift_right";
|
||||
case Fn::mux: return "mux";
|
||||
case Fn::constant: return "constant";
|
||||
case Fn::input: return "input";
|
||||
case Fn::state: return "state";
|
||||
case Fn::memory_read: return "memory_read";
|
||||
case Fn::memory_write: return "memory_write";
|
||||
}
|
||||
log_error("fn_to_string: unknown Functional::Fn value %d", (int)fn);
|
||||
}
|
||||
|
||||
vector<IRInput const*> IR::inputs(IdString kind) const {
|
||||
vector<IRInput const*> ret;
|
||||
for (const auto &[name, input] : _inputs)
|
||||
if(input.kind == kind)
|
||||
ret.push_back(&input);
|
||||
return ret;
|
||||
}
|
||||
|
||||
vector<IROutput const*> IR::outputs(IdString kind) const {
|
||||
vector<IROutput const*> ret;
|
||||
for (const auto &[name, output] : _outputs)
|
||||
if(output.kind == kind)
|
||||
ret.push_back(&output);
|
||||
return ret;
|
||||
}
|
||||
|
||||
vector<IRState const*> IR::states(IdString kind) const {
|
||||
vector<IRState const*> ret;
|
||||
for (const auto &[name, state] : _states)
|
||||
if(state.kind == kind)
|
||||
ret.push_back(&state);
|
||||
return ret;
|
||||
}
|
||||
|
||||
vector<IRInput const*> IR::all_inputs() const {
|
||||
vector<IRInput const*> ret;
|
||||
for (const auto &[name, input] : _inputs)
|
||||
ret.push_back(&input);
|
||||
return ret;
|
||||
}
|
||||
|
||||
vector<IROutput const*> IR::all_outputs() const {
|
||||
vector<IROutput const*> ret;
|
||||
for (const auto &[name, output] : _outputs)
|
||||
ret.push_back(&output);
|
||||
return ret;
|
||||
}
|
||||
|
||||
vector<IRState const*> IR::all_states() const {
|
||||
vector<IRState const*> ret;
|
||||
for (const auto &[name, state] : _states)
|
||||
ret.push_back(&state);
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct PrintVisitor : DefaultVisitor<std::string> {
|
||||
std::function<std::string(Node)> 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
|
||||
std::string slice(Node, Node a, int offset, int out_width) override { return "slice(" + np(a) + ", " + std::to_string(offset) + ", " + std::to_string(out_width) + ")"; }
|
||||
std::string zero_extend(Node, Node a, int out_width) override { return "zero_extend(" + np(a) + ", " + std::to_string(out_width) + ")"; }
|
||||
std::string sign_extend(Node, Node a, int out_width) override { return "sign_extend(" + np(a) + ", " + std::to_string(out_width) + ")"; }
|
||||
std::string constant(Node, RTLIL::Const const& value) override { return "constant(" + value.as_string() + ")"; }
|
||||
std::string input(Node, IdString name, IdString kind) override { return "input(" + name.str() + ", " + kind.str() + ")"; }
|
||||
std::string state(Node, IdString name, IdString kind) override { return "state(" + name.str() + ", " + kind.str() + ")"; }
|
||||
std::string default_handler(Node self) override {
|
||||
std::string ret = fn_to_string(self.fn());
|
||||
ret += "(";
|
||||
for(size_t i = 0; i < self.arg_count(); i++) {
|
||||
if(i > 0) ret += ", ";
|
||||
ret += np(self.arg(i));
|
||||
}
|
||||
ret += ")";
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
std::string Node::to_string()
|
||||
{
|
||||
return to_string([](Node n) { return RTLIL::unescape_id(n.name()); });
|
||||
}
|
||||
|
||||
std::string Node::to_string(std::function<std::string(Node)> np)
|
||||
{
|
||||
return visit(PrintVisitor(np));
|
||||
}
|
||||
|
||||
class CellSimplifier {
|
||||
Factory &factory;
|
||||
Node sign(Node a) {
|
||||
return factory.slice(a, a.width() - 1, 1);
|
||||
}
|
||||
Node neg_if(Node a, Node s) {
|
||||
return factory.mux(a, factory.unary_minus(a), s);
|
||||
}
|
||||
Node abs(Node a) {
|
||||
return neg_if(a, sign(a));
|
||||
}
|
||||
Node handle_shift(Node a, Node b, bool is_right, bool is_signed) {
|
||||
// to prevent new_width == 0, we handle this case separately
|
||||
if(a.width() == 1) {
|
||||
if(!is_signed)
|
||||
return factory.bitwise_and(a, factory.bitwise_not(factory.reduce_or(b)));
|
||||
else
|
||||
return a;
|
||||
}
|
||||
int new_width = ceil_log2(a.width());
|
||||
Node b_truncated = factory.extend(b, new_width, false);
|
||||
Node y =
|
||||
!is_right ? factory.logical_shift_left(a, b_truncated) :
|
||||
!is_signed ? factory.logical_shift_right(a, b_truncated) :
|
||||
factory.arithmetic_shift_right(a, b_truncated);
|
||||
if(b.width() <= new_width)
|
||||
return y;
|
||||
Node overflow = factory.unsigned_greater_equal(b, factory.constant(RTLIL::Const(a.width(), b.width())));
|
||||
Node y_if_overflow = is_signed ? factory.extend(sign(a), a.width(), true) : factory.constant(RTLIL::Const(State::S0, a.width()));
|
||||
return factory.mux(y, y_if_overflow, overflow);
|
||||
}
|
||||
public:
|
||||
Node logical_shift_left(Node a, Node b) { return handle_shift(a, b, false, false); }
|
||||
Node logical_shift_right(Node a, Node b) { return handle_shift(a, b, true, false); }
|
||||
Node arithmetic_shift_right(Node a, Node b) { return handle_shift(a, b, true, true); }
|
||||
Node bitwise_mux(Node a, Node b, Node s) {
|
||||
Node aa = factory.bitwise_and(a, factory.bitwise_not(s));
|
||||
Node bb = factory.bitwise_and(b, s);
|
||||
return factory.bitwise_or(aa, bb);
|
||||
}
|
||||
CellSimplifier(Factory &f) : factory(f) {}
|
||||
private:
|
||||
Node handle_pow(Node a0, Node b, int y_width, bool is_signed) {
|
||||
Node a = factory.extend(a0, y_width, is_signed);
|
||||
Node r = factory.constant(Const(1, y_width));
|
||||
for(int i = 0; i < b.width(); i++) {
|
||||
Node b_bit = factory.slice(b, i, 1);
|
||||
r = factory.mux(r, factory.mul(r, a), b_bit);
|
||||
a = factory.mul(a, a);
|
||||
}
|
||||
if (is_signed) {
|
||||
Node a_ge_1 = factory.unsigned_greater_than(abs(a0), factory.constant(Const(1, a0.width())));
|
||||
Node zero_result = factory.bitwise_and(a_ge_1, sign(b));
|
||||
r = factory.mux(r, factory.constant(Const(0, y_width)), zero_result);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
Node handle_bmux(Node a, Node s, int a_offset, int width, int sn) {
|
||||
if(sn < 1)
|
||||
return factory.slice(a, a_offset, width);
|
||||
else {
|
||||
Node y0 = handle_bmux(a, s, a_offset, width, sn - 1);
|
||||
Node y1 = handle_bmux(a, s, a_offset + (width << (sn - 1)), width, sn - 1);
|
||||
return factory.mux(y0, y1, factory.slice(s, sn - 1, 1));
|
||||
}
|
||||
}
|
||||
Node handle_pmux(Node a, Node b, Node s) {
|
||||
// TODO : what to do about multiple b bits set ?
|
||||
log_assert(b.width() == a.width() * s.width());
|
||||
Node y = a;
|
||||
for(int i = 0; i < s.width(); i++)
|
||||
y = factory.mux(y, factory.slice(b, a.width() * i, a.width()), factory.slice(s, i, 1));
|
||||
return y;
|
||||
}
|
||||
dict<IdString, Node> handle_fa(Node a, Node b, Node c) {
|
||||
Node t1 = factory.bitwise_xor(a, b);
|
||||
Node t2 = factory.bitwise_and(a, b);
|
||||
Node t3 = factory.bitwise_and(c, t1);
|
||||
Node y = factory.bitwise_xor(c, t1);
|
||||
Node x = factory.bitwise_or(t2, t3);
|
||||
return {{ID(X), x}, {ID(Y), y}};
|
||||
}
|
||||
dict<IdString, Node> handle_alu(Node a_in, Node b_in, int y_width, bool is_signed, Node ci, Node bi) {
|
||||
Node a = factory.extend(a_in, y_width, is_signed);
|
||||
Node b_uninverted = factory.extend(b_in, y_width, is_signed);
|
||||
Node b = factory.mux(b_uninverted, factory.bitwise_not(b_uninverted), bi);
|
||||
Node x = factory.bitwise_xor(a, b);
|
||||
// we can compute the carry into each bit using (a+b+c)^a^b. since we want the carry out,
|
||||
// i.e. the carry into the next bit, we have to add an extra bit to a and b, and
|
||||
// then slice off the bottom bit of the result.
|
||||
Node a_extra = factory.extend(a, y_width + 1, false);
|
||||
Node b_extra = factory.extend(b, y_width + 1, false);
|
||||
Node y_extra = factory.add(factory.add(a_extra, b_extra), factory.extend(ci, a.width() + 1, false));
|
||||
Node y = factory.slice(y_extra, 0, y_width);
|
||||
Node carries = factory.bitwise_xor(y_extra, factory.bitwise_xor(a_extra, b_extra));
|
||||
Node co = factory.slice(carries, 1, y_width);
|
||||
return {{ID(X), x}, {ID(Y), y}, {ID(CO), co}};
|
||||
}
|
||||
Node handle_lcu(Node p, Node g, Node ci) {
|
||||
return handle_alu(g, factory.bitwise_or(p, g), g.width(), false, ci, factory.constant(Const(State::S0, 1))).at(ID(CO));
|
||||
}
|
||||
public:
|
||||
std::variant<dict<IdString, Node>, Node> handle(IdString cellName, IdString cellType, dict<IdString, Const> parameters, dict<IdString, Node> inputs)
|
||||
{
|
||||
int a_width = parameters.at(ID(A_WIDTH), Const(-1)).as_int();
|
||||
int b_width = parameters.at(ID(B_WIDTH), Const(-1)).as_int();
|
||||
int y_width = parameters.at(ID(Y_WIDTH), Const(-1)).as_int();
|
||||
bool a_signed = parameters.at(ID(A_SIGNED), Const(0)).as_bool();
|
||||
bool b_signed = parameters.at(ID(B_SIGNED), Const(0)).as_bool();
|
||||
if(cellType.in({ID($add), ID($sub), ID($and), ID($or), ID($xor), ID($xnor), ID($mul)})){
|
||||
bool is_signed = a_signed && b_signed;
|
||||
Node a = factory.extend(inputs.at(ID(A)), y_width, is_signed);
|
||||
Node b = factory.extend(inputs.at(ID(B)), y_width, is_signed);
|
||||
if(cellType == ID($add))
|
||||
return factory.add(a, b);
|
||||
else if(cellType == ID($sub))
|
||||
return factory.sub(a, b);
|
||||
else if(cellType == ID($mul))
|
||||
return factory.mul(a, b);
|
||||
else if(cellType == ID($and))
|
||||
return factory.bitwise_and(a, b);
|
||||
else if(cellType == ID($or))
|
||||
return factory.bitwise_or(a, b);
|
||||
else if(cellType == ID($xor))
|
||||
return factory.bitwise_xor(a, b);
|
||||
else if(cellType == ID($xnor))
|
||||
return factory.bitwise_not(factory.bitwise_xor(a, b));
|
||||
else
|
||||
log_abort();
|
||||
}else if(cellType.in({ID($eq), ID($ne), ID($eqx), ID($nex), ID($le), ID($lt), ID($ge), ID($gt)})){
|
||||
bool is_signed = a_signed && b_signed;
|
||||
int width = max(a_width, b_width);
|
||||
Node a = factory.extend(inputs.at(ID(A)), width, is_signed);
|
||||
Node b = factory.extend(inputs.at(ID(B)), width, is_signed);
|
||||
if(cellType.in({ID($eq), ID($eqx)}))
|
||||
return factory.extend(factory.equal(a, b), y_width, false);
|
||||
else if(cellType.in({ID($ne), ID($nex)}))
|
||||
return factory.extend(factory.not_equal(a, b), y_width, false);
|
||||
else if(cellType == ID($lt))
|
||||
return factory.extend(is_signed ? factory.signed_greater_than(b, a) : factory.unsigned_greater_than(b, a), y_width, false);
|
||||
else if(cellType == ID($le))
|
||||
return factory.extend(is_signed ? factory.signed_greater_equal(b, a) : factory.unsigned_greater_equal(b, a), y_width, false);
|
||||
else if(cellType == ID($gt))
|
||||
return factory.extend(is_signed ? factory.signed_greater_than(a, b) : factory.unsigned_greater_than(a, b), y_width, false);
|
||||
else if(cellType == ID($ge))
|
||||
return factory.extend(is_signed ? factory.signed_greater_equal(a, b) : factory.unsigned_greater_equal(a, b), y_width, false);
|
||||
else
|
||||
log_abort();
|
||||
}else if(cellType.in({ID($logic_or), ID($logic_and)})){
|
||||
Node a = factory.reduce_or(inputs.at(ID(A)));
|
||||
Node b = factory.reduce_or(inputs.at(ID(B)));
|
||||
Node y = cellType == ID($logic_and) ? factory.bitwise_and(a, b) : factory.bitwise_or(a, b);
|
||||
return factory.extend(y, y_width, false);
|
||||
}else if(cellType == ID($not)){
|
||||
Node a = factory.extend(inputs.at(ID(A)), y_width, a_signed);
|
||||
return factory.bitwise_not(a);
|
||||
}else if(cellType == ID($pos)){
|
||||
return factory.extend(inputs.at(ID(A)), y_width, a_signed);
|
||||
}else if(cellType == ID($neg)){
|
||||
Node a = factory.extend(inputs.at(ID(A)), y_width, a_signed);
|
||||
return factory.unary_minus(a);
|
||||
}else if(cellType == ID($logic_not)){
|
||||
Node a = factory.reduce_or(inputs.at(ID(A)));
|
||||
Node y = factory.bitwise_not(a);
|
||||
return factory.extend(y, y_width, false);
|
||||
}else if(cellType.in({ID($reduce_or), ID($reduce_bool)})){
|
||||
Node a = factory.reduce_or(inputs.at(ID(A)));
|
||||
return factory.extend(a, y_width, false);
|
||||
}else if(cellType == ID($reduce_and)){
|
||||
Node a = factory.reduce_and(inputs.at(ID(A)));
|
||||
return factory.extend(a, y_width, false);
|
||||
}else if(cellType.in({ID($reduce_xor), ID($reduce_xnor)})){
|
||||
Node a = factory.reduce_xor(inputs.at(ID(A)));
|
||||
Node y = cellType == ID($reduce_xnor) ? factory.bitwise_not(a) : a;
|
||||
return factory.extend(y, y_width, false);
|
||||
}else if(cellType == ID($shl) || cellType == ID($sshl)){
|
||||
Node a = factory.extend(inputs.at(ID(A)), y_width, a_signed);
|
||||
Node b = inputs.at(ID(B));
|
||||
return logical_shift_left(a, b);
|
||||
}else if(cellType == ID($shr) || cellType == ID($sshr)){
|
||||
int width = max(a_width, y_width);
|
||||
Node a = factory.extend(inputs.at(ID(A)), width, a_signed);
|
||||
Node b = inputs.at(ID(B));
|
||||
Node y = a_signed && cellType == ID($sshr) ?
|
||||
arithmetic_shift_right(a, b) :
|
||||
logical_shift_right(a, b);
|
||||
return factory.extend(y, y_width, a_signed);
|
||||
}else if(cellType == ID($shiftx) || cellType == ID($shift)){
|
||||
int width = max(a_width, y_width);
|
||||
Node a = factory.extend(inputs.at(ID(A)), width, cellType == ID($shift) && a_signed);
|
||||
Node b = inputs.at(ID(B));
|
||||
Node shr = logical_shift_right(a, b);
|
||||
if(b_signed) {
|
||||
Node shl = logical_shift_left(a, factory.unary_minus(b));
|
||||
Node y = factory.mux(shr, shl, sign(b));
|
||||
return factory.extend(y, y_width, false);
|
||||
} else {
|
||||
return factory.extend(shr, y_width, false);
|
||||
}
|
||||
}else if(cellType == ID($mux)){
|
||||
return factory.mux(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(S)));
|
||||
}else if(cellType == ID($pmux)){
|
||||
return handle_pmux(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(S)));
|
||||
}else if(cellType == ID($concat)){
|
||||
Node a = inputs.at(ID(A));
|
||||
Node b = inputs.at(ID(B));
|
||||
return factory.concat(a, b);
|
||||
}else if(cellType == ID($slice)){
|
||||
int offset = parameters.at(ID(OFFSET)).as_int();
|
||||
Node a = inputs.at(ID(A));
|
||||
return factory.slice(a, offset, y_width);
|
||||
}else if(cellType.in({ID($div), ID($mod), ID($divfloor), ID($modfloor)})) {
|
||||
int width = max(a_width, b_width);
|
||||
bool is_signed = a_signed && b_signed;
|
||||
Node a = factory.extend(inputs.at(ID(A)), width, is_signed);
|
||||
Node b = factory.extend(inputs.at(ID(B)), width, is_signed);
|
||||
if(is_signed) {
|
||||
if(cellType == ID($div)) {
|
||||
// divide absolute values, then flip the sign if input signs differ
|
||||
// but extend the width first, to handle the case (most negative value) / (-1)
|
||||
Node abs_y = factory.unsigned_div(abs(a), abs(b));
|
||||
Node out_sign = factory.not_equal(sign(a), sign(b));
|
||||
return neg_if(factory.extend(abs_y, y_width, false), out_sign);
|
||||
} else if(cellType == ID($mod)) {
|
||||
// similar to division but output sign == divisor sign
|
||||
Node abs_y = factory.unsigned_mod(abs(a), abs(b));
|
||||
return neg_if(factory.extend(abs_y, y_width, false), sign(a));
|
||||
} else if(cellType == ID($divfloor)) {
|
||||
// if b is negative, flip both signs so that b is positive
|
||||
Node b_sign = sign(b);
|
||||
Node a1 = neg_if(a, b_sign);
|
||||
Node b1 = neg_if(b, b_sign);
|
||||
// if a is now negative, calculate ~((~a) / b) = -((-a - 1) / b + 1)
|
||||
// which equals the negative of (-a) / b with rounding up rather than down
|
||||
// note that to handle the case where a = most negative value properly,
|
||||
// we have to calculate a1_sign from the original values rather than using sign(a1)
|
||||
Node a1_sign = factory.bitwise_and(factory.not_equal(sign(a), sign(b)), factory.reduce_or(a));
|
||||
Node a2 = factory.mux(a1, factory.bitwise_not(a1), a1_sign);
|
||||
Node y1 = factory.unsigned_div(a2, b1);
|
||||
Node y2 = factory.extend(y1, y_width, false);
|
||||
return factory.mux(y2, factory.bitwise_not(y2), a1_sign);
|
||||
} else if(cellType == ID($modfloor)) {
|
||||
// calculate |a| % |b| and then subtract from |b| if input signs differ and the remainder is non-zero
|
||||
Node abs_b = abs(b);
|
||||
Node abs_y = factory.unsigned_mod(abs(a), abs_b);
|
||||
Node flip_y = factory.bitwise_and(factory.bitwise_xor(sign(a), sign(b)), factory.reduce_or(abs_y));
|
||||
Node y_flipped = factory.mux(abs_y, factory.sub(abs_b, abs_y), flip_y);
|
||||
// since y_flipped is strictly less than |b|, the top bit is always 0 and we can just sign extend the flipped result
|
||||
Node y = neg_if(y_flipped, sign(b));
|
||||
return factory.extend(y, y_width, true);
|
||||
} else
|
||||
log_error("unhandled cell in CellSimplifier %s\n", cellType.c_str());
|
||||
} else {
|
||||
if(cellType.in({ID($mod), ID($modfloor)}))
|
||||
return factory.extend(factory.unsigned_mod(a, b), y_width, false);
|
||||
else
|
||||
return factory.extend(factory.unsigned_div(a, b), y_width, false);
|
||||
}
|
||||
} else if(cellType == ID($pow)) {
|
||||
return handle_pow(inputs.at(ID(A)), inputs.at(ID(B)), y_width, a_signed && b_signed);
|
||||
} else if (cellType == ID($lut)) {
|
||||
int width = parameters.at(ID(WIDTH)).as_int();
|
||||
Const lut_table = parameters.at(ID(LUT));
|
||||
lut_table.extu(1 << width);
|
||||
return handle_bmux(factory.constant(lut_table), inputs.at(ID(A)), 0, 1, width);
|
||||
} else if (cellType == ID($bwmux)) {
|
||||
Node a = inputs.at(ID(A));
|
||||
Node b = inputs.at(ID(B));
|
||||
Node s = inputs.at(ID(S));
|
||||
return factory.bitwise_or(
|
||||
factory.bitwise_and(a, factory.bitwise_not(s)),
|
||||
factory.bitwise_and(b, s));
|
||||
} else if (cellType == ID($bweqx)) {
|
||||
Node a = inputs.at(ID(A));
|
||||
Node b = inputs.at(ID(B));
|
||||
return factory.bitwise_not(factory.bitwise_xor(a, b));
|
||||
} else if(cellType == ID($bmux)) {
|
||||
int width = parameters.at(ID(WIDTH)).as_int();
|
||||
int s_width = parameters.at(ID(S_WIDTH)).as_int();
|
||||
return handle_bmux(inputs.at(ID(A)), inputs.at(ID(S)), 0, width, s_width);
|
||||
} else if(cellType == ID($demux)) {
|
||||
int width = parameters.at(ID(WIDTH)).as_int();
|
||||
int s_width = parameters.at(ID(S_WIDTH)).as_int();
|
||||
int y_width = width << s_width;
|
||||
int b_width = ceil_log2(y_width);
|
||||
Node a = factory.extend(inputs.at(ID(A)), y_width, false);
|
||||
Node s = factory.extend(inputs.at(ID(S)), b_width, false);
|
||||
Node b = factory.mul(s, factory.constant(Const(width, b_width)));
|
||||
return factory.logical_shift_left(a, b);
|
||||
} else if(cellType == ID($fa)) {
|
||||
return handle_fa(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(C)));
|
||||
} else if(cellType == ID($lcu)) {
|
||||
return handle_lcu(inputs.at(ID(P)), inputs.at(ID(G)), inputs.at(ID(CI)));
|
||||
} else if(cellType == ID($alu)) {
|
||||
return handle_alu(inputs.at(ID(A)), inputs.at(ID(B)), y_width, a_signed && b_signed, inputs.at(ID(CI)), inputs.at(ID(BI)));
|
||||
} else if(cellType.in({ID($assert), ID($assume), ID($live), ID($fair), ID($cover)})) {
|
||||
Node a = factory.mux(factory.constant(Const(State::S1, 1)), inputs.at(ID(A)), inputs.at(ID(EN)));
|
||||
auto &output = factory.add_output(cellName, cellType, Sort(1));
|
||||
output.set_value(a);
|
||||
return {};
|
||||
} else if(cellType.in({ID($anyconst), ID($allconst), ID($anyseq), ID($allseq)})) {
|
||||
int width = parameters.at(ID(WIDTH)).as_int();
|
||||
auto &input = factory.add_input(cellName, cellType, Sort(width));
|
||||
return factory.value(input);
|
||||
} else if(cellType == ID($initstate)) {
|
||||
if(factory.ir().has_state(ID($initstate), ID($state)))
|
||||
return factory.value(factory.ir().state(ID($initstate)));
|
||||
else {
|
||||
auto &state = factory.add_state(ID($initstate), ID($state), Sort(1));
|
||||
state.set_initial_value(RTLIL::Const(State::S1, 1));
|
||||
state.set_next_value(factory.constant(RTLIL::Const(State::S0, 1)));
|
||||
return factory.value(state);
|
||||
}
|
||||
} else if(cellType == ID($check)) {
|
||||
log_error("The design contains a $check cell `%s'. This is not supported by the functional backend. Call `chformal -lower' to avoid this error.\n", cellName.c_str());
|
||||
} else {
|
||||
log_error("`%s' cells are not supported by the functional backend\n", cellType.c_str());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class FunctionalIRConstruction {
|
||||
std::deque<std::variant<DriveSpec, Cell *>> queue;
|
||||
dict<DriveSpec, Node> graph_nodes;
|
||||
dict<std::pair<Cell *, IdString>, Node> cell_outputs;
|
||||
DriverMap driver_map;
|
||||
Factory& factory;
|
||||
CellSimplifier simplifier;
|
||||
vector<Mem> memories_vector;
|
||||
dict<Cell*, Mem*> memories;
|
||||
SigMap sig_map; // TODO: this is only for FfInitVals, remove this once FfInitVals supports DriverMap
|
||||
FfInitVals ff_initvals;
|
||||
|
||||
Node enqueue(DriveSpec const &spec)
|
||||
{
|
||||
auto it = graph_nodes.find(spec);
|
||||
if(it == graph_nodes.end()){
|
||||
auto node = factory.create_pending(spec.size());
|
||||
graph_nodes.insert({spec, node});
|
||||
queue.emplace_back(spec);
|
||||
return node;
|
||||
}else
|
||||
return it->second;
|
||||
}
|
||||
Node enqueue_cell(Cell *cell, IdString port_name)
|
||||
{
|
||||
auto it = cell_outputs.find({cell, port_name});
|
||||
if(it == cell_outputs.end()) {
|
||||
queue.emplace_back(cell);
|
||||
std::optional<Node> rv;
|
||||
for(auto const &[name, sigspec] : cell->connections())
|
||||
if(driver_map.celltypes.cell_output(cell->type, name)) {
|
||||
auto node = factory.create_pending(sigspec.size());
|
||||
factory.suggest_name(node, cell->name.str() + "$" + name.str());
|
||||
cell_outputs.emplace({cell, name}, node);
|
||||
if(name == port_name)
|
||||
rv = node;
|
||||
}
|
||||
return *rv;
|
||||
} else
|
||||
return it->second;
|
||||
}
|
||||
public:
|
||||
FunctionalIRConstruction(Module *module, Factory &f)
|
||||
: factory(f)
|
||||
, simplifier(f)
|
||||
, sig_map(module)
|
||||
, ff_initvals(&sig_map, module)
|
||||
{
|
||||
driver_map.add(module);
|
||||
for (auto cell : module->cells()) {
|
||||
if (cell->type.in(ID($assert), ID($assume), ID($live), ID($fair), ID($cover), ID($check)))
|
||||
queue.emplace_back(cell);
|
||||
}
|
||||
for (auto wire : module->wires()) {
|
||||
if (wire->port_input)
|
||||
factory.add_input(wire->name, ID($input), Sort(wire->width));
|
||||
if (wire->port_output) {
|
||||
auto &output = factory.add_output(wire->name, ID($output), Sort(wire->width));
|
||||
output.set_value(enqueue(DriveChunk(DriveChunkWire(wire, 0, wire->width))));
|
||||
}
|
||||
}
|
||||
memories_vector = Mem::get_all_memories(module);
|
||||
for (auto &mem : memories_vector) {
|
||||
if (mem.cell != nullptr)
|
||||
memories[mem.cell] = &mem;
|
||||
}
|
||||
}
|
||||
private:
|
||||
Node concatenate_read_results(Mem *mem, vector<Node> results)
|
||||
{
|
||||
// sanity check: all read ports concatenated should equal to the RD_DATA port
|
||||
const SigSpec &rd_data = mem->cell->connections().at(ID(RD_DATA));
|
||||
int current = 0;
|
||||
for(size_t i = 0; i < mem->rd_ports.size(); i++) {
|
||||
int width = mem->width << mem->rd_ports[i].wide_log2;
|
||||
log_assert (results[i].width() == width);
|
||||
log_assert (mem->rd_ports[i].data == rd_data.extract(current, width));
|
||||
current += width;
|
||||
}
|
||||
log_assert (current == rd_data.size());
|
||||
log_assert (!results.empty());
|
||||
Node node = results[0];
|
||||
for(size_t i = 1; i < results.size(); i++)
|
||||
node = factory.concat(node, results[i]);
|
||||
return node;
|
||||
}
|
||||
Node handle_memory(Mem *mem)
|
||||
{
|
||||
// To simplify memory handling, the functional backend makes the following assumptions:
|
||||
// - Since async2sync or clk2fflogic must be run to use the functional backend,
|
||||
// we can assume that all ports are asynchronous.
|
||||
// - Async rd/wr are always transparent and so we must do reads after writes,
|
||||
// but we can ignore transparency_mask.
|
||||
// - We ignore collision_x_mask because x is a dont care value for us anyway.
|
||||
// - 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.
|
||||
vector<Node> read_results;
|
||||
auto &state = factory.add_state(mem->cell->name, ID($state), Sort(ceil_log2(mem->size), mem->width));
|
||||
state.set_initial_value(MemContents(mem));
|
||||
Node node = factory.value(state);
|
||||
for (size_t i = 0; i < mem->wr_ports.size(); i++) {
|
||||
const auto &wr = mem->wr_ports[i];
|
||||
if (wr.clk_enable)
|
||||
log_error("Write port %zd of memory %s.%s is clocked. This is not supported by the functional backend. "
|
||||
"Call async2sync or clk2fflogic to avoid this error.\n", i, log_id(mem->module), log_id(mem->memid));
|
||||
Node en = enqueue(driver_map(DriveSpec(wr.en)));
|
||||
Node addr = enqueue(driver_map(DriveSpec(wr.addr)));
|
||||
Node new_data = enqueue(driver_map(DriveSpec(wr.data)));
|
||||
Node old_data = factory.memory_read(node, addr);
|
||||
Node wr_data = simplifier.bitwise_mux(old_data, new_data, en);
|
||||
node = factory.memory_write(node, addr, wr_data);
|
||||
}
|
||||
if (mem->rd_ports.empty())
|
||||
log_error("Memory %s.%s has no read ports. This is not supported by the functional backend. "
|
||||
"Call opt_clean to remove it.", log_id(mem->module), log_id(mem->memid));
|
||||
for (size_t i = 0; i < mem->rd_ports.size(); i++) {
|
||||
const auto &rd = mem->rd_ports[i];
|
||||
if (rd.clk_enable)
|
||||
log_error("Read port %zd of memory %s.%s is clocked. This is not supported by the functional backend. "
|
||||
"Call memory_nordff to avoid this error.\n", i, log_id(mem->module), log_id(mem->memid));
|
||||
Node addr = enqueue(driver_map(DriveSpec(rd.addr)));
|
||||
read_results.push_back(factory.memory_read(node, addr));
|
||||
}
|
||||
state.set_next_value(node);
|
||||
return concatenate_read_results(mem, read_results);
|
||||
}
|
||||
void process_cell(Cell *cell)
|
||||
{
|
||||
if (cell->is_mem_cell()) {
|
||||
Mem *mem = memories.at(cell, nullptr);
|
||||
if (mem == nullptr) {
|
||||
log_assert(cell->has_memid());
|
||||
log_error("The design contains an unpacked memory at %s. This is not supported by the functional backend. "
|
||||
"Call memory_collect to avoid this error.\n", log_const(cell->parameters.at(ID(MEMID))));
|
||||
}
|
||||
Node node = handle_memory(mem);
|
||||
factory.update_pending(cell_outputs.at({cell, ID(RD_DATA)}), node);
|
||||
} else if (RTLIL::builtin_ff_cell_types().count(cell->type)) {
|
||||
FfData ff(&ff_initvals, cell);
|
||||
if (!ff.has_gclk)
|
||||
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));
|
||||
auto &state = factory.add_state(ff.name, ID($state), Sort(ff.width));
|
||||
Node q_value = factory.value(state);
|
||||
factory.suggest_name(q_value, ff.name);
|
||||
factory.update_pending(cell_outputs.at({cell, ID(Q)}), q_value);
|
||||
state.set_next_value(enqueue(ff.sig_d));
|
||||
state.set_initial_value(ff.val_init);
|
||||
} else {
|
||||
dict<IdString, Node> connections;
|
||||
IdString output_name; // for the single output case
|
||||
int n_outputs = 0;
|
||||
for(auto const &[name, sigspec] : cell->connections()) {
|
||||
if(driver_map.celltypes.cell_input(cell->type, name) && sigspec.size() > 0)
|
||||
connections.insert({ name, enqueue(DriveChunkPort(cell, {name, sigspec})) });
|
||||
if(driver_map.celltypes.cell_output(cell->type, name)) {
|
||||
output_name = name;
|
||||
n_outputs++;
|
||||
}
|
||||
}
|
||||
std::variant<dict<IdString, Node>, Node> outputs = simplifier.handle(cell->name, cell->type, cell->parameters, connections);
|
||||
if(auto *nodep = std::get_if<Node>(&outputs); nodep != nullptr) {
|
||||
log_assert(n_outputs == 1);
|
||||
factory.update_pending(cell_outputs.at({cell, output_name}), *nodep);
|
||||
} else {
|
||||
for(auto [name, node] : std::get<dict<IdString, Node>>(outputs))
|
||||
factory.update_pending(cell_outputs.at({cell, name}), node);
|
||||
}
|
||||
}
|
||||
}
|
||||
void undriven(const char *name) {
|
||||
log_error("The design contains an undriven signal %s. This is not supported by the functional backend. "
|
||||
"Call setundef with appropriate options to avoid this error.\n", name);
|
||||
}
|
||||
// we perform this check separately to give better error messages that include the wire or port name
|
||||
void check_undriven(DriveSpec const& spec, std::string const& name) {
|
||||
for(auto const &chunk : spec.chunks())
|
||||
if(chunk.is_none())
|
||||
undriven(name.c_str());
|
||||
}
|
||||
public:
|
||||
void process_queue()
|
||||
{
|
||||
for (; !queue.empty(); queue.pop_front()) {
|
||||
if(auto p = std::get_if<Cell *>(&queue.front()); p != nullptr) {
|
||||
process_cell(*p);
|
||||
continue;
|
||||
}
|
||||
|
||||
DriveSpec spec = std::get<DriveSpec>(queue.front());
|
||||
Node pending = graph_nodes.at(spec);
|
||||
|
||||
if (spec.chunks().size() > 1) {
|
||||
auto chunks = spec.chunks();
|
||||
Node node = enqueue(chunks[0]);
|
||||
for(size_t i = 1; i < chunks.size(); i++)
|
||||
node = factory.concat(node, enqueue(chunks[i]));
|
||||
factory.update_pending(pending, node);
|
||||
} else if (spec.chunks().size() == 1) {
|
||||
DriveChunk chunk = spec.chunks()[0];
|
||||
if (chunk.is_wire()) {
|
||||
DriveChunkWire wire_chunk = chunk.wire();
|
||||
if (wire_chunk.is_whole()) {
|
||||
if (wire_chunk.wire->port_input) {
|
||||
Node node = factory.value(factory.ir().input(wire_chunk.wire->name));
|
||||
factory.suggest_name(node, wire_chunk.wire->name);
|
||||
factory.update_pending(pending, node);
|
||||
} else {
|
||||
DriveSpec driver = driver_map(DriveSpec(wire_chunk));
|
||||
check_undriven(driver, RTLIL::unescape_id(wire_chunk.wire->name));
|
||||
Node node = enqueue(driver);
|
||||
factory.suggest_name(node, wire_chunk.wire->name);
|
||||
factory.update_pending(pending, node);
|
||||
}
|
||||
} else {
|
||||
DriveChunkWire whole_wire(wire_chunk.wire, 0, wire_chunk.wire->width);
|
||||
Node node = factory.slice(enqueue(whole_wire), wire_chunk.offset, wire_chunk.width);
|
||||
factory.update_pending(pending, node);
|
||||
}
|
||||
} else if (chunk.is_port()) {
|
||||
DriveChunkPort port_chunk = chunk.port();
|
||||
if (port_chunk.is_whole()) {
|
||||
if (driver_map.celltypes.cell_output(port_chunk.cell->type, port_chunk.port)) {
|
||||
Node node = enqueue_cell(port_chunk.cell, port_chunk.port);
|
||||
factory.update_pending(pending, node);
|
||||
} else {
|
||||
DriveSpec driver = driver_map(DriveSpec(port_chunk));
|
||||
check_undriven(driver, RTLIL::unescape_id(port_chunk.cell->name) + " port " + RTLIL::unescape_id(port_chunk.port));
|
||||
factory.update_pending(pending, enqueue(driver));
|
||||
}
|
||||
} else {
|
||||
DriveChunkPort whole_port(port_chunk.cell, port_chunk.port, 0, GetSize(port_chunk.cell->connections().at(port_chunk.port)));
|
||||
Node node = factory.slice(enqueue(whole_port), port_chunk.offset, port_chunk.width);
|
||||
factory.update_pending(pending, node);
|
||||
}
|
||||
} else if (chunk.is_constant()) {
|
||||
Node node = factory.constant(chunk.constant());
|
||||
factory.suggest_name(node, "$const" + std::to_string(chunk.size()) + "b" + chunk.constant().as_string());
|
||||
factory.update_pending(pending, node);
|
||||
} else if (chunk.is_multiple()) {
|
||||
log_error("Signal %s has multiple drivers. This is not supported by the functional backend. "
|
||||
"If tristate drivers are used, call tristate -formal to avoid this error.\n", log_signal(chunk));
|
||||
} else if (chunk.is_none()) {
|
||||
undriven(log_signal(chunk));
|
||||
} else {
|
||||
log_error("unhandled drivespec: %s\n", log_signal(chunk));
|
||||
log_abort();
|
||||
}
|
||||
} else {
|
||||
log_abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IR IR::from_module(Module *module) {
|
||||
IR ir;
|
||||
auto factory = ir.factory();
|
||||
FunctionalIRConstruction ctor(module, factory);
|
||||
ctor.process_queue();
|
||||
ir.topological_sort();
|
||||
ir.forward_buf();
|
||||
return ir;
|
||||
}
|
||||
|
||||
void IR::topological_sort() {
|
||||
Graph::SccAdaptor compute_graph_scc(_graph);
|
||||
bool scc = false;
|
||||
std::vector<int> perm;
|
||||
TopoSortedSccs toposort(compute_graph_scc, [&](int *begin, int *end) {
|
||||
perm.insert(perm.end(), begin, end);
|
||||
if (end > begin + 1)
|
||||
{
|
||||
log_warning("Combinational loop:\n");
|
||||
for (int *i = begin; i != end; ++i) {
|
||||
Node node(_graph[*i]);
|
||||
log("- %s = %s\n", RTLIL::unescape_id(node.name()).c_str(), node.to_string().c_str());
|
||||
}
|
||||
log("\n");
|
||||
scc = true;
|
||||
}
|
||||
});
|
||||
for(const auto &[name, state]: _states)
|
||||
if(state.has_next_value())
|
||||
toposort.process(state.next_value().id());
|
||||
for(const auto &[name, output]: _outputs)
|
||||
if(output.has_value())
|
||||
toposort.process(output.value().id());
|
||||
// any nodes untouched by this point are dead code and will be removed by permute
|
||||
_graph.permute(perm);
|
||||
if(scc) log_error("The design contains combinational loops. This is not supported by the functional backend. "
|
||||
"Try `scc -select; simplemap; select -clear` to avoid this error.\n");
|
||||
}
|
||||
|
||||
static IdString merge_name(IdString a, IdString b) {
|
||||
if(a[0] == '$' && b[0] == '\\')
|
||||
return b;
|
||||
else
|
||||
return a;
|
||||
}
|
||||
|
||||
void IR::forward_buf() {
|
||||
std::vector<int> perm, alias;
|
||||
perm.clear();
|
||||
|
||||
for (int i = 0; i < _graph.size(); ++i)
|
||||
{
|
||||
auto node = _graph[i];
|
||||
if (node.function().fn() == Fn::buf && node.arg(0).index() < i)
|
||||
{
|
||||
int target_index = alias[node.arg(0).index()];
|
||||
auto target_node = _graph[perm[target_index]];
|
||||
if(node.has_sparse_attr()) {
|
||||
if(target_node.has_sparse_attr()) {
|
||||
IdString id = merge_name(node.sparse_attr(), target_node.sparse_attr());
|
||||
target_node.sparse_attr() = id;
|
||||
} else {
|
||||
IdString id = node.sparse_attr();
|
||||
target_node.sparse_attr() = id;
|
||||
}
|
||||
}
|
||||
alias.push_back(target_index);
|
||||
}
|
||||
else
|
||||
{
|
||||
alias.push_back(GetSize(perm));
|
||||
perm.push_back(i);
|
||||
}
|
||||
}
|
||||
_graph.permute(perm, alias);
|
||||
}
|
||||
|
||||
// Quoting routine to make error messages nicer
|
||||
static std::string quote_fmt(const char *fmt)
|
||||
{
|
||||
std::string r;
|
||||
for(const char *p = fmt; *p != 0; p++) {
|
||||
switch(*p) {
|
||||
case '\n': r += "\\n"; break;
|
||||
case '\t': r += "\\t"; break;
|
||||
case '"': r += "\\\""; break;
|
||||
case '\\': r += "\\\\"; break;
|
||||
default: r += *p; break;
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
void Writer::print_impl(const char *fmt, vector<std::function<void()>> &fns)
|
||||
{
|
||||
size_t next_index = 0;
|
||||
for(const char *p = fmt; *p != 0; p++)
|
||||
switch(*p) {
|
||||
case '{':
|
||||
if(*++p == '{') {
|
||||
*os << '{';
|
||||
} else {
|
||||
char *pe;
|
||||
size_t index = strtoul(p, &pe, 10);
|
||||
if(*pe != '}')
|
||||
log_error("invalid format string: expected {<number>}, {} or {{, got \"%s\": \"%s\"\n",
|
||||
quote_fmt(std::string(p - 1, pe - p + 2).c_str()).c_str(),
|
||||
quote_fmt(fmt).c_str());
|
||||
if(p == pe)
|
||||
index = next_index;
|
||||
else
|
||||
p = pe;
|
||||
if(index >= fns.size())
|
||||
log_error("invalid format string: index %zu out of bounds (%zu): \"%s\"\n", index, fns.size(), quote_fmt(fmt).c_str());
|
||||
fns[index]();
|
||||
next_index = index + 1;
|
||||
}
|
||||
break;
|
||||
case '}':
|
||||
p++;
|
||||
if(*p != '}')
|
||||
log_error("invalid format string: unescaped }: \"%s\"\n", quote_fmt(fmt).c_str());
|
||||
*os << '}';
|
||||
break;
|
||||
default:
|
||||
*os << *p;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
YOSYS_NAMESPACE_END
|
|
@ -0,0 +1,642 @@
|
|||
/*
|
||||
* yosys -- Yosys Open SYnthesis Suite
|
||||
*
|
||||
* Copyright (C) 2024 Emily Schmidt <emily@yosyshq.com>
|
||||
* Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC
|
||||
*
|
||||
* 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 FUNCTIONAL_H
|
||||
#define FUNCTIONAL_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
|
||||
|
||||
namespace Functional {
|
||||
// each function is documented with a short pseudocode declaration or definition
|
||||
// standard C/Verilog operators are used to describe the result
|
||||
//
|
||||
// the sorts 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 sort is unconstrained
|
||||
//
|
||||
// nodes in the functional backend are either of sort bit[N] or memory[N,M] (for some N, M: int)
|
||||
// additionally, they can carry a constant of sort int, Const[N] or IdString
|
||||
// each node has a 'sort' field that stores the sort of the node
|
||||
// slice, zero_extend, sign_extend use the sort 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 Fn value, as a string literal
|
||||
const char *fn_to_string(Fn);
|
||||
// Sort represents the sort or type of a node
|
||||
// currently the only two sorts 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 sort, errors out for other sorts
|
||||
int width() const { return std::get<0>(_v); }
|
||||
// returns the address width of a bitvector sort, errors out for other sorts
|
||||
int addr_width() const { return std::get<1>(_v).first; }
|
||||
// returns the data width of a bitvector sort, errors out for other sorts
|
||||
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); }
|
||||
};
|
||||
class IR;
|
||||
class Factory;
|
||||
class Node;
|
||||
class IRInput {
|
||||
friend class Factory;
|
||||
public:
|
||||
IdString name;
|
||||
IdString kind;
|
||||
Sort sort;
|
||||
private:
|
||||
IRInput(IR &, IdString name, IdString kind, Sort sort)
|
||||
: name(name), kind(kind), sort(std::move(sort)) {}
|
||||
};
|
||||
class IROutput {
|
||||
friend class Factory;
|
||||
IR &_ir;
|
||||
public:
|
||||
IdString name;
|
||||
IdString kind;
|
||||
Sort sort;
|
||||
private:
|
||||
IROutput(IR &ir, IdString name, IdString kind, Sort sort)
|
||||
: _ir(ir), name(name), kind(kind), sort(std::move(sort)) {}
|
||||
public:
|
||||
Node value() const;
|
||||
bool has_value() const;
|
||||
void set_value(Node value);
|
||||
};
|
||||
class IRState {
|
||||
friend class Factory;
|
||||
IR &_ir;
|
||||
public:
|
||||
IdString name;
|
||||
IdString kind;
|
||||
Sort sort;
|
||||
private:
|
||||
std::variant<RTLIL::Const, MemContents> _initial;
|
||||
IRState(IR &ir, IdString name, IdString kind, Sort sort)
|
||||
: _ir(ir), name(name), kind(kind), sort(std::move(sort)) {}
|
||||
public:
|
||||
Node next_value() const;
|
||||
bool has_next_value() const;
|
||||
RTLIL::Const const& initial_value_signal() const { return std::get<RTLIL::Const>(_initial); }
|
||||
MemContents const& initial_value_memory() const { return std::get<MemContents>(_initial); }
|
||||
void set_next_value(Node value);
|
||||
void set_initial_value(RTLIL::Const value) { value.extu(sort.width()); _initial = std::move(value); }
|
||||
void set_initial_value(MemContents value) { log_assert(Sort(value.addr_width(), value.data_width()) == sort); _initial = std::move(value); }
|
||||
};
|
||||
class IR {
|
||||
friend class Factory;
|
||||
friend class Node;
|
||||
friend class IRInput;
|
||||
friend class IROutput;
|
||||
friend class IRState;
|
||||
// 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,
|
||||
std::pair<IdString, 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); }
|
||||
std::pair<IdString, IdString> as_idstring_pair() const { return std::get<std::pair<IdString, 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::tuple<IdString, IdString, bool>>;
|
||||
Graph _graph;
|
||||
dict<std::pair<IdString, IdString>, IRInput> _inputs;
|
||||
dict<std::pair<IdString, IdString>, IROutput> _outputs;
|
||||
dict<std::pair<IdString, IdString>, IRState> _states;
|
||||
IR::Graph::Ref mutate(Node n);
|
||||
public:
|
||||
static IR from_module(Module *module);
|
||||
Factory factory();
|
||||
int size() const { return _graph.size(); }
|
||||
Node operator[](int i);
|
||||
void topological_sort();
|
||||
void forward_buf();
|
||||
IRInput const& input(IdString name, IdString kind) const { return _inputs.at({name, kind}); }
|
||||
IRInput const& input(IdString name) const { return input(name, ID($input)); }
|
||||
IROutput const& output(IdString name, IdString kind) const { return _outputs.at({name, kind}); }
|
||||
IROutput const& output(IdString name) const { return output(name, ID($output)); }
|
||||
IRState const& state(IdString name, IdString kind) const { return _states.at({name, kind}); }
|
||||
IRState const& state(IdString name) const { return state(name, ID($state)); }
|
||||
bool has_input(IdString name, IdString kind) const { return _inputs.count({name, kind}); }
|
||||
bool has_output(IdString name, IdString kind) const { return _outputs.count({name, kind}); }
|
||||
bool has_state(IdString name, IdString kind) const { return _states.count({name, kind}); }
|
||||
vector<IRInput const*> inputs(IdString kind) const;
|
||||
vector<IRInput const*> inputs() const { return inputs(ID($input)); }
|
||||
vector<IROutput const*> outputs(IdString kind) const;
|
||||
vector<IROutput const*> outputs() const { return outputs(ID($output)); }
|
||||
vector<IRState const*> states(IdString kind) const;
|
||||
vector<IRState const*> states() const { return states(ID($state)); }
|
||||
vector<IRInput const*> all_inputs() const;
|
||||
vector<IROutput const*> all_outputs() const;
|
||||
vector<IRState const*> all_states() const;
|
||||
class iterator {
|
||||
friend class IR;
|
||||
IR *_ir;
|
||||
int _index;
|
||||
iterator(IR *ir, int index) : _ir(ir), _index(index) {}
|
||||
public:
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
using value_type = Node;
|
||||
using pointer = arrow_proxy<Node>;
|
||||
using reference = Node;
|
||||
using difference_type = ptrdiff_t;
|
||||
Node operator*();
|
||||
iterator &operator++() { _index++; return *this; }
|
||||
bool operator!=(iterator const &other) const { return _ir != other._ir || _index != other._index; }
|
||||
bool operator==(iterator const &other) const { return !(*this != other); }
|
||||
pointer operator->();
|
||||
};
|
||||
iterator begin() { return iterator(this, 0); }
|
||||
iterator end() { return iterator(this, _graph.size()); }
|
||||
};
|
||||
// Node is an immutable reference to a FunctionalIR node
|
||||
class Node {
|
||||
friend class Factory;
|
||||
friend class IR;
|
||||
friend class IRInput;
|
||||
friend class IROutput;
|
||||
friend class IRState;
|
||||
IR::Graph::ConstRef _ref;
|
||||
explicit Node(IR::Graph::ConstRef ref) : _ref(ref) { }
|
||||
explicit operator IR::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_pair().first, _ref.function().as_idstring_pair().second); break;
|
||||
case Fn::state: return v.state(*this, _ref.function().as_idstring_pair().first, _ref.function().as_idstring_pair().second); 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;
|
||||
}
|
||||
log_abort();
|
||||
}
|
||||
std::string to_string();
|
||||
std::string to_string(std::function<std::string(Node)>);
|
||||
};
|
||||
inline IR::Graph::Ref IR::mutate(Node n) { return _graph[n._ref.index()]; }
|
||||
inline Node IR::operator[](int i) { return Node(_graph[i]); }
|
||||
inline Node IROutput::value() const { return Node(_ir._graph({name, kind, false})); }
|
||||
inline bool IROutput::has_value() const { return _ir._graph.has_key({name, kind, false}); }
|
||||
inline void IROutput::set_value(Node value) { log_assert(sort == value.sort()); _ir.mutate(value).assign_key({name, kind, false}); }
|
||||
inline Node IRState::next_value() const { return Node(_ir._graph({name, kind, true})); }
|
||||
inline bool IRState::has_next_value() const { return _ir._graph.has_key({name, kind, true}); }
|
||||
inline void IRState::set_next_value(Node value) { log_assert(sort == value.sort()); _ir.mutate(value).assign_key({name, kind, true}); }
|
||||
inline Node IR::iterator::operator*() { return Node(_ir->_graph[_index]); }
|
||||
inline arrow_proxy<Node> IR::iterator::operator->() { return arrow_proxy<Node>(**this); }
|
||||
// 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, IdString kind) = 0;
|
||||
virtual T state(Node self, IdString name, IdString kind) = 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, IdString) override { return default_handler(self); }
|
||||
T state(Node self, IdString, 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 {
|
||||
friend class IR;
|
||||
IR &_ir;
|
||||
explicit Factory(IR &ir) : _ir(ir) {}
|
||||
Node add(IR::NodeData &&fn, Sort const &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));
|
||||
IR::Graph::Ref ref = _ir._graph.add(std::move(fn), {std::move(sort)});
|
||||
for (auto arg : args)
|
||||
ref.append_arg(IR::Graph::ConstRef(arg));
|
||||
return Node(ref);
|
||||
}
|
||||
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:
|
||||
IR &ir() { return _ir; }
|
||||
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());
|
||||
_ir.mutate(node).append_arg(value._ref);
|
||||
}
|
||||
IRInput &add_input(IdString name, IdString kind, Sort sort) {
|
||||
auto [it, inserted] = _ir._inputs.emplace({name, kind}, IRInput(_ir, name, kind, std::move(sort)));
|
||||
if (!inserted) log_error("input `%s` was re-defined", name.c_str());
|
||||
return it->second;
|
||||
}
|
||||
IROutput &add_output(IdString name, IdString kind, Sort sort) {
|
||||
auto [it, inserted] = _ir._outputs.emplace({name, kind}, IROutput(_ir, name, kind, std::move(sort)));
|
||||
if (!inserted) log_error("output `%s` was re-defined", name.c_str());
|
||||
return it->second;
|
||||
}
|
||||
IRState &add_state(IdString name, IdString kind, Sort sort) {
|
||||
auto [it, inserted] = _ir._states.emplace({name, kind}, IRState(_ir, name, kind, std::move(sort)));
|
||||
if (!inserted) log_error("state `%s` was re-defined", name.c_str());
|
||||
return it->second;
|
||||
}
|
||||
Node value(IRInput const& input) {
|
||||
return add(IR::NodeData(Fn::input, std::pair(input.name, input.kind)), input.sort, {});
|
||||
}
|
||||
Node value(IRState const& state) {
|
||||
return add(IR::NodeData(Fn::state, std::pair(state.name, state.kind)), state.sort, {});
|
||||
}
|
||||
void suggest_name(Node node, IdString name) {
|
||||
_ir.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, int) = 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], 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
|
|
@ -186,6 +186,37 @@ inline unsigned int mkhash(const T &v) {
|
|||
return hash_ops<T>().hash(v);
|
||||
}
|
||||
|
||||
template<> struct hash_ops<std::monostate> {
|
||||
static inline bool cmp(std::monostate a, std::monostate b) {
|
||||
return a == b;
|
||||
}
|
||||
static inline unsigned int hash(std::monostate) {
|
||||
return mkhash_init;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... T> struct hash_ops<std::variant<T...>> {
|
||||
static inline bool cmp(std::variant<T...> a, std::variant<T...> b) {
|
||||
return a == b;
|
||||
}
|
||||
static inline unsigned int hash(std::variant<T...> a) {
|
||||
unsigned int h = std::visit([](const auto &v) { return mkhash(v); }, a);
|
||||
return mkhash(a.index(), h);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T> struct hash_ops<std::optional<T>> {
|
||||
static inline bool cmp(std::optional<T> a, std::optional<T> b) {
|
||||
return a == b;
|
||||
}
|
||||
static inline unsigned int hash(std::optional<T> a) {
|
||||
if(a.has_value())
|
||||
return mkhash(*a);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
inline int hashtable_size(int min_size)
|
||||
{
|
||||
// Primes as generated by https://oeis.org/A175953
|
||||
|
|
|
@ -662,6 +662,16 @@ const char *log_id(const RTLIL::IdString &str)
|
|||
return p+1;
|
||||
}
|
||||
|
||||
const char *log_str(const char *str)
|
||||
{
|
||||
log_id_cache.push_back(strdup(str));
|
||||
return log_id_cache.back();
|
||||
}
|
||||
|
||||
const char *log_str(std::string const &str) {
|
||||
return log_str(str.c_str());
|
||||
}
|
||||
|
||||
void log_module(RTLIL::Module *module, std::string indent)
|
||||
{
|
||||
std::stringstream buf;
|
||||
|
|
|
@ -206,6 +206,8 @@ void log_check_expected();
|
|||
const char *log_signal(const RTLIL::SigSpec &sig, bool autoint = true);
|
||||
const char *log_const(const RTLIL::Const &value, bool autoint = true);
|
||||
const char *log_id(const RTLIL::IdString &id);
|
||||
const char *log_str(const char *str);
|
||||
const char *log_str(std::string const &str);
|
||||
|
||||
template<typename T> static inline const char *log_id(T *obj, const char *nullstr = nullptr) {
|
||||
if (nullstr && obj == nullptr)
|
||||
|
|
216
kernel/mem.cc
216
kernel/mem.cc
|
@ -1679,3 +1679,219 @@ SigSpec MemWr::decompress_en(const std::vector<int> &swizzle, SigSpec sig) {
|
|||
res.append(sig[i]);
|
||||
return res;
|
||||
}
|
||||
|
||||
using addr_t = MemContents::addr_t;
|
||||
|
||||
MemContents::MemContents(Mem *mem) :
|
||||
MemContents(ceil_log2(mem->size), mem->width)
|
||||
{
|
||||
for(const auto &init : mem->inits) {
|
||||
if(init.en.is_fully_zero()) continue;
|
||||
log_assert(init.en.size() == _data_width);
|
||||
if(init.en.is_fully_ones())
|
||||
insert_concatenated(init.addr.as_int(), init.data);
|
||||
else {
|
||||
// TODO: this case could be handled more efficiently by adding
|
||||
// a flag to reserve_range that tells it to preserve
|
||||
// previous contents
|
||||
addr_t addr = init.addr.as_int();
|
||||
addr_t words = init.data.size() / _data_width;
|
||||
RTLIL::Const data = init.data;
|
||||
log_assert(data.size() % _data_width == 0);
|
||||
for(addr_t i = 0; i < words; i++) {
|
||||
RTLIL::Const previous = (*this)[addr + i];
|
||||
for(int j = 0; j < _data_width; j++)
|
||||
if(init.en[j] != State::S1)
|
||||
data[_data_width * i + j] = previous[j];
|
||||
}
|
||||
insert_concatenated(init.addr.as_int(), data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MemContents::iterator & MemContents::iterator::operator++() {
|
||||
auto it = _memory->_values.upper_bound(_addr);
|
||||
if(it == _memory->_values.end()) {
|
||||
_memory = nullptr;
|
||||
_addr = ~(addr_t) 0;
|
||||
} else
|
||||
_addr = it->first;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void MemContents::check() {
|
||||
log_assert(_addr_width > 0 && _addr_width < (int)sizeof(addr_t) * 8);
|
||||
log_assert(_data_width > 0);
|
||||
log_assert(_default_value.size() == _data_width);
|
||||
if(_values.empty()) return;
|
||||
auto it = _values.begin();
|
||||
for(;;) {
|
||||
log_assert(!it->second.empty());
|
||||
log_assert(it->second.size() % _data_width == 0);
|
||||
auto end1 = _range_end(it);
|
||||
log_assert(_range_begin(it) < (addr_t)(1<<_addr_width));
|
||||
log_assert(end1 <= (addr_t)(1<<_addr_width));
|
||||
if(++it == _values.end())
|
||||
break;
|
||||
// check that ranges neither overlap nor touch
|
||||
log_assert(_range_begin(it) > end1);
|
||||
}
|
||||
}
|
||||
|
||||
bool MemContents::_range_contains(std::map<addr_t, RTLIL::Const>::iterator it, addr_t addr) const {
|
||||
// if addr < begin, the subtraction will overflow, and the comparison will always fail
|
||||
// (since we have an invariant that begin + size <= 2^(addr_t bits))
|
||||
return it != _values.end() && addr - _range_begin(it) < _range_size(it);
|
||||
}
|
||||
|
||||
|
||||
bool MemContents::_range_contains(std::map<addr_t, RTLIL::Const>::iterator it, addr_t begin_addr, addr_t end_addr) const {
|
||||
// note that we assume begin_addr <= end_addr
|
||||
return it != _values.end() && _range_begin(it) <= begin_addr && end_addr - _range_begin(it) <= _range_size(it);
|
||||
}
|
||||
|
||||
bool MemContents::_range_overlaps(std::map<addr_t, RTLIL::Const>::iterator it, addr_t begin_addr, addr_t end_addr) const {
|
||||
if(it == _values.end() || begin_addr >= end_addr)
|
||||
return false;
|
||||
auto top1 = _range_end(it) - 1;
|
||||
auto top2 = end_addr - 1;
|
||||
return !(top1 < begin_addr || top2 < _range_begin(it));
|
||||
}
|
||||
|
||||
std::map<addr_t, RTLIL::Const>::iterator MemContents::_range_at(addr_t addr) const {
|
||||
// allow addr == 1<<_addr_width (which will just return end())
|
||||
log_assert(addr <= (addr_t)(1<<_addr_width));
|
||||
// get the first range with base > addr
|
||||
// (we use const_cast since map::iterators are only passed around internally and not exposed to the user
|
||||
// and using map::iterator in both the const and non-const case simplifies the code a little,
|
||||
// at the cost of having to be a little careful when implementing const methods)
|
||||
auto it = const_cast<std::map<addr_t, RTLIL::Const> &>(_values).upper_bound(addr);
|
||||
// if we get the very first range, all ranges are past the addr, so return the first one
|
||||
if(it == _values.begin())
|
||||
return it;
|
||||
// otherwise, go back to the previous interval
|
||||
// this must be the last interval with base <= addr
|
||||
auto it_prev = std::next(it, -1);
|
||||
if(_range_contains(it_prev, addr))
|
||||
return it_prev;
|
||||
else
|
||||
return it;
|
||||
}
|
||||
|
||||
RTLIL::Const MemContents::operator[](addr_t addr) const {
|
||||
auto it = _range_at(addr);
|
||||
if(_range_contains(it, addr))
|
||||
return it->second.extract(_range_offset(it, addr), _data_width);
|
||||
else
|
||||
return _default_value;
|
||||
}
|
||||
|
||||
addr_t MemContents::count_range(addr_t begin_addr, addr_t end_addr) const {
|
||||
addr_t count = 0;
|
||||
for(auto it = _range_at(begin_addr); _range_overlaps(it, begin_addr, end_addr); it++) {
|
||||
auto first = std::max(_range_begin(it), begin_addr);
|
||||
auto last = std::min(_range_end(it), end_addr);
|
||||
count += last - first;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void MemContents::clear_range(addr_t begin_addr, addr_t end_addr) {
|
||||
if(begin_addr >= end_addr) return;
|
||||
// identify which ranges are affected by this operation
|
||||
// the first iterator affected is the first one containing any addr >= begin_addr
|
||||
auto begin_it = _range_at(begin_addr);
|
||||
// the first iterator *not* affected is the first one with base addr > end_addr - 1
|
||||
auto end_it = _values.upper_bound(end_addr - 1);
|
||||
if(begin_it == end_it)
|
||||
return; // nothing to do
|
||||
// the last iterator affected is one before the first one not affected
|
||||
auto last_it = std::next(end_it, -1);
|
||||
// the first and last range may need to be truncated, the rest can just be deleted
|
||||
// to handle the begin_it == last_it case correctly, do the end case first by inserting a new range past the end
|
||||
if(_range_contains(last_it, end_addr - 1)) {
|
||||
auto new_begin = end_addr;
|
||||
auto end = _range_end(last_it);
|
||||
// if there is data past the end address, preserve it by creating a new range
|
||||
if(new_begin != end)
|
||||
end_it = _values.emplace_hint(last_it, new_begin, last_it->second.extract(_range_offset(last_it, new_begin), (_range_end(last_it) - new_begin) * _data_width));
|
||||
// the original range will either be truncated in the next if() block or deleted in the erase, so we can leave it untruncated
|
||||
}
|
||||
if(_range_contains(begin_it, begin_addr)) {
|
||||
auto new_end = begin_addr;
|
||||
// if there is data before the start address, truncate but don't delete
|
||||
if(new_end != begin_it->first) {
|
||||
begin_it->second.extu(_range_offset(begin_it, new_end));
|
||||
++begin_it;
|
||||
}
|
||||
// else: begin_it will be deleted
|
||||
}
|
||||
_values.erase(begin_it, end_it);
|
||||
}
|
||||
|
||||
std::map<addr_t, RTLIL::Const>::iterator MemContents::_reserve_range(addr_t begin_addr, addr_t end_addr) {
|
||||
if(begin_addr >= end_addr)
|
||||
return _values.end(); // need a dummy value to return, end() is cheap
|
||||
// find the first range containing any addr >= begin_addr - 1
|
||||
auto lower_it = begin_addr == 0 ? _values.begin() : _range_at(begin_addr - 1);
|
||||
// check if our range is already covered by a single range
|
||||
// note that since ranges are not allowed to touch, if any range contains begin_addr, lower_it equals that range
|
||||
if (_range_contains(lower_it, begin_addr, end_addr))
|
||||
return lower_it;
|
||||
// find the first range containing any addr >= end_addr
|
||||
auto upper_it = _range_at(end_addr);
|
||||
// check if either of the two ranges we just found touch our range
|
||||
bool lower_touch = begin_addr > 0 && _range_contains(lower_it, begin_addr - 1);
|
||||
bool upper_touch = _range_contains(upper_it, end_addr);
|
||||
if (lower_touch && upper_touch) {
|
||||
log_assert (lower_it != upper_it); // lower_it == upper_it should be excluded by the check above
|
||||
// we have two different ranges touching at either end, we need to merge them
|
||||
auto upper_end = _range_end(upper_it);
|
||||
// make range bigger (maybe reserve here instead of resize?)
|
||||
lower_it->second.bits.resize(_range_offset(lower_it, upper_end), State::Sx);
|
||||
// copy only the data beyond our range
|
||||
std::copy(_range_data(upper_it, end_addr), _range_data(upper_it, upper_end), _range_data(lower_it, end_addr));
|
||||
// keep lower_it, but delete upper_it
|
||||
_values.erase(std::next(lower_it), std::next(upper_it));
|
||||
return lower_it;
|
||||
} else if (lower_touch) {
|
||||
// we have a range to the left, just make it bigger and delete any other that may exist.
|
||||
lower_it->second.bits.resize(_range_offset(lower_it, end_addr), State::Sx);
|
||||
// keep lower_it and upper_it
|
||||
_values.erase(std::next(lower_it), upper_it);
|
||||
return lower_it;
|
||||
} else if (upper_touch) {
|
||||
// we have a range to the right, we need to expand it
|
||||
// since we need to erase and reinsert to a new address, steal the data
|
||||
RTLIL::Const data = std::move(upper_it->second);
|
||||
// note that begin_addr is not in upper_it, otherwise the whole range covered check would have tripped
|
||||
data.bits.insert(data.bits.begin(), (_range_begin(upper_it) - begin_addr) * _data_width, State::Sx);
|
||||
// delete lower_it and upper_it, then reinsert
|
||||
_values.erase(lower_it, std::next(upper_it));
|
||||
return _values.emplace(begin_addr, std::move(data)).first;
|
||||
} else {
|
||||
// no ranges are touching, so just delete all ranges in our range and allocate a new one
|
||||
// could try to resize an existing range but not sure if that actually helps
|
||||
_values.erase(lower_it, upper_it);
|
||||
return _values.emplace(begin_addr, RTLIL::Const(State::Sx, (end_addr - begin_addr) * _data_width)).first;
|
||||
}
|
||||
}
|
||||
|
||||
void MemContents::insert_concatenated(addr_t addr, RTLIL::Const const &values) {
|
||||
addr_t words = (values.size() + _data_width - 1) / _data_width;
|
||||
log_assert(addr < (addr_t)(1<<_addr_width));
|
||||
log_assert(words <= (addr_t)(1<<_addr_width) - addr);
|
||||
auto it = _reserve_range(addr, addr + words);
|
||||
auto to_begin = _range_data(it, addr);
|
||||
std::copy(values.bits.begin(), values.bits.end(), to_begin);
|
||||
// if values is not word-aligned, fill any missing bits with 0
|
||||
std::fill(to_begin + values.size(), to_begin + words * _data_width, State::S0);
|
||||
}
|
||||
|
||||
std::vector<State>::iterator MemContents::_range_write(std::vector<State>::iterator it, RTLIL::Const const &word) {
|
||||
auto from_end = word.size() <= _data_width ? word.bits.end() : word.bits.begin() + _data_width;
|
||||
auto to_end = std::copy(word.bits.begin(), from_end, it);
|
||||
auto it_next = std::next(it, _data_width);
|
||||
std::fill(to_end, it_next, State::S0);
|
||||
return it_next;
|
||||
}
|
109
kernel/mem.h
109
kernel/mem.h
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include "kernel/yosys.h"
|
||||
#include "kernel/ffinit.h"
|
||||
#include "kernel/utils.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
|
@ -224,6 +225,114 @@ 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) {}
|
||||
};
|
||||
|
||||
// MemContents efficiently represents the contents of a potentially sparse memory by storing only those segments that are actually defined
|
||||
class MemContents {
|
||||
public:
|
||||
class range; class iterator;
|
||||
using addr_t = uint32_t;
|
||||
private:
|
||||
// we ban _addr_width == sizeof(addr_t) * 8 because it adds too many cornercases
|
||||
int _addr_width;
|
||||
int _data_width;
|
||||
RTLIL::Const _default_value;
|
||||
// for each range, store the concatenation of the words at the start address
|
||||
// invariants:
|
||||
// - no overlapping or adjacent ranges
|
||||
// - no empty ranges
|
||||
// - all Consts are a multiple of the word size
|
||||
std::map<addr_t, RTLIL::Const> _values;
|
||||
// returns an iterator to the range containing addr, if it exists, or the first range past addr
|
||||
std::map<addr_t, RTLIL::Const>::iterator _range_at(addr_t addr) const;
|
||||
addr_t _range_size(std::map<addr_t, RTLIL::Const>::iterator it) const { return it->second.size() / _data_width; }
|
||||
addr_t _range_begin(std::map<addr_t, RTLIL::Const>::iterator it) const { return it->first; }
|
||||
addr_t _range_end(std::map<addr_t, RTLIL::Const>::iterator it) const { return _range_begin(it) + _range_size(it); }
|
||||
// check if the iterator points to a range containing addr
|
||||
bool _range_contains(std::map<addr_t, RTLIL::Const>::iterator it, addr_t addr) const;
|
||||
// check if the iterator points to a range containing [begin_addr, end_addr). assumes end_addr >= begin_addr.
|
||||
bool _range_contains(std::map<addr_t, RTLIL::Const>::iterator it, addr_t begin_addr, addr_t end_addr) const;
|
||||
// check if the iterator points to a range overlapping with [begin_addr, end_addr)
|
||||
bool _range_overlaps(std::map<addr_t, RTLIL::Const>::iterator it, addr_t begin_addr, addr_t end_addr) const;
|
||||
// return the offset the addr would have in the range at `it`
|
||||
size_t _range_offset(std::map<addr_t, RTLIL::Const>::iterator it, addr_t addr) const { return (addr - it->first) * _data_width; }
|
||||
// assuming _range_contains(it, addr), return an iterator pointing to the data at addr
|
||||
std::vector<State>::iterator _range_data(std::map<addr_t, RTLIL::Const>::iterator it, addr_t addr) { return it->second.bits.begin() + _range_offset(it, addr); }
|
||||
// internal version of reserve_range that returns an iterator to the range
|
||||
std::map<addr_t, RTLIL::Const>::iterator _reserve_range(addr_t begin_addr, addr_t end_addr);
|
||||
// write a single word at addr, return iterator to next word
|
||||
std::vector<State>::iterator _range_write(std::vector<State>::iterator it, RTLIL::Const const &data);
|
||||
public:
|
||||
class range {
|
||||
int _data_width;
|
||||
addr_t _base;
|
||||
RTLIL::Const const &_values;
|
||||
friend class iterator;
|
||||
range(int data_width, addr_t base, RTLIL::Const const &values)
|
||||
: _data_width(data_width), _base(base), _values(values) {}
|
||||
public:
|
||||
addr_t base() const { return _base; }
|
||||
addr_t size() const { return ((addr_t) _values.size()) / _data_width; }
|
||||
addr_t limit() const { return _base + size(); }
|
||||
RTLIL::Const const &concatenated() const { return _values; }
|
||||
RTLIL::Const operator[](addr_t addr) const {
|
||||
log_assert(addr - _base < size());
|
||||
return _values.extract((addr - _base) * _data_width, _data_width);
|
||||
}
|
||||
RTLIL::Const at_offset(addr_t offset) const { return (*this)[_base + offset]; }
|
||||
};
|
||||
class iterator {
|
||||
MemContents const *_memory;
|
||||
// storing addr instead of an iterator gives more well-defined behaviour under insertions/deletions
|
||||
// use ~0 for end so that all end iterators compare the same
|
||||
addr_t _addr;
|
||||
friend class MemContents;
|
||||
iterator(MemContents const *memory, addr_t addr) : _memory(memory), _addr(addr) {}
|
||||
public:
|
||||
using iterator_category = std::input_iterator_tag;
|
||||
using value_type = range;
|
||||
using pointer = arrow_proxy<range>;
|
||||
using reference = range;
|
||||
using difference_type = addr_t;
|
||||
reference operator *() const { return range(_memory->_data_width, _addr, _memory->_values.at(_addr)); }
|
||||
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 !(*this != other); }
|
||||
iterator &operator++();
|
||||
};
|
||||
MemContents(int addr_width, int data_width, RTLIL::Const default_value)
|
||||
: _addr_width(addr_width), _data_width(data_width)
|
||||
, _default_value((default_value.extu(data_width), std::move(default_value)))
|
||||
{ log_assert(_addr_width > 0 && _addr_width < (int)sizeof(addr_t) * 8); log_assert(_data_width > 0); }
|
||||
MemContents(int addr_width, int data_width) : MemContents(addr_width, data_width, RTLIL::Const(State::Sx, data_width)) {}
|
||||
explicit MemContents(Mem *mem);
|
||||
int addr_width() const { return _addr_width; }
|
||||
int data_width() const { return _data_width; }
|
||||
RTLIL::Const const &default_value() const { return _default_value; }
|
||||
// return the value at the address if it exists, the default_value of the memory otherwise. address must not exceed 2**addr_width.
|
||||
RTLIL::Const operator [](addr_t addr) const;
|
||||
// return the number of defined words in the range [begin_addr, end_addr)
|
||||
addr_t count_range(addr_t begin_addr, addr_t end_addr) const;
|
||||
// allocate memory for the range [begin_addr, end_addr), but leave the contents undefined.
|
||||
void reserve_range(addr_t begin_addr, addr_t end_addr) { _reserve_range(begin_addr, end_addr); }
|
||||
// insert multiple words (provided as a single concatenated RTLIL::Const) at the given address, overriding any previous assignment.
|
||||
void insert_concatenated(addr_t addr, RTLIL::Const const &values);
|
||||
// insert multiple words at the given address, overriding any previous assignment.
|
||||
template<typename Iterator> void insert_range(addr_t addr, Iterator begin, Iterator end) {
|
||||
auto words = end - begin;
|
||||
log_assert(addr < (addr_t)(1<<_addr_width)); log_assert(words <= (addr_t)(1<<_addr_width) - addr);
|
||||
auto range = _reserve_range(addr, addr + words);
|
||||
auto it = _range_data(range, addr);
|
||||
for(; begin != end; ++begin)
|
||||
it = _range_write(it, *begin);
|
||||
}
|
||||
// undefine all words in the range [begin_addr, end_addr)
|
||||
void clear_range(addr_t begin_addr, addr_t end_addr);
|
||||
// check invariants, abort if invariants failed
|
||||
void check();
|
||||
iterator end() const { return iterator(nullptr, ~(addr_t) 0); }
|
||||
iterator begin() const { return _values.empty() ? end() : iterator(this, _values.begin()->first); }
|
||||
bool empty() const { return _values.empty(); }
|
||||
};
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
|
|
|
@ -3754,6 +3754,20 @@ RTLIL::SigChunk RTLIL::SigChunk::extract(int offset, int length) const
|
|||
return ret;
|
||||
}
|
||||
|
||||
RTLIL::SigBit RTLIL::SigChunk::operator[](int offset) const
|
||||
{
|
||||
log_assert(offset >= 0);
|
||||
log_assert(offset <= width);
|
||||
RTLIL::SigBit ret;
|
||||
if (wire) {
|
||||
ret.wire = wire;
|
||||
ret.offset = this->offset + offset;
|
||||
} else {
|
||||
ret.data = data[offset];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool RTLIL::SigChunk::operator <(const RTLIL::SigChunk &other) const
|
||||
{
|
||||
if (wire && other.wire)
|
||||
|
|
|
@ -769,6 +769,7 @@ struct RTLIL::SigChunk
|
|||
SigChunk(const RTLIL::SigBit &bit);
|
||||
|
||||
RTLIL::SigChunk extract(int offset, int length) const;
|
||||
RTLIL::SigBit operator[](int offset) const;
|
||||
inline int size() const { return width; }
|
||||
inline bool is_wire() const { return wire != NULL; }
|
||||
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "sexpr.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, SExpr const &sexpr) {
|
||||
if(sexpr.is_atom())
|
||||
os << sexpr.atom();
|
||||
else if(sexpr.is_list()){
|
||||
os << "(";
|
||||
auto l = sexpr.list();
|
||||
for(size_t i = 0; i < l.size(); i++) {
|
||||
if(i > 0) os << " ";
|
||||
os << l[i];
|
||||
}
|
||||
os << ")";
|
||||
}else
|
||||
os << "<invalid>";
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string SExpr::to_string() const {
|
||||
std::stringstream ss;
|
||||
ss << *this;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void SExprWriter::nl_if_pending() {
|
||||
if(_pending_nl) {
|
||||
os << '\n';
|
||||
_pos = 0;
|
||||
_pending_nl = false;
|
||||
}
|
||||
}
|
||||
|
||||
void SExprWriter::puts(std::string_view s) {
|
||||
if(s.empty()) return;
|
||||
nl_if_pending();
|
||||
for(auto c : s) {
|
||||
if(c == '\n') {
|
||||
os << c;
|
||||
_pos = 0;
|
||||
} else {
|
||||
if(_pos == 0) {
|
||||
for(int i = 0; i < _indent; i++)
|
||||
os << " ";
|
||||
_pos = 2 * _indent;
|
||||
}
|
||||
os << c;
|
||||
_pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Calculate how much space would be left if expression was written
|
||||
// out in full horizontally. Returns any negative value if it doesn't fit.
|
||||
//
|
||||
// (Ideally we would avoid recalculating the widths of subexpression,
|
||||
// but I can't figure out how to store the widths. As an alternative,
|
||||
// we bail out of the calculation as soon as we can tell the expression
|
||||
// doesn't fit in the available space.)
|
||||
int SExprWriter::check_fit(SExpr const &sexpr, int space) {
|
||||
if(sexpr.is_atom())
|
||||
return space - sexpr.atom().size();
|
||||
else if(sexpr.is_list()) {
|
||||
space -= 2;
|
||||
if(sexpr.list().size() > 1)
|
||||
space -= sexpr.list().size() - 1;
|
||||
for(auto arg : sexpr.list()) {
|
||||
if(space < 0) break;
|
||||
space = check_fit(arg, space);
|
||||
}
|
||||
return space;
|
||||
} else
|
||||
return -1;
|
||||
}
|
||||
|
||||
void SExprWriter::print(SExpr const &sexpr, bool close, bool indent_rest) {
|
||||
if(sexpr.is_atom())
|
||||
puts(sexpr.atom());
|
||||
else if(sexpr.is_list()) {
|
||||
auto args = sexpr.list();
|
||||
puts("(");
|
||||
// Expressions are printed horizontally if they fit on the line.
|
||||
// We do the check *after* puts("(") to make sure that _pos is accurate.
|
||||
// (Otherwise there could be a pending newline + indentation)
|
||||
bool vertical = args.size() > 1 && check_fit(sexpr, _max_line_width - _pos + 1) < 0;
|
||||
if(vertical) _indent++;
|
||||
for(size_t i = 0; i < args.size(); i++) {
|
||||
if(i > 0) puts(vertical ? "\n" : " ");
|
||||
print(args[i]);
|
||||
}
|
||||
// Any remaining arguments are currently always printed vertically,
|
||||
// but are not indented if indent_rest = false.
|
||||
_indent += (!close && indent_rest) - vertical;
|
||||
if(close)
|
||||
puts(")");
|
||||
else {
|
||||
_unclosed.push_back(indent_rest);
|
||||
_pending_nl = true;
|
||||
}
|
||||
}else
|
||||
log_error("shouldn't happen: SExpr '%s' is neither an atom nor a list", sexpr.to_string().c_str());
|
||||
}
|
||||
|
||||
void SExprWriter::close(size_t n) {
|
||||
log_assert(_unclosed.size() - (_unclosed_stack.empty() ? 0 : _unclosed_stack.back()) >= n);
|
||||
while(n-- > 0) {
|
||||
bool indented = _unclosed[_unclosed.size() - 1];
|
||||
_unclosed.pop_back();
|
||||
// Only print ) on the same line if it fits.
|
||||
_pending_nl = _pos >= _max_line_width;
|
||||
if(indented)
|
||||
_indent--;
|
||||
puts(")");
|
||||
_pending_nl = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SExprWriter::comment(std::string const &str, bool hanging) {
|
||||
if(hanging) {
|
||||
if(_pending_nl) {
|
||||
_pending_nl = false;
|
||||
puts(" ");
|
||||
}
|
||||
}
|
||||
size_t i = 0, e;
|
||||
do{
|
||||
e = str.find('\n', i);
|
||||
puts("; ");
|
||||
puts(std::string_view(str).substr(i, e - i));
|
||||
puts("\n");
|
||||
i = e + 1;
|
||||
}while(e != std::string::npos);
|
||||
}
|
||||
|
||||
SExprWriter::~SExprWriter() {
|
||||
while(!_unclosed_stack.empty())
|
||||
pop();
|
||||
close(_unclosed.size());
|
||||
nl_if_pending();
|
||||
}
|
||||
|
||||
YOSYS_NAMESPACE_END
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* 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 SEXPR_H
|
||||
#define SEXPR_H
|
||||
|
||||
#include "kernel/yosys.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
class SExpr {
|
||||
public:
|
||||
std::variant<std::vector<SExpr>, std::string> _v;
|
||||
public:
|
||||
SExpr(std::string a) : _v(std::move(a)) {}
|
||||
SExpr(const char *a) : _v(a) {}
|
||||
// FIXME: should maybe be defined for all integral types
|
||||
SExpr(int n) : _v(std::to_string(n)) {}
|
||||
SExpr(std::vector<SExpr> const &l) : _v(l) {}
|
||||
SExpr(std::vector<SExpr> &&l) : _v(std::move(l)) {}
|
||||
// It would be nicer to have an std::initializer_list constructor,
|
||||
// but that causes confusing issues with overload resolution sometimes.
|
||||
template<typename... Args> static SExpr list(Args&&... args) {
|
||||
return SExpr(std::vector<SExpr>{std::forward<Args>(args)...});
|
||||
}
|
||||
bool is_atom() const { return std::holds_alternative<std::string>(_v); }
|
||||
std::string const &atom() const { return std::get<std::string>(_v); }
|
||||
bool is_list() const { return std::holds_alternative<std::vector<SExpr>>(_v); }
|
||||
std::vector<SExpr> const &list() const { return std::get<std::vector<SExpr>>(_v); }
|
||||
std::string to_string() const;
|
||||
};
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, SExpr const &sexpr);
|
||||
|
||||
namespace SExprUtil {
|
||||
// A little hack so that `using SExprUtil::list` lets you import a shortcut to `SExpr::list`
|
||||
template<typename... Args> SExpr list(Args&&... args) {
|
||||
return SExpr(std::vector<SExpr>{std::forward<Args>(args)...});
|
||||
}
|
||||
}
|
||||
|
||||
// SExprWriter is a pretty printer for s-expr. It does not try very hard to get a good layout.
|
||||
class SExprWriter {
|
||||
std::ostream &os;
|
||||
int _max_line_width;
|
||||
int _indent = 0;
|
||||
int _pos = 0;
|
||||
// If _pending_nl is set, print a newline before the next character.
|
||||
// This lets us "undo" the last newline so we can put
|
||||
// closing parentheses or a hanging comment on the same line.
|
||||
bool _pending_nl = false;
|
||||
// Unclosed parentheses (boolean stored is indent_rest)
|
||||
vector<bool> _unclosed;
|
||||
// Used only for push() and pop() (stores _unclosed.size())
|
||||
vector<size_t> _unclosed_stack;
|
||||
void nl_if_pending();
|
||||
void puts(std::string_view s);
|
||||
int check_fit(SExpr const &sexpr, int space);
|
||||
void print(SExpr const &sexpr, bool close = true, bool indent_rest = true);
|
||||
public:
|
||||
SExprWriter(std::ostream &os, int max_line_width = 80)
|
||||
: os(os)
|
||||
, _max_line_width(max_line_width)
|
||||
{}
|
||||
// Print an s-expr.
|
||||
SExprWriter &operator <<(SExpr const &sexpr) {
|
||||
print(sexpr);
|
||||
_pending_nl = true;
|
||||
return *this;
|
||||
}
|
||||
// Print an s-expr (which must be a list), but leave room for extra elements
|
||||
// which may be printed using either << or further calls to open.
|
||||
// If indent_rest = false, the remaining elements are not intended
|
||||
// (for avoiding unreasonable indentation on deeply nested structures).
|
||||
void open(SExpr const &sexpr, bool indent_rest = true) {
|
||||
log_assert(sexpr.is_list());
|
||||
print(sexpr, false, indent_rest);
|
||||
}
|
||||
// Close the s-expr opened with the last call to open
|
||||
// (if an argument is given, close that many s-exprs).
|
||||
void close(size_t n = 1);
|
||||
// push() remembers how many s-exprs are currently open
|
||||
void push() {
|
||||
_unclosed_stack.push_back(_unclosed.size());
|
||||
}
|
||||
// pop() closes all s-expr opened since the corresponding call to push()
|
||||
void pop() {
|
||||
auto t = _unclosed_stack.back();
|
||||
log_assert(_unclosed.size() >= t);
|
||||
close(_unclosed.size() - t);
|
||||
_unclosed_stack.pop_back();
|
||||
}
|
||||
// Print a comment.
|
||||
// If hanging = true, append it to the end of the last printed s-expr.
|
||||
void comment(std::string const &str, bool hanging = false);
|
||||
// Flush any unprinted characters to the std::ostream, but does not close unclosed parentheses.
|
||||
void flush() {
|
||||
nl_if_pending();
|
||||
}
|
||||
// Destructor closes any unclosed parentheses and flushes.
|
||||
~SExprWriter();
|
||||
};
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
|
||||
#endif
|
|
@ -0,0 +1,357 @@
|
|||
/*
|
||||
* 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 TOPO_SCC_H
|
||||
#define TOPO_SCC_H
|
||||
|
||||
#include "kernel/yosys.h"
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
class SigCellGraph {
|
||||
public:
|
||||
typedef int node_type;
|
||||
|
||||
struct successor_enumerator {
|
||||
std::vector<std::pair<int, int>>::const_iterator current, end;
|
||||
bool finished() const { return current == end; }
|
||||
node_type next() {
|
||||
log_assert(!finished());
|
||||
node_type result = current->second;
|
||||
++current;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct node_enumerator {
|
||||
int current, end;
|
||||
bool finished() const { return current == end; }
|
||||
node_type next() {
|
||||
log_assert(!finished());
|
||||
node_type result = current;
|
||||
++current;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
idict<RTLIL::Cell *> cell_ids;
|
||||
idict<RTLIL::SigBit> sig_ids;
|
||||
std::vector<std::pair<int, int>> edges;
|
||||
std::vector<std::pair<int, int>> edge_ranges;
|
||||
std::vector<int> indices_;
|
||||
int offset;
|
||||
bool computed = false;
|
||||
|
||||
void compute() {
|
||||
offset = GetSize(sig_ids);
|
||||
edge_ranges.clear();
|
||||
indices_.clear();
|
||||
indices_.resize(GetSize(sig_ids) + GetSize(cell_ids), -1);
|
||||
|
||||
std::sort(edges.begin(), edges.end());
|
||||
auto last = std::unique(edges.begin(), edges.end());
|
||||
edges.erase(last, edges.end());
|
||||
auto edge = edges.begin();
|
||||
auto edge_end = edges.end();
|
||||
int range_begin = 0;
|
||||
for (int node = -offset, node_end = GetSize(cell_ids); node != node_end; ++node) {
|
||||
while (edge != edge_end && edge->first <= node)
|
||||
++edge;
|
||||
int range_end = edge - edges.begin();
|
||||
edge_ranges.emplace_back(std::make_pair(range_begin, range_end));
|
||||
range_begin = range_end;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
node_type node(RTLIL::Cell *cell) { return cell_ids(cell); }
|
||||
node_type node(SigBit const &bit) { return ~sig_ids(bit); }
|
||||
|
||||
bool is_cell(node_type node) { return node >= 0; }
|
||||
bool is_sig(node_type node) { return node < 0; }
|
||||
|
||||
Cell *cell(node_type node) { return node >= 0 ? cell_ids[node] : nullptr; }
|
||||
SigBit sig(node_type node) { return node < 0 ? sig_ids[~node] : SigBit(); }
|
||||
|
||||
template<typename Src, typename Dst>
|
||||
void add_edge(Src &&src, Dst &&dst) {
|
||||
computed = false;
|
||||
node_type src_node = node(std::forward<Src>(src));
|
||||
node_type dst_node = node(std::forward<Dst>(dst));
|
||||
edges.emplace_back(std::make_pair(src_node, dst_node));
|
||||
}
|
||||
|
||||
node_enumerator enumerate_nodes() {
|
||||
if (!computed) compute();
|
||||
return {-GetSize(sig_ids), GetSize(cell_ids)};
|
||||
}
|
||||
|
||||
successor_enumerator enumerate_successors(node_type const &node) const {
|
||||
auto range = edge_ranges[node + offset];
|
||||
return {edges.begin() + range.first, edges.begin() + range.second};
|
||||
}
|
||||
|
||||
int &dfs_index(node_type const &node) {
|
||||
return indices_[node + offset];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class IntGraph {
|
||||
public:
|
||||
typedef int node_type;
|
||||
|
||||
struct successor_enumerator {
|
||||
std::vector<std::pair<int, int>>::const_iterator current, end;
|
||||
bool finished() const { return current == end; }
|
||||
node_type next() {
|
||||
log_assert(!finished());
|
||||
node_type result = current->second;
|
||||
++current;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct node_enumerator {
|
||||
int current, end;
|
||||
bool finished() const { return current == end; }
|
||||
node_type next() {
|
||||
log_assert(!finished());
|
||||
node_type result = current;
|
||||
++current;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
std::vector<std::pair<int, int>> edges;
|
||||
std::vector<std::pair<int, int>> edge_ranges;
|
||||
std::vector<int> indices_;
|
||||
bool computed = false;
|
||||
|
||||
void compute() {
|
||||
edge_ranges.clear();
|
||||
|
||||
int node_end = 0;
|
||||
for (auto const &edge : edges)
|
||||
node_end = std::max(node_end, std::max(edge.first, edge.second) + 1);
|
||||
indices_.clear();
|
||||
indices_.resize(node_end, -1);
|
||||
|
||||
std::sort(edges.begin(), edges.end());
|
||||
auto last = std::unique(edges.begin(), edges.end());
|
||||
edges.erase(last, edges.end());
|
||||
auto edge = edges.begin();
|
||||
auto edge_end = edges.end();
|
||||
int range_begin = 0;
|
||||
for (int node = 0; node != node_end; ++node) {
|
||||
while (edge != edge_end && edge->first <= node)
|
||||
++edge;
|
||||
int range_end = edge - edges.begin();
|
||||
edge_ranges.emplace_back(std::make_pair(range_begin, range_end));
|
||||
range_begin = range_end;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void add_edge(int src, int dst) {
|
||||
log_assert(src >= 0);
|
||||
log_assert(dst >= 0);
|
||||
computed = false;
|
||||
edges.emplace_back(std::make_pair(src, dst));
|
||||
}
|
||||
|
||||
node_enumerator enumerate_nodes() {
|
||||
if (!computed) compute();
|
||||
return {0, GetSize(indices_)};
|
||||
}
|
||||
|
||||
successor_enumerator enumerate_successors(int node) const {
|
||||
auto range = edge_ranges[node];
|
||||
return {edges.begin() + range.first, edges.begin() + range.second};
|
||||
}
|
||||
|
||||
int &dfs_index(node_type const &node) {
|
||||
return indices_[node];
|
||||
}
|
||||
};
|
||||
|
||||
template<typename G, typename ComponentCallback>
|
||||
class TopoSortedSccs
|
||||
{
|
||||
typedef typename G::node_enumerator node_enumerator;
|
||||
typedef typename G::successor_enumerator successor_enumerator;
|
||||
typedef typename G::node_type node_type;
|
||||
|
||||
struct dfs_entry {
|
||||
node_type node;
|
||||
successor_enumerator successors;
|
||||
int lowlink;
|
||||
|
||||
dfs_entry(node_type node, successor_enumerator successors, int lowlink) :
|
||||
node(node), successors(successors), lowlink(lowlink)
|
||||
{}
|
||||
};
|
||||
|
||||
G &graph;
|
||||
ComponentCallback component;
|
||||
|
||||
std::vector<dfs_entry> dfs_stack;
|
||||
std::vector<node_type> component_stack;
|
||||
int next_index = 0;
|
||||
|
||||
public:
|
||||
TopoSortedSccs(G &graph, ComponentCallback component)
|
||||
: graph(graph), component(component) {}
|
||||
|
||||
// process all sources (nodes without a successor)
|
||||
TopoSortedSccs &process_sources() {
|
||||
node_enumerator nodes = graph.enumerate_nodes();
|
||||
while (!nodes.finished()) {
|
||||
node_type node = nodes.next();
|
||||
successor_enumerator successors = graph.enumerate_successors(node);
|
||||
if (successors.finished())
|
||||
{
|
||||
graph.dfs_index(node) = next_index;
|
||||
next_index++;
|
||||
component_stack.push_back(node);
|
||||
component(component_stack.data(), component_stack.data() + 1);
|
||||
component_stack.clear();
|
||||
graph.dfs_index(node) = INT_MAX;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// process all remaining nodes in the graph
|
||||
TopoSortedSccs &process_all() {
|
||||
node_enumerator nodes = graph.enumerate_nodes();
|
||||
// iterate over all nodes to ensure we process the whole graph
|
||||
while (!nodes.finished())
|
||||
process(nodes.next());
|
||||
return *this;
|
||||
}
|
||||
|
||||
// process all nodes that are reachable from a given start node
|
||||
TopoSortedSccs &process(node_type node) {
|
||||
// only start a new search if the node wasn't visited yet
|
||||
if (graph.dfs_index(node) >= 0)
|
||||
return *this;
|
||||
while (true) {
|
||||
// at this point we're visiting the node for the first time during
|
||||
// the DFS search
|
||||
|
||||
// we record the timestamp of when we first visited the node as the
|
||||
// dfs_index
|
||||
int lowlink = next_index;
|
||||
next_index++;
|
||||
graph.dfs_index(node) = lowlink;
|
||||
|
||||
// and we add the node to the component stack where it will remain
|
||||
// until all nodes of the component containing this node are popped
|
||||
component_stack.push_back(node);
|
||||
|
||||
// then we start iterating over the successors of this node
|
||||
successor_enumerator successors = graph.enumerate_successors(node);
|
||||
while (true) {
|
||||
if (successors.finished()) {
|
||||
// when we processed all successors, i.e. when we visited
|
||||
// the complete DFS subtree rooted at the current node, we
|
||||
// first check whether the current node is a SCC root
|
||||
//
|
||||
// (why this check identifies SCC roots is out of scope for
|
||||
// this comment, see other material on Tarjan's SCC
|
||||
// algorithm)
|
||||
if (lowlink == graph.dfs_index(node)) {
|
||||
// the SCC containing the current node is at the top of
|
||||
// the component stack, with the current node at the bottom
|
||||
int current = GetSize(component_stack);
|
||||
do {
|
||||
--current;
|
||||
} while (component_stack[current] != node);
|
||||
|
||||
// we invoke the callback with a pointer range of the
|
||||
// nodes in the SCC
|
||||
|
||||
node_type *stack_ptr = component_stack.data();
|
||||
node_type *component_begin = stack_ptr + current;
|
||||
node_type *component_end = stack_ptr + component_stack.size();
|
||||
|
||||
// note that we allow the callback to permute the nodes
|
||||
// in this range as well as to modify dfs_index of the
|
||||
// nodes in the SCC.
|
||||
component(component_begin, component_end);
|
||||
|
||||
// by setting the dfs_index of all already emitted
|
||||
// nodes to INT_MAX, we don't need a separate check for
|
||||
// whether successor nodes are still on the component
|
||||
// stack before updating the lowlink value
|
||||
for (; component_begin != component_end; ++component_begin)
|
||||
graph.dfs_index(*component_begin) = INT_MAX;
|
||||
component_stack.resize(current);
|
||||
}
|
||||
|
||||
// after checking for a completed SCC the DFS either
|
||||
// continues the search at the parent node or returns to
|
||||
// the outer loop if we already are at the root node.
|
||||
if (dfs_stack.empty())
|
||||
return *this;
|
||||
auto &dfs_top = dfs_stack.back();
|
||||
|
||||
node = dfs_top.node;
|
||||
successors = std::move(dfs_top.successors);
|
||||
|
||||
// the parent's lowlink is updated when returning
|
||||
lowlink = min(lowlink, dfs_top.lowlink);
|
||||
dfs_stack.pop_back();
|
||||
// continue checking the remaining successors of the parent node.
|
||||
} else {
|
||||
node_type succ = successors.next();
|
||||
if (graph.dfs_index(succ) < 0) {
|
||||
// if the successor wasn't visted yet, the DFS recurses
|
||||
// into the successor
|
||||
|
||||
// we save the state for this node and make the
|
||||
// successor the current node.
|
||||
dfs_stack.emplace_back(node, std::move(successors), lowlink);
|
||||
node = succ;
|
||||
|
||||
// this break gets us to the section corresponding to
|
||||
// the function entry in the recursive version
|
||||
break;
|
||||
} else {
|
||||
// the textbook version guards this update with a check
|
||||
// whether the successor is still on the component
|
||||
// stack. If the successor node was already visisted
|
||||
// but is not on the component stack, it must be part
|
||||
// of an already emitted SCC. We can avoid this check
|
||||
// by setting the DFS index of all nodes in a SCC to
|
||||
// INT_MAX when the SCC is emitted.
|
||||
lowlink = min(lowlink, graph.dfs_index(succ));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
|
||||
#endif
|
|
@ -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
|
||||
|
||||
#endif
|
||||
|
|
|
@ -30,6 +30,8 @@
|
|||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <initializer_list>
|
||||
#include <variant>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <memory>
|
||||
#include <cmath>
|
||||
|
|
|
@ -50,3 +50,4 @@ OBJS += passes/cmds/xprop.o
|
|||
OBJS += passes/cmds/dft_tag.o
|
||||
OBJS += passes/cmds/future.o
|
||||
OBJS += passes/cmds/box_derive.o
|
||||
OBJS += passes/cmds/example_dt.o
|
||||
|
|
|
@ -0,0 +1,254 @@
|
|||
#include "kernel/yosys.h"
|
||||
#include "kernel/drivertools.h"
|
||||
#include "kernel/topo_scc.h"
|
||||
#include "kernel/compute_graph.h"
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
|
||||
|
||||
|
||||
struct ExampleWorker
|
||||
{
|
||||
DriverMap dm;
|
||||
Module *module;
|
||||
|
||||
ExampleWorker(Module *module) : module(module) {
|
||||
dm.celltypes.setup();
|
||||
}
|
||||
};
|
||||
|
||||
struct ExampleDtPass : public Pass
|
||||
{
|
||||
ExampleDtPass() : Pass("example_dt", "drivertools example") {}
|
||||
|
||||
void help() override
|
||||
{
|
||||
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
||||
log("\n");
|
||||
}
|
||||
|
||||
|
||||
void execute(std::vector<std::string> args, RTLIL::Design *design) override
|
||||
{
|
||||
size_t argidx = 1;
|
||||
extra_args(args, argidx, design);
|
||||
|
||||
for (auto module : design->selected_modules()) {
|
||||
ExampleWorker worker(module);
|
||||
DriverMap dm;
|
||||
|
||||
struct ExampleFn {
|
||||
IdString name;
|
||||
dict<IdString, Const> parameters;
|
||||
|
||||
ExampleFn(IdString name) : name(name) {}
|
||||
ExampleFn(IdString name, dict<IdString, Const> parameters) : name(name), parameters(parameters) {}
|
||||
|
||||
bool operator==(ExampleFn const &other) const {
|
||||
return name == other.name && parameters == other.parameters;
|
||||
}
|
||||
|
||||
unsigned int hash() const {
|
||||
return mkhash(name.hash(), parameters.hash());
|
||||
}
|
||||
};
|
||||
|
||||
typedef ComputeGraph<ExampleFn, int, IdString, IdString> ExampleGraph;
|
||||
|
||||
ExampleGraph compute_graph;
|
||||
|
||||
|
||||
dm.add(module);
|
||||
|
||||
idict<DriveSpec> queue;
|
||||
idict<Cell *> cells;
|
||||
|
||||
IntGraph edges;
|
||||
std::vector<int> graph_nodes;
|
||||
|
||||
auto enqueue = [&](DriveSpec const &spec) {
|
||||
int index = queue(spec);
|
||||
if (index == GetSize(graph_nodes))
|
||||
graph_nodes.emplace_back(compute_graph.add(ID($pending), index).index());
|
||||
//if (index >= GetSize(graph_nodes))
|
||||
return compute_graph[graph_nodes[index]];
|
||||
};
|
||||
|
||||
for (auto cell : module->cells()) {
|
||||
if (cell->type.in(ID($assert), ID($assume), ID($cover), ID($check)))
|
||||
enqueue(DriveBitMarker(cells(cell), 0));
|
||||
}
|
||||
|
||||
for (auto wire : module->wires()) {
|
||||
if (!wire->port_output)
|
||||
continue;
|
||||
enqueue(DriveChunk(DriveChunkWire(wire, 0, wire->width))).assign_key(wire->name);
|
||||
}
|
||||
|
||||
for (int i = 0; i != GetSize(queue); ++i)
|
||||
{
|
||||
DriveSpec spec = queue[i];
|
||||
ExampleGraph::Ref node = compute_graph[i];
|
||||
|
||||
if (spec.chunks().size() > 1) {
|
||||
node.set_function(ID($$concat));
|
||||
|
||||
for (auto const &chunk : spec.chunks()) {
|
||||
node.append_arg(enqueue(chunk));
|
||||
}
|
||||
} else if (spec.chunks().size() == 1) {
|
||||
DriveChunk chunk = spec.chunks()[0];
|
||||
if (chunk.is_wire()) {
|
||||
DriveChunkWire wire_chunk = chunk.wire();
|
||||
if (wire_chunk.is_whole()) {
|
||||
node.sparse_attr() = wire_chunk.wire->name;
|
||||
if (wire_chunk.wire->port_input) {
|
||||
node.set_function(ExampleFn(ID($$input), {{wire_chunk.wire->name, {}}}));
|
||||
} else {
|
||||
DriveSpec driver = dm(DriveSpec(wire_chunk));
|
||||
node.set_function(ID($$buf));
|
||||
|
||||
node.append_arg(enqueue(driver));
|
||||
}
|
||||
} else {
|
||||
DriveChunkWire whole_wire(wire_chunk.wire, 0, wire_chunk.wire->width);
|
||||
node.set_function(ExampleFn(ID($$slice), {{ID(offset), wire_chunk.offset}, {ID(width), wire_chunk.width}}));
|
||||
node.append_arg(enqueue(whole_wire));
|
||||
}
|
||||
} else if (chunk.is_port()) {
|
||||
DriveChunkPort port_chunk = chunk.port();
|
||||
if (port_chunk.is_whole()) {
|
||||
if (dm.celltypes.cell_output(port_chunk.cell->type, port_chunk.port)) {
|
||||
if (port_chunk.cell->type.in(ID($dff), ID($ff)))
|
||||
{
|
||||
Cell *cell = port_chunk.cell;
|
||||
node.set_function(ExampleFn(ID($$state), {{cell->name, {}}}));
|
||||
for (auto const &conn : cell->connections()) {
|
||||
if (!dm.celltypes.cell_input(cell->type, conn.first))
|
||||
continue;
|
||||
enqueue(DriveChunkPort(cell, conn)).assign_key(cell->name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
node.set_function(ExampleFn(ID($$cell_output), {{port_chunk.port, {}}}));
|
||||
node.append_arg(enqueue(DriveBitMarker(cells(port_chunk.cell), 0)));
|
||||
}
|
||||
} else {
|
||||
node.set_function(ID($$buf));
|
||||
|
||||
DriveSpec driver = dm(DriveSpec(port_chunk));
|
||||
node.append_arg(enqueue(driver));
|
||||
}
|
||||
|
||||
} else {
|
||||
DriveChunkPort whole_port(port_chunk.cell, port_chunk.port, 0, GetSize(port_chunk.cell->connections().at(port_chunk.port)));
|
||||
node.set_function(ExampleFn(ID($$slice), {{ID(offset), port_chunk.offset}}));
|
||||
node.append_arg(enqueue(whole_port));
|
||||
}
|
||||
} else if (chunk.is_constant()) {
|
||||
node.set_function(ExampleFn(ID($$const), {{ID(value), chunk.constant()}}));
|
||||
|
||||
} else if (chunk.is_multiple()) {
|
||||
node.set_function(ID($$multi));
|
||||
for (auto const &driver : chunk.multiple().multiple())
|
||||
node.append_arg(enqueue(driver));
|
||||
} else if (chunk.is_marker()) {
|
||||
Cell *cell = cells[chunk.marker().marker];
|
||||
|
||||
node.set_function(ExampleFn(cell->type, cell->parameters));
|
||||
for (auto const &conn : cell->connections()) {
|
||||
if (!dm.celltypes.cell_input(cell->type, conn.first))
|
||||
continue;
|
||||
|
||||
node.append_arg(enqueue(DriveChunkPort(cell, conn)));
|
||||
}
|
||||
} else if (chunk.is_none()) {
|
||||
node.set_function(ID($$undriven));
|
||||
|
||||
} else {
|
||||
log_error("unhandled drivespec: %s\n", log_signal(chunk));
|
||||
log_abort();
|
||||
}
|
||||
} else {
|
||||
log_abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Perform topo sort and detect SCCs
|
||||
ExampleGraph::SccAdaptor compute_graph_scc(compute_graph);
|
||||
|
||||
|
||||
std::vector<int> perm;
|
||||
TopoSortedSccs(compute_graph_scc, [&](int *begin, int *end) {
|
||||
perm.insert(perm.end(), begin, end);
|
||||
if (end > begin + 1)
|
||||
{
|
||||
log_warning("SCC:");
|
||||
for (int *i = begin; i != end; ++i)
|
||||
log(" %d", *i);
|
||||
log("\n");
|
||||
}
|
||||
}).process_sources().process_all();
|
||||
compute_graph.permute(perm);
|
||||
|
||||
|
||||
// Forward $$buf unless we have a name in the sparse attribute
|
||||
std::vector<int> alias;
|
||||
perm.clear();
|
||||
|
||||
for (int i = 0; i < compute_graph.size(); ++i)
|
||||
{
|
||||
if (compute_graph[i].function().name == ID($$buf) && !compute_graph[i].has_sparse_attr() && compute_graph[i].arg(0).index() < i)
|
||||
{
|
||||
|
||||
alias.push_back(alias[compute_graph[i].arg(0).index()]);
|
||||
}
|
||||
else
|
||||
{
|
||||
alias.push_back(GetSize(perm));
|
||||
perm.push_back(i);
|
||||
}
|
||||
}
|
||||
compute_graph.permute(perm, alias);
|
||||
|
||||
// Dump the compute graph
|
||||
for (int i = 0; i < compute_graph.size(); ++i)
|
||||
{
|
||||
auto ref = compute_graph[i];
|
||||
log("n%d ", i);
|
||||
log("%s", log_id(ref.function().name));
|
||||
for (auto const ¶m : ref.function().parameters)
|
||||
{
|
||||
if (param.second.empty())
|
||||
log("[%s]", log_id(param.first));
|
||||
else
|
||||
log("[%s=%s]", log_id(param.first), log_const(param.second));
|
||||
}
|
||||
log("(");
|
||||
|
||||
for (int i = 0, end = ref.size(); i != end; ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
log(", ");
|
||||
log("n%d", ref.arg(i).index());
|
||||
}
|
||||
log(")\n");
|
||||
if (ref.has_sparse_attr())
|
||||
log("// wire %s\n", log_id(ref.sparse_attr()));
|
||||
log("// was #%d %s\n", ref.attr(), log_signal(queue[ref.attr()]));
|
||||
}
|
||||
|
||||
for (auto const &key : compute_graph.keys())
|
||||
{
|
||||
log("return %d as %s \n", key.second, log_id(key.first));
|
||||
}
|
||||
}
|
||||
log("Plugin test passed!\n");
|
||||
}
|
||||
} ExampleDtPass;
|
||||
|
||||
PRIVATE_NAMESPACE_END
|
|
@ -123,6 +123,7 @@ struct SimShared
|
|||
std::vector<TriggeredAssertion> triggered_assertions;
|
||||
std::vector<DisplayOutput> display_output;
|
||||
bool serious_asserts = false;
|
||||
bool fst_noinit = false;
|
||||
bool initstate = true;
|
||||
};
|
||||
|
||||
|
@ -1553,7 +1554,7 @@ struct SimWorker : SimShared
|
|||
bool did_something = top->setInputs();
|
||||
|
||||
if (initial) {
|
||||
did_something |= top->setInitState();
|
||||
if (!fst_noinit) did_something |= top->setInitState();
|
||||
initialize_stable_past();
|
||||
initial = false;
|
||||
}
|
||||
|
@ -2688,6 +2689,10 @@ struct SimPass : public Pass {
|
|||
log(" fail the simulation command if, in the course of simulating,\n");
|
||||
log(" any of the asserts in the design fail\n");
|
||||
log("\n");
|
||||
log(" -fst-noinit\n");
|
||||
log(" do not initialize latches and memories from an input FST or VCD file\n");
|
||||
log(" (use the initial defined by the design instead)\n");
|
||||
log("\n");
|
||||
log(" -q\n");
|
||||
log(" disable per-cycle/sample log message\n");
|
||||
log("\n");
|
||||
|
@ -2850,6 +2855,10 @@ struct SimPass : public Pass {
|
|||
worker.serious_asserts = true;
|
||||
continue;
|
||||
}
|
||||
if (args[argidx] == "-fst-noinit") {
|
||||
worker.fst_noinit = true;
|
||||
continue;
|
||||
}
|
||||
if (args[argidx] == "-x") {
|
||||
worker.ignore_x = true;
|
||||
continue;
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
my_module_cxxrtl.cc
|
||||
my_module_functional_cxx.cc
|
||||
vcd_harness
|
||||
*.vcd
|
||||
*.smt2
|
||||
*.rkt
|
|
@ -0,0 +1,19 @@
|
|||
Tests for the functional backend use pytest as a testrunner.
|
||||
|
||||
Run with `pytest -v`
|
||||
|
||||
Pytest options you might want:
|
||||
|
||||
- `-v`: More progress indication.
|
||||
|
||||
- `--basetemp tmp`: Store test files (including vcd results) in tmp.
|
||||
CAREFUL: contents of tmp will be deleted
|
||||
|
||||
- `-k <pattern>`: Run only tests that contain the pattern, e.g.
|
||||
`-k cxx` or `-k smt` or `-k demux` or `-k 'cxx[demux`
|
||||
|
||||
- `-s`: Don't hide stdout/stderr from the test code.
|
||||
|
||||
Custom options for functional backend tests:
|
||||
|
||||
- `--per-cell N`: Run only N tests for each cell.
|
|
@ -0,0 +1,34 @@
|
|||
import pytest
|
||||
from rtlil_cells import generate_test_cases
|
||||
import random
|
||||
|
||||
random_seed = random.getrandbits(32)
|
||||
|
||||
def pytest_configure(config):
|
||||
config.addinivalue_line("markers", "smt: test uses smtlib/z3")
|
||||
config.addinivalue_line("markers", "rkt: test uses racket/rosette")
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--per-cell", type=int, default=None, help="run only N tests per cell")
|
||||
parser.addoption("--steps", type=int, default=1000, help="run each test for N steps")
|
||||
parser.addoption("--seed", type=int, default=random_seed, help="seed for random number generation, use random seed if unspecified")
|
||||
|
||||
def pytest_collection_finish(session):
|
||||
print('random seed: {}'.format(session.config.getoption("seed")))
|
||||
|
||||
@pytest.fixture
|
||||
def num_steps(request):
|
||||
return request.config.getoption("steps")
|
||||
|
||||
@pytest.fixture
|
||||
def rnd(request):
|
||||
seed1 = request.config.getoption("seed")
|
||||
return lambda seed2: random.Random('{}-{}'.format(seed1, seed2))
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
if "cell" in metafunc.fixturenames:
|
||||
per_cell = metafunc.config.getoption("per_cell", default=None)
|
||||
seed1 = metafunc.config.getoption("seed")
|
||||
rnd = lambda seed2: random.Random('{}-{}'.format(seed1, seed2))
|
||||
names, cases = generate_test_cases(per_cell, rnd)
|
||||
metafunc.parametrize("cell,parameters", cases, ids=names)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,62 @@
|
|||
module gold(
|
||||
input wire clk,
|
||||
output reg resetn,
|
||||
output wire trap,
|
||||
output wire mem_valid,
|
||||
output wire mem_instr,
|
||||
output wire mem_ready,
|
||||
output wire [31:0] mem_addr,
|
||||
output wire [31:0] mem_wdata,
|
||||
output wire [31:0] mem_wstrb,
|
||||
output wire [31:0] mem_rdata,
|
||||
);
|
||||
|
||||
initial resetn = 1'b0;
|
||||
always @(posedge clk) resetn <= 1'b1;
|
||||
|
||||
reg [31:0] rom[0:15];
|
||||
|
||||
initial begin
|
||||
rom[0] = 32'h00200093;
|
||||
rom[1] = 32'h00200113;
|
||||
rom[2] = 32'h00111863;
|
||||
rom[3] = 32'h00102023;
|
||||
rom[4] = 32'h00108093;
|
||||
rom[5] = 32'hfe0008e3;
|
||||
rom[6] = 32'h00008193;
|
||||
rom[7] = 32'h402181b3;
|
||||
rom[8] = 32'hfe304ee3;
|
||||
rom[9] = 32'hfe0186e3;
|
||||
rom[10] = 32'h00110113;
|
||||
rom[11] = 32'hfc000ee3;
|
||||
end
|
||||
|
||||
assign mem_ready = 1'b1;
|
||||
assign mem_rdata = rom[mem_addr[5:2]];
|
||||
|
||||
wire pcpi_wr = 1'b0;
|
||||
wire [31:0] pcpi_rd = 32'b0;
|
||||
wire pcpi_wait = 1'b0;
|
||||
wire pcpi_ready = 1'b0;
|
||||
|
||||
wire [31:0] irq = 32'b0;
|
||||
|
||||
picorv32 picorv32_i(
|
||||
.clk(clk),
|
||||
.resetn(resetn),
|
||||
.trap(trap),
|
||||
.mem_valid(mem_valid),
|
||||
.mem_instr(mem_instr),
|
||||
.mem_ready(mem_ready),
|
||||
.mem_addr(mem_addr),
|
||||
.mem_wdata(mem_wdata),
|
||||
.mem_wstrb(mem_wstrb),
|
||||
.mem_rdata(mem_rdata),
|
||||
.pcpi_wr(pcpi_wr),
|
||||
.pcpi_rd(pcpi_rd),
|
||||
.pcpi_wait(pcpi_wait),
|
||||
.pcpi_ready(pcpi_ready),
|
||||
.irq(irq)
|
||||
);
|
||||
|
||||
endmodule
|
|
@ -0,0 +1,115 @@
|
|||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
from typing import Dict, List, Tuple
|
||||
from random import Random
|
||||
|
||||
vcd_version = "Yosys/tests/functional/rkt_vcd.py"
|
||||
StepList = List[Tuple[int, str]]
|
||||
SignalStepMap = Dict[str, StepList]
|
||||
SignalWidthMap = Dict[str, int]
|
||||
|
||||
def write_vcd(filename: Path, signals: SignalStepMap, timescale='1 ns', date='today'):
|
||||
with open(filename, 'w') as f:
|
||||
# Write the header
|
||||
f.write(f"$date\n {date}\n$end\n")
|
||||
f.write(f"$timescale {timescale} $end\n")
|
||||
|
||||
# Declare signals
|
||||
f.write("$scope module gold $end\n")
|
||||
for signal_name, changes in signals.items():
|
||||
signal_size = len(changes[0][1])
|
||||
f.write(f"$var wire {signal_size - 1} {signal_name} {signal_name} $end\n")
|
||||
f.write("$upscope $end\n")
|
||||
f.write("$enddefinitions $end\n")
|
||||
|
||||
# Collect all unique timestamps
|
||||
timestamps = sorted(set(time for changes in signals.values() for time, _ in changes))
|
||||
|
||||
# Write initial values
|
||||
f.write("#0\n")
|
||||
for signal_name, changes in signals.items():
|
||||
for time, value in changes:
|
||||
if time == 0:
|
||||
f.write(f"{value} {signal_name}\n")
|
||||
|
||||
# Write value changes
|
||||
for time in timestamps:
|
||||
if time != 0:
|
||||
f.write(f"#{time}\n")
|
||||
for signal_name, changes in signals.items():
|
||||
for change_time, value in changes:
|
||||
if change_time == time:
|
||||
f.write(f"{value} {signal_name}\n")
|
||||
|
||||
def simulate_rosette(rkt_file_path: Path, vcd_path: Path, num_steps: int, rnd: Random):
|
||||
signals: dict[str, list[str]] = {}
|
||||
inputs: SignalWidthMap = {}
|
||||
outputs: SignalWidthMap = {}
|
||||
|
||||
current_struct_name: str = ""
|
||||
with open(rkt_file_path, 'r') as rkt_file:
|
||||
for line in rkt_file:
|
||||
m = re.search(r'gold_(Inputs|Outputs|State)', line)
|
||||
if m:
|
||||
current_struct_name = m.group(1)
|
||||
if current_struct_name == "State": break
|
||||
elif not current_struct_name: continue # skip lines before structs
|
||||
m = re.search(r'; (.+?)\b \(bitvector (\d+)\)', line)
|
||||
if not m: continue # skip non matching lines (probably closing the struct)
|
||||
signal = m.group(1)
|
||||
width = int(m.group(2))
|
||||
if current_struct_name == "Inputs":
|
||||
inputs[signal] = width
|
||||
elif current_struct_name == "Outputs":
|
||||
outputs[signal] = width
|
||||
|
||||
for signal, width in inputs.items():
|
||||
step_list: list[int] = []
|
||||
for step in range(num_steps):
|
||||
value = rnd.getrandbits(width)
|
||||
binary_string = format(value, '0{}b'.format(width))
|
||||
step_list.append(binary_string)
|
||||
signals[signal] = step_list
|
||||
|
||||
test_rkt_file_path = rkt_file_path.with_suffix('.tst.rkt')
|
||||
with open(test_rkt_file_path, 'w') as test_rkt_file:
|
||||
test_rkt_file.writelines([
|
||||
'#lang rosette\n',
|
||||
f'(require "{rkt_file_path.name}")\n',
|
||||
])
|
||||
|
||||
for step in range(num_steps):
|
||||
this_step = f"step_{step}"
|
||||
value_list: list[str] = []
|
||||
for signal, width in inputs.items():
|
||||
value = signals[signal][step]
|
||||
value_list.append(f"(bv #b{value} {width})")
|
||||
gold_Inputs = f"(gold_Inputs {' '.join(value_list)})"
|
||||
gold_State = f"(cdr step_{step-1})" if step else "gold_initial"
|
||||
test_rkt_file.write(f"(define {this_step} (gold {gold_Inputs} {gold_State})) (car {this_step})\n")
|
||||
|
||||
cmd = ["racket", test_rkt_file_path]
|
||||
status = subprocess.run(cmd, capture_output=True)
|
||||
assert status.returncode == 0, f"{cmd[0]} failed"
|
||||
|
||||
for signal in outputs.keys():
|
||||
signals[signal] = []
|
||||
|
||||
for line in status.stdout.decode().splitlines():
|
||||
m = re.match(r'\(gold_Outputs( \(bv \S+ \d+\))+\)', line)
|
||||
assert m, f"Incomplete output definition {line!r}"
|
||||
for output, (value, width) in zip(outputs.keys(), re.findall(r'\(bv (\S+) (\d+)\)', line)):
|
||||
assert isinstance(value, str), f"Bad value {value!r}"
|
||||
assert value.startswith(('#b', '#x')), f"Non-binary value {value!r}"
|
||||
assert int(width) == outputs[output], f"Width mismatch for output {output!r} (got {width}, expected {outputs[output]})"
|
||||
int_value = int(value[2:], 16 if value.startswith('#x') else 2)
|
||||
binary_string = format(int_value, '0{}b'.format(width))
|
||||
signals[output].append(binary_string)
|
||||
|
||||
vcd_signals: SignalStepMap = {}
|
||||
for signal, steps in signals.items():
|
||||
vcd_signals[signal] = [(time, f"b{value}") for time, value in enumerate(steps)]
|
||||
|
||||
write_vcd(vcd_path, vcd_signals)
|
|
@ -0,0 +1,381 @@
|
|||
from itertools import chain
|
||||
import random
|
||||
|
||||
def write_rtlil_cell(f, cell_type, inputs, outputs, parameters):
|
||||
f.write('autoidx 1\n')
|
||||
f.write('module \\gold\n')
|
||||
idx = 1
|
||||
for name, width in inputs.items():
|
||||
f.write(f'\twire width {width} input {idx} \\{name}\n')
|
||||
idx += 1
|
||||
for name, width in outputs.items():
|
||||
f.write(f'\twire width {width} output {idx} \\{name}\n')
|
||||
idx += 1
|
||||
f.write(f'\tcell ${cell_type} \\UUT\n')
|
||||
for (name, value) in parameters.items():
|
||||
if value >= 2**32:
|
||||
f.write(f'\t\tparameter \\{name} {value.bit_length()}\'{value:b}\n')
|
||||
else:
|
||||
f.write(f'\t\tparameter \\{name} {value}\n')
|
||||
for name in chain(inputs.keys(), outputs.keys()):
|
||||
f.write(f'\t\tconnect \\{name} \\{name}\n')
|
||||
f.write(f'\tend\nend\n')
|
||||
|
||||
class BaseCell:
|
||||
def __init__(self, name, parameters, inputs, outputs, test_values):
|
||||
self.name = name
|
||||
self.parameters = parameters
|
||||
self.inputs = inputs
|
||||
self.outputs = outputs
|
||||
self.test_values = test_values
|
||||
def get_port_width(self, port, parameters):
|
||||
def parse_specifier(spec):
|
||||
if isinstance(spec, int):
|
||||
return spec
|
||||
if isinstance(spec, str):
|
||||
return parameters[spec]
|
||||
if callable(spec):
|
||||
return spec(parameters)
|
||||
assert False, "expected int, str or lambda"
|
||||
if port in self.inputs:
|
||||
return parse_specifier(self.inputs[port])
|
||||
elif port in self.outputs:
|
||||
return parse_specifier(self.outputs[port])
|
||||
else:
|
||||
assert False, "expected input or output"
|
||||
def generate_tests(self, rnd):
|
||||
def print_parameter(v):
|
||||
if isinstance(v, bool):
|
||||
return "S" if v else "U"
|
||||
else:
|
||||
return str(v)
|
||||
for values in self.test_values:
|
||||
if isinstance(values, int):
|
||||
values = [values]
|
||||
name = '-'.join([print_parameter(v) for v in values])
|
||||
parameters = {parameter: int(values[i]) for i, parameter in enumerate(self.parameters)}
|
||||
if self.is_test_valid(values):
|
||||
yield (name, parameters)
|
||||
def write_rtlil_file(self, path, parameters):
|
||||
inputs = {port: self.get_port_width(port, parameters) for port in self.inputs}
|
||||
outputs = {port: self.get_port_width(port, parameters) for port in self.outputs}
|
||||
with open(path, 'w') as f:
|
||||
write_rtlil_cell(f, self.name, inputs, outputs, parameters)
|
||||
def is_test_valid(self, values):
|
||||
return True
|
||||
|
||||
class UnaryCell(BaseCell):
|
||||
def __init__(self, name, values):
|
||||
super().__init__(name, ['A_WIDTH', 'Y_WIDTH', 'A_SIGNED'], {'A': 'A_WIDTH'}, {'Y': 'Y_WIDTH'}, values)
|
||||
|
||||
class BinaryCell(BaseCell):
|
||||
def __init__(self, name, values):
|
||||
super().__init__(name, ['A_WIDTH', 'B_WIDTH', 'Y_WIDTH', 'A_SIGNED', 'B_SIGNED'], {'A': 'A_WIDTH', 'B': 'B_WIDTH'}, {'Y': 'Y_WIDTH'}, values)
|
||||
|
||||
class ShiftCell(BaseCell):
|
||||
def __init__(self, name, values):
|
||||
super().__init__(name, ['A_WIDTH', 'B_WIDTH', 'Y_WIDTH', 'A_SIGNED', 'B_SIGNED'], {'A': 'A_WIDTH', 'B': 'B_WIDTH'}, {'Y': 'Y_WIDTH'}, values)
|
||||
def is_test_valid(self, values):
|
||||
(a_width, b_width, y_width, a_signed, b_signed) = values
|
||||
if not self.name in ('shift', 'shiftx') and b_signed: return False
|
||||
if self.name == 'shiftx' and a_signed: return False
|
||||
return True
|
||||
|
||||
class MuxCell(BaseCell):
|
||||
def __init__(self, name, values):
|
||||
super().__init__(name, ['WIDTH'], {'A': 'WIDTH', 'B': 'WIDTH', 'S': 1}, {'Y': 'WIDTH'}, values)
|
||||
|
||||
class BWCell(BaseCell):
|
||||
def __init__(self, name, values):
|
||||
inputs = {'A': 'WIDTH', 'B': 'WIDTH'}
|
||||
if name == "bwmux": inputs['S'] = 'WIDTH'
|
||||
super().__init__(name, ['WIDTH'], inputs, {'Y': 'WIDTH'}, values)
|
||||
|
||||
class PMuxCell(BaseCell):
|
||||
def __init__(self, name, values):
|
||||
b_width = lambda par: par['WIDTH'] * par['S_WIDTH']
|
||||
super().__init__(name, ['WIDTH', 'S_WIDTH'], {'A': 'WIDTH', 'B': b_width, 'S': 'S_WIDTH'}, {'Y': 'WIDTH'}, values)
|
||||
|
||||
class BMuxCell(BaseCell):
|
||||
def __init__(self, name, values):
|
||||
a_width = lambda par: par['WIDTH'] << par['S_WIDTH']
|
||||
super().__init__(name, ['WIDTH', 'S_WIDTH'], {'A': a_width, 'S': 'S_WIDTH'}, {'Y': 'WIDTH'}, values)
|
||||
|
||||
class DemuxCell(BaseCell):
|
||||
def __init__(self, name, values):
|
||||
y_width = lambda par: par['WIDTH'] << par['S_WIDTH']
|
||||
super().__init__(name, ['WIDTH', 'S_WIDTH'], {'A': 'WIDTH', 'S': 'S_WIDTH'}, {'Y': y_width}, values)
|
||||
|
||||
class LUTCell(BaseCell):
|
||||
def __init__(self, name, values):
|
||||
super().__init__(name, ['WIDTH', 'LUT'], {'A': 'WIDTH'}, {'Y': 1}, values)
|
||||
def generate_tests(self, rnd):
|
||||
for width in self.test_values:
|
||||
lut = rnd(f'lut-{width}').getrandbits(2**width)
|
||||
yield (f'{width}', {'WIDTH' : width, 'LUT' : lut})
|
||||
|
||||
class ConcatCell(BaseCell):
|
||||
def __init__(self, name, values):
|
||||
y_width = lambda par: par['A_WIDTH'] + par['B_WIDTH']
|
||||
super().__init__(name, ['A_WIDTH', 'B_WIDTH'], {'A': 'A_WIDTH', 'B': 'B_WIDTH'}, {'Y': y_width}, values)
|
||||
|
||||
class SliceCell(BaseCell):
|
||||
def __init__(self, name, values):
|
||||
super().__init__(name, ['A_WIDTH', 'OFFSET', 'Y_WIDTH'], {'A': 'A_WIDTH'}, {'Y': 'Y_WIDTH'}, values)
|
||||
|
||||
class FACell(BaseCell):
|
||||
def __init__(self, name, values):
|
||||
super().__init__(name, ['WIDTH'], {'A': 'WIDTH', 'B': 'WIDTH', 'C': 'WIDTH'}, {'X': 'WIDTH', 'Y': 'WIDTH'}, values)
|
||||
self.sim_preprocessing = "techmap" # because FA is not implemented in yosys sim
|
||||
|
||||
class LCUCell(BaseCell):
|
||||
def __init__(self, name, values):
|
||||
super().__init__(name, ['WIDTH'], {'P': 'WIDTH', 'G': 'WIDTH', 'CI': 1}, {'CO': 'WIDTH'}, values)
|
||||
self.sim_preprocessing = "techmap" # because LCU is not implemented in yosys sim
|
||||
|
||||
class ALUCell(BaseCell):
|
||||
def __init__(self, name, values):
|
||||
super().__init__(name, ['A_WIDTH', 'B_WIDTH', 'Y_WIDTH', 'A_SIGNED', 'B_SIGNED'], {'A': 'A_WIDTH', 'B': 'B_WIDTH', 'CI': 1, 'BI': 1}, {'X': 'Y_WIDTH', 'Y': 'Y_WIDTH', 'CO': 'Y_WIDTH'}, values)
|
||||
self.sim_preprocessing = "techmap" # because ALU is not implemented in yosys sim
|
||||
|
||||
class FailCell(BaseCell):
|
||||
def __init__(self, name):
|
||||
super().__init__(name, [], {}, {})
|
||||
def generate_tests(self, rnd):
|
||||
yield ('', {})
|
||||
def write_rtlil_file(self, path, parameters):
|
||||
raise Exception(f'\'{self.name}\' cell unimplemented in test generator')
|
||||
|
||||
class FFCell(BaseCell):
|
||||
def __init__(self, name, values):
|
||||
super().__init__(name, ['WIDTH'], ['D'], ['Q'], values)
|
||||
def write_rtlil_file(self, path, parameters):
|
||||
from test_functional import yosys_synth
|
||||
verilog_file = path.parent / 'verilog.v'
|
||||
with open(verilog_file, 'w') as f:
|
||||
width = parameters['WIDTH']
|
||||
f.write(f"""
|
||||
module gold(
|
||||
input wire clk,
|
||||
input wire [{width-1}:0] D,
|
||||
output reg [{width-1}:0] Q
|
||||
);
|
||||
initial Q = {width}'b{("101" * width)[:width]};
|
||||
always @(posedge clk)
|
||||
Q <= D;
|
||||
endmodule""")
|
||||
yosys_synth(verilog_file, path)
|
||||
|
||||
class MemCell(BaseCell):
|
||||
def __init__(self, name, values):
|
||||
super().__init__(name, ['DATA_WIDTH', 'ADDR_WIDTH'], {'WA': 'ADDR_WIDTH', 'RA': 'ADDR_WIDTH', 'WD': 'DATA_WIDTH'}, {'RD': 'DATA_WIDTH'}, values)
|
||||
def write_rtlil_file(self, path, parameters):
|
||||
from test_functional import yosys_synth
|
||||
verilog_file = path.parent / 'verilog.v'
|
||||
with open(verilog_file, 'w') as f:
|
||||
f.write("""
|
||||
module gold(
|
||||
input wire clk,
|
||||
input wire [{1}:0] WA,
|
||||
input wire [{0}:0] WD,
|
||||
input wire [{1}:0] RA,
|
||||
output reg [{0}:0] RD
|
||||
);
|
||||
reg [{0}:0] mem[0:{2}];
|
||||
integer i;
|
||||
initial
|
||||
for(i = 0; i <= {2}; i = i + 1)
|
||||
mem[i] = 9192 * (i + 1);
|
||||
always @(*)
|
||||
RD = mem[RA];
|
||||
always @(posedge clk)
|
||||
mem[WA] <= WD;
|
||||
endmodule""".format(parameters['DATA_WIDTH'] - 1, parameters['ADDR_WIDTH'] - 1, 2**parameters['ADDR_WIDTH'] - 1))
|
||||
yosys_synth(verilog_file, path)
|
||||
|
||||
class MemDualCell(BaseCell):
|
||||
def __init__(self, name, values):
|
||||
super().__init__(name, ['DATA_WIDTH', 'ADDR_WIDTH'],
|
||||
{'WA1': 'ADDR_WIDTH', 'WA2': 'ADDR_WIDTH',
|
||||
'RA1': 'ADDR_WIDTH', 'RA2': 'ADDR_WIDTH',
|
||||
'WD1': 'DATA_WIDTH', 'WD2': 'DATA_WIDTH'},
|
||||
{'RD1': 'DATA_WIDTH', 'RD2': 'DATA_WIDTH'}, values)
|
||||
self.sim_preprocessing = "memory_map" # issue #4496 in yosys -sim prevents this example from working without memory_map
|
||||
def write_rtlil_file(self, path, parameters):
|
||||
from test_functional import yosys_synth
|
||||
verilog_file = path.parent / 'verilog.v'
|
||||
with open(verilog_file, 'w') as f:
|
||||
f.write("""
|
||||
module gold(
|
||||
input wire clk,
|
||||
input wire [{1}:0] WA1,
|
||||
input wire [{0}:0] WD1,
|
||||
input wire [{1}:0] WA2,
|
||||
input wire [{0}:0] WD2,
|
||||
input wire [{1}:0] RA1,
|
||||
input wire [{1}:0] RA2,
|
||||
output reg [{0}:0] RD1,
|
||||
output reg [{0}:0] RD2
|
||||
);
|
||||
reg [{0}:0] mem[0:{2}];
|
||||
integer i;
|
||||
initial
|
||||
for(i = 0; i <= {2}; i = i + 1)
|
||||
mem[i] = 9192 * (i + 1);
|
||||
always @(*)
|
||||
RD1 = mem[RA1];
|
||||
always @(*)
|
||||
RD2 = mem[RA2];
|
||||
always @(posedge clk) begin
|
||||
mem[WA1] <= WD1;
|
||||
mem[WA2] <= WD2;
|
||||
end
|
||||
endmodule""".format(parameters['DATA_WIDTH'] - 1, parameters['ADDR_WIDTH'] - 1, 2**parameters['ADDR_WIDTH'] - 1))
|
||||
yosys_synth(verilog_file, path)
|
||||
|
||||
class PicorvCell(BaseCell):
|
||||
def __init__(self):
|
||||
super().__init__("picorv", [], {}, {}, [()])
|
||||
self.smt_max_steps = 50 # z3 is too slow for more steps
|
||||
def write_rtlil_file(self, path, parameters):
|
||||
from test_functional import yosys, base_path, quote
|
||||
tb_file = base_path / 'tests/functional/picorv32_tb.v'
|
||||
cpu_file = base_path / 'tests/functional/picorv32.v'
|
||||
yosys(f"read_verilog {quote(tb_file)} {quote(cpu_file)}; prep -top gold; flatten; write_rtlil {quote(path)}")
|
||||
|
||||
binary_widths = [
|
||||
# try to cover extending A operand, extending B operand, extending/truncating result
|
||||
(16, 32, 48, True, True),
|
||||
(16, 32, 48, False, False),
|
||||
(32, 16, 48, True, True),
|
||||
(32, 16, 48, False, False),
|
||||
(32, 32, 16, True, True),
|
||||
(32, 32, 16, False, False),
|
||||
# have at least one test that checks small inputs, which will exercise the cornercases more
|
||||
(4, 4, 8, True, True),
|
||||
(4, 4, 8, False, False)
|
||||
]
|
||||
|
||||
unary_widths = [
|
||||
(6, 12, True),
|
||||
(6, 12, False),
|
||||
(32, 16, True),
|
||||
(32, 16, False)
|
||||
]
|
||||
|
||||
# note that meaningless combinations of signednesses are eliminated,
|
||||
# like e.g. most shift operations don't take signed shift amounts
|
||||
shift_widths = [
|
||||
# one set of tests that definitely checks all possible shift amounts
|
||||
# with a bigger result width to make sure it's not truncated
|
||||
(32, 6, 64, True, False),
|
||||
(32, 6, 64, False, False),
|
||||
(32, 6, 64, True, True),
|
||||
(32, 6, 64, False, True),
|
||||
# one set that checks very oversized shifts
|
||||
(32, 32, 64, True, False),
|
||||
(32, 32, 64, False, False),
|
||||
(32, 32, 64, True, True),
|
||||
(32, 32, 64, False, True),
|
||||
# at least one test where the result is going to be truncated
|
||||
(32, 6, 16, False, False),
|
||||
# since 1-bit shifts are special cased
|
||||
(1, 4, 1, False, False),
|
||||
(1, 4, 1, True, False),
|
||||
]
|
||||
|
||||
rtlil_cells = [
|
||||
UnaryCell("not", unary_widths),
|
||||
UnaryCell("pos", unary_widths),
|
||||
UnaryCell("neg", unary_widths),
|
||||
BinaryCell("and", binary_widths),
|
||||
BinaryCell("or", binary_widths),
|
||||
BinaryCell("xor", binary_widths),
|
||||
BinaryCell("xnor", binary_widths),
|
||||
UnaryCell("reduce_and", unary_widths),
|
||||
UnaryCell("reduce_or", unary_widths),
|
||||
UnaryCell("reduce_xor", unary_widths),
|
||||
UnaryCell("reduce_xnor", unary_widths),
|
||||
UnaryCell("reduce_bool", unary_widths),
|
||||
ShiftCell("shl", shift_widths),
|
||||
ShiftCell("shr", shift_widths),
|
||||
ShiftCell("sshl", shift_widths),
|
||||
ShiftCell("sshr", shift_widths),
|
||||
ShiftCell("shift", shift_widths),
|
||||
ShiftCell("shiftx", shift_widths),
|
||||
FACell("fa", [8, 20]),
|
||||
LCUCell("lcu", [1, 10]),
|
||||
ALUCell("alu", binary_widths),
|
||||
BinaryCell("lt", binary_widths),
|
||||
BinaryCell("le", binary_widths),
|
||||
BinaryCell("eq", binary_widths),
|
||||
BinaryCell("ne", binary_widths),
|
||||
BinaryCell("eqx", binary_widths),
|
||||
BinaryCell("nex", binary_widths),
|
||||
BinaryCell("ge", binary_widths),
|
||||
BinaryCell("gt", binary_widths),
|
||||
BinaryCell("add", binary_widths),
|
||||
BinaryCell("sub", binary_widths),
|
||||
BinaryCell("mul", binary_widths),
|
||||
# BinaryCell("macc"),
|
||||
BinaryCell("div", binary_widths),
|
||||
BinaryCell("mod", binary_widths),
|
||||
BinaryCell("divfloor", binary_widths),
|
||||
BinaryCell("modfloor", binary_widths),
|
||||
BinaryCell("pow", binary_widths),
|
||||
UnaryCell("logic_not", unary_widths),
|
||||
BinaryCell("logic_and", binary_widths),
|
||||
BinaryCell("logic_or", binary_widths),
|
||||
SliceCell("slice", [(32, 10, 15), (8, 0, 4), (10, 0, 10)]),
|
||||
ConcatCell("concat", [(16, 16), (8, 14), (20, 10)]),
|
||||
MuxCell("mux", [10, 16, 40]),
|
||||
BMuxCell("bmux", [(10, 1), (10, 2), (10, 4)]),
|
||||
PMuxCell("pmux", [(10, 1), (10, 4), (20, 4)]),
|
||||
DemuxCell("demux", [(10, 1), (32, 2), (16, 4)]),
|
||||
LUTCell("lut", [4, 6, 8]),
|
||||
# ("sop", ["A", "Y"]),
|
||||
# ("tribuf", ["A", "EN", "Y"]),
|
||||
# ("specify2", ["EN", "SRC", "DST"]),
|
||||
# ("specify3", ["EN", "SRC", "DST", "DAT"]),
|
||||
# ("specrule", ["EN_SRC", "EN_DST", "SRC", "DST"]),
|
||||
BWCell("bweqx", [10, 16, 40]),
|
||||
BWCell("bwmux", [10, 16, 40]),
|
||||
FFCell("ff", [10, 20, 40]),
|
||||
MemCell("mem", [(16, 4)]),
|
||||
MemDualCell("mem-dual", [(16, 4)]),
|
||||
# ("assert", ["A", "EN"]),
|
||||
# ("assume", ["A", "EN"]),
|
||||
# ("live", ["A", "EN"]),
|
||||
# ("fair", ["A", "EN"]),
|
||||
# ("cover", ["A", "EN"]),
|
||||
# ("initstate", ["Y"]),
|
||||
# ("anyconst", ["Y"]),
|
||||
# ("anyseq", ["Y"]),
|
||||
# ("anyinit", ["D", "Q"]),
|
||||
# ("allconst", ["Y"]),
|
||||
# ("allseq", ["Y"]),
|
||||
# ("equiv", ["A", "B", "Y"]),
|
||||
# ("print", ["EN", "TRG", "ARGS"]),
|
||||
# ("check", ["A", "EN", "TRG", "ARGS"]),
|
||||
# ("set_tag", ["A", "SET", "CLR", "Y"]),
|
||||
# ("get_tag", ["A", "Y"]),
|
||||
# ("overwrite_tag", ["A", "SET", "CLR"]),
|
||||
# ("original_tag", ["A", "Y"]),
|
||||
# ("future_ff", ["A", "Y"]),
|
||||
# ("scopeinfo", []),
|
||||
PicorvCell()
|
||||
]
|
||||
|
||||
def generate_test_cases(per_cell, rnd):
|
||||
tests = []
|
||||
names = []
|
||||
for cell in rtlil_cells:
|
||||
seen_names = set()
|
||||
for (name, parameters) in cell.generate_tests(rnd):
|
||||
if not name in seen_names:
|
||||
seen_names.add(name)
|
||||
tests.append((cell, parameters))
|
||||
names.append(f'{cell.name}-{name}' if name != '' else cell.name)
|
||||
if per_cell is not None and len(seen_names) >= per_cell:
|
||||
break
|
||||
return (names, tests)
|
|
@ -0,0 +1,2 @@
|
|||
#!/usr/bin/env bash
|
||||
pytest -v -m "not smt and not rkt" "$@"
|
|
@ -0,0 +1,188 @@
|
|||
import sys
|
||||
import argparse
|
||||
import os
|
||||
import smtio
|
||||
import re
|
||||
|
||||
class SExprParserError(Exception):
|
||||
pass
|
||||
|
||||
class SExprParser:
|
||||
def __init__(self):
|
||||
self.peekbuf = None
|
||||
self.stack = [[]]
|
||||
self.atom_pattern = re.compile(r'[a-zA-Z0-9~!@$%^&*_\-+=<>.?/#]+')
|
||||
def parse_line(self, line):
|
||||
ptr = 0
|
||||
while ptr < len(line):
|
||||
if line[ptr].isspace():
|
||||
ptr += 1
|
||||
elif line[ptr] == ';':
|
||||
break
|
||||
elif line[ptr] == '(':
|
||||
ptr += 1
|
||||
self.stack.append([])
|
||||
elif line[ptr] == ')':
|
||||
ptr += 1
|
||||
assert len(self.stack) > 1, "too many closed parentheses"
|
||||
v = self.stack.pop()
|
||||
self.stack[-1].append(v)
|
||||
else:
|
||||
match = self.atom_pattern.match(line, ptr)
|
||||
if match is None:
|
||||
raise SExprParserError(f"invalid character '{line[ptr]}' in line '{line}'")
|
||||
start, ptr = match.span()
|
||||
self.stack[-1].append(line[start:ptr])
|
||||
def finish(self):
|
||||
assert len(self.stack) == 1, "too many open parentheses"
|
||||
def retrieve(self):
|
||||
rv, self.stack[0] = self.stack[0], []
|
||||
return rv
|
||||
|
||||
def simulate_smt_with_smtio(smt_file_path, vcd_path, smt_io, num_steps, rnd):
|
||||
inputs = {}
|
||||
outputs = {}
|
||||
states = {}
|
||||
|
||||
def handle_datatype(lst):
|
||||
print(lst)
|
||||
datatype_name = lst[1]
|
||||
declarations = lst[2][0][1:] # Skip the first item (e.g., 'mk_inputs')
|
||||
if datatype_name.endswith("_Inputs"):
|
||||
for declaration in declarations:
|
||||
input_name = declaration[0]
|
||||
bitvec_size = declaration[1][2]
|
||||
assert input_name.startswith("gold_Inputs_")
|
||||
inputs[input_name[len("gold_Inputs_"):]] = int(bitvec_size)
|
||||
elif datatype_name.endswith("_Outputs"):
|
||||
for declaration in declarations:
|
||||
output_name = declaration[0]
|
||||
bitvec_size = declaration[1][2]
|
||||
assert output_name.startswith("gold_Outputs_")
|
||||
outputs[output_name[len("gold_Outputs_"):]] = int(bitvec_size)
|
||||
elif datatype_name.endswith("_State"):
|
||||
for declaration in declarations:
|
||||
state_name = declaration[0]
|
||||
assert state_name.startswith("gold_State_")
|
||||
if declaration[1][0] == "_":
|
||||
states[state_name[len("gold_State_"):]] = int(declaration[1][2])
|
||||
else:
|
||||
states[state_name[len("gold_State_"):]] = (declaration[1][1][2], declaration[1][2][2])
|
||||
|
||||
parser = SExprParser()
|
||||
with open(smt_file_path, 'r') as smt_file:
|
||||
for line in smt_file:
|
||||
parser.parse_line(line)
|
||||
for expr in parser.retrieve():
|
||||
smt_io.write(smt_io.unparse(expr))
|
||||
if expr[0] == "declare-datatype":
|
||||
handle_datatype(expr)
|
||||
|
||||
parser.finish()
|
||||
assert smt_io.check_sat() == 'sat'
|
||||
|
||||
def set_step(inputs, step):
|
||||
# This function assumes 'inputs' is a dictionary like {"A": 5, "B": 4}
|
||||
# and 'input_values' is a dictionary like {"A": 5, "B": 13} specifying the concrete values for each input.
|
||||
|
||||
mk_inputs_parts = []
|
||||
for input_name, width in inputs.items():
|
||||
value = rnd.getrandbits(width) # Generate a random number up to the maximum value for the bit size
|
||||
binary_string = format(value, '0{}b'.format(width)) # Convert value to binary with leading zeros
|
||||
mk_inputs_parts.append(f"#b{binary_string}")
|
||||
|
||||
mk_inputs_call = "gold_Inputs " + " ".join(mk_inputs_parts)
|
||||
return [
|
||||
f"(define-const test_inputs_step_n{step} gold_Inputs ({mk_inputs_call}))\n",
|
||||
f"(define-const test_results_step_n{step} (Pair gold_Outputs gold_State) (gold test_inputs_step_n{step} test_state_step_n{step}))\n",
|
||||
f"(define-const test_outputs_step_n{step} gold_Outputs (first test_results_step_n{step}))\n",
|
||||
f"(define-const test_state_step_n{step+1} gold_State (second test_results_step_n{step}))\n",
|
||||
]
|
||||
|
||||
smt_commands = [f"(define-const test_state_step_n0 gold_State gold-initial)\n"]
|
||||
for step in range(num_steps):
|
||||
for step_command in set_step(inputs, step):
|
||||
smt_commands.append(step_command)
|
||||
|
||||
for command in smt_commands:
|
||||
smt_io.write(command)
|
||||
|
||||
assert smt_io.check_sat() == 'sat'
|
||||
|
||||
# Store signal values
|
||||
signals = {name: [] for name in list(inputs.keys()) + list(outputs.keys())}
|
||||
# Retrieve and print values for each state
|
||||
def hex_to_bin(value):
|
||||
if value.startswith('x'):
|
||||
hex_value = value[1:] # Remove the 'x' prefix
|
||||
bin_value = bin(int(hex_value, 16))[2:] # Convert to binary and remove the '0b' prefix
|
||||
return f'b{bin_value.zfill(len(hex_value) * 4)}' # Add 'b' prefix and pad with zeros
|
||||
return value
|
||||
|
||||
combined_assertions = []
|
||||
for step in range(num_steps):
|
||||
print(f"Values for step {step + 1}:")
|
||||
for input_name, width in inputs.items():
|
||||
value = smt_io.get(f'(gold_Inputs_{input_name} test_inputs_step_n{step})')
|
||||
value = hex_to_bin(value[1:])
|
||||
print(f" {input_name}: {value}")
|
||||
signals[input_name].append((step, value))
|
||||
for output_name, width in outputs.items():
|
||||
value = smt_io.get(f'(gold_Outputs_{output_name} test_outputs_step_n{step})')
|
||||
value = hex_to_bin(value[1:])
|
||||
print(f" {output_name}: {value}")
|
||||
signals[output_name].append((step, value))
|
||||
combined_assertions.append(f'(= (gold_Outputs_{output_name} test_outputs_step_n{step}) #{value})')
|
||||
# Create a single assertion covering all timesteps
|
||||
combined_condition = " ".join(combined_assertions)
|
||||
smt_io.write(f'(assert (not (and {combined_condition})))')
|
||||
|
||||
# Check the combined assertion
|
||||
assert smt_io.check_sat(["unsat"]) == "unsat"
|
||||
|
||||
def write_vcd(filename, signals, timescale='1 ns', date='today'):
|
||||
with open(filename, 'w') as f:
|
||||
# Write the header
|
||||
f.write(f"$date\n {date}\n$end\n")
|
||||
f.write(f"$timescale {timescale} $end\n")
|
||||
|
||||
# Declare signals
|
||||
f.write("$scope module gold $end\n")
|
||||
for signal_name, changes in signals.items():
|
||||
signal_size = len(changes[0][1])
|
||||
f.write(f"$var wire {signal_size - 1} {signal_name} {signal_name} $end\n")
|
||||
f.write("$upscope $end\n")
|
||||
f.write("$enddefinitions $end\n")
|
||||
|
||||
# Collect all unique timestamps
|
||||
timestamps = sorted(set(time for changes in signals.values() for time, _ in changes))
|
||||
|
||||
# Write initial values
|
||||
f.write("#0\n")
|
||||
for signal_name, changes in signals.items():
|
||||
for time, value in changes:
|
||||
if time == 0:
|
||||
f.write(f"{value} {signal_name}\n")
|
||||
|
||||
# Write value changes
|
||||
for time in timestamps:
|
||||
if time != 0:
|
||||
f.write(f"#{time}\n")
|
||||
for signal_name, changes in signals.items():
|
||||
for change_time, value in changes:
|
||||
if change_time == time:
|
||||
f.write(f"{value} {signal_name}\n")
|
||||
|
||||
|
||||
write_vcd(vcd_path, signals)
|
||||
|
||||
def simulate_smt(smt_file_path, vcd_path, num_steps, rnd):
|
||||
so = smtio.SmtOpts()
|
||||
so.solver = "z3"
|
||||
so.logic = "ABV"
|
||||
so.debug_print = True
|
||||
smt_io = smtio.SmtIo(opts=so)
|
||||
try:
|
||||
simulate_smt_with_smtio(smt_file_path, vcd_path, smt_io, num_steps, rnd)
|
||||
finally:
|
||||
smt_io.p_close()
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,94 @@
|
|||
import subprocess
|
||||
import pytest
|
||||
import sys
|
||||
import shlex
|
||||
from pathlib import Path
|
||||
|
||||
base_path = Path(__file__).resolve().parent.parent.parent
|
||||
|
||||
# quote a string or pathlib path so that it can be used by bash or yosys
|
||||
# TODO: is this really appropriate for yosys?
|
||||
def quote(path):
|
||||
return shlex.quote(str(path))
|
||||
|
||||
# run a shell command and require the return code to be 0
|
||||
def run(cmd, **kwargs):
|
||||
print(' '.join([quote(x) for x in cmd]))
|
||||
status = subprocess.run(cmd, **kwargs)
|
||||
assert status.returncode == 0, f"{cmd[0]} failed"
|
||||
|
||||
def yosys(script):
|
||||
run([base_path / 'yosys', '-Q', '-p', script])
|
||||
|
||||
def compile_cpp(in_path, out_path, args):
|
||||
run(['g++', '-g', '-std=c++17'] + args + [str(in_path), '-o', str(out_path)])
|
||||
|
||||
def yosys_synth(verilog_file, rtlil_file):
|
||||
yosys(f"read_verilog {quote(verilog_file)} ; prep ; write_rtlil {quote(rtlil_file)}")
|
||||
|
||||
# simulate an rtlil file with yosys, comparing with a given vcd file, and writing out the yosys simulation results into a second vcd file
|
||||
def yosys_sim(rtlil_file, vcd_reference_file, vcd_out_file, preprocessing = ""):
|
||||
try:
|
||||
yosys(f"read_rtlil {quote(rtlil_file)}; {preprocessing}; sim -r {quote(vcd_reference_file)} -scope gold -vcd {quote(vcd_out_file)} -timescale 1us -sim-gold -fst-noinit")
|
||||
except:
|
||||
# if yosys sim fails it's probably because of a simulation mismatch
|
||||
# since yosys sim aborts on simulation mismatch to generate vcd output
|
||||
# we have to re-run with a different set of flags
|
||||
# on this run we ignore output and return code, we just want a best-effort attempt to get a vcd
|
||||
subprocess.run([base_path / 'yosys', '-Q', '-p',
|
||||
f'read_rtlil {quote(rtlil_file)}; sim -vcd {quote(vcd_out_file)} -a -r {quote(vcd_reference_file)} -scope gold -timescale 1us -fst-noinit'],
|
||||
capture_output=True, check=False)
|
||||
raise
|
||||
|
||||
def test_cxx(cell, parameters, tmp_path, num_steps, rnd):
|
||||
rtlil_file = tmp_path / 'rtlil.il'
|
||||
vcdharness_cc_file = base_path / 'tests/functional/vcd_harness.cc'
|
||||
cc_file = tmp_path / 'my_module_functional_cxx.cc'
|
||||
vcdharness_exe_file = tmp_path / 'a.out'
|
||||
vcd_functional_file = tmp_path / 'functional.vcd'
|
||||
vcd_yosys_sim_file = tmp_path / 'yosys.vcd'
|
||||
|
||||
cell.write_rtlil_file(rtlil_file, parameters)
|
||||
yosys(f"read_rtlil {quote(rtlil_file)} ; clk2fflogic ; write_functional_cxx {quote(cc_file)}")
|
||||
compile_cpp(vcdharness_cc_file, vcdharness_exe_file, ['-I', tmp_path, '-I', str(base_path / 'backends/functional/cxx_runtime')])
|
||||
seed = str(rnd(cell.name + "-cxx").getrandbits(32))
|
||||
run([str(vcdharness_exe_file.resolve()), str(vcd_functional_file), str(num_steps), str(seed)])
|
||||
yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', ''))
|
||||
|
||||
@pytest.mark.smt
|
||||
def test_smt(cell, parameters, tmp_path, num_steps, rnd):
|
||||
import smt_vcd
|
||||
|
||||
rtlil_file = tmp_path / 'rtlil.il'
|
||||
smt_file = tmp_path / 'smtlib.smt'
|
||||
vcd_functional_file = tmp_path / 'functional.vcd'
|
||||
vcd_yosys_sim_file = tmp_path / 'yosys.vcd'
|
||||
|
||||
if hasattr(cell, 'smt_max_steps'):
|
||||
num_steps = min(num_steps, cell.smt_max_steps)
|
||||
|
||||
cell.write_rtlil_file(rtlil_file, parameters)
|
||||
yosys(f"read_rtlil {quote(rtlil_file)} ; clk2fflogic ; write_functional_smt2 {quote(smt_file)}")
|
||||
run(['z3', smt_file]) # check if output is valid smtlib before continuing
|
||||
smt_vcd.simulate_smt(smt_file, vcd_functional_file, num_steps, rnd(cell.name + "-smt"))
|
||||
yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', ''))
|
||||
|
||||
@pytest.mark.rkt
|
||||
def test_rkt(cell, parameters, tmp_path, num_steps, rnd):
|
||||
import rkt_vcd
|
||||
|
||||
rtlil_file = tmp_path / 'rtlil.il'
|
||||
rkt_file = tmp_path / 'smtlib.rkt'
|
||||
vcd_functional_file = tmp_path / 'functional.vcd'
|
||||
vcd_yosys_sim_file = tmp_path / 'yosys.vcd'
|
||||
|
||||
cell.write_rtlil_file(rtlil_file, parameters)
|
||||
yosys(f"read_rtlil {quote(rtlil_file)} ; clk2fflogic ; write_functional_rosette -provides {quote(rkt_file)}")
|
||||
rkt_vcd.simulate_rosette(rkt_file, vcd_functional_file, num_steps, rnd(cell.name + "-rkt"))
|
||||
yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', ''))
|
||||
|
||||
def test_print_graph(tmp_path):
|
||||
tb_file = base_path / 'tests/functional/picorv32_tb.v'
|
||||
cpu_file = base_path / 'tests/functional/picorv32.v'
|
||||
# currently we only check that we can print the graph without getting an error, not that it prints anything sensibl
|
||||
yosys(f"read_verilog {quote(tb_file)} {quote(cpu_file)}; prep -top gold; flatten; clk2fflogic; test_generic")
|
|
@ -0,0 +1,146 @@
|
|||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <random>
|
||||
#include <ctype.h>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "my_module_functional_cxx.cc"
|
||||
|
||||
class VcdFile {
|
||||
std::ofstream &ofs;
|
||||
std::string code_alloc = "!";
|
||||
std::unordered_map<std::string, std::string> codes;
|
||||
std::string name_mangle(std::string name) {
|
||||
std::string ret = name;
|
||||
bool escape = ret.empty() || !isalpha(ret[0]) && ret[0] != '_';
|
||||
for(size_t i = 0; i < ret.size(); i++) {
|
||||
if(isspace(ret[i])) ret[i] = '_';
|
||||
if(!isalnum(ret[i]) && ret[i] != '_' && ret[i] != '$')
|
||||
escape = true;
|
||||
}
|
||||
if(escape)
|
||||
return "\\" + ret;
|
||||
else
|
||||
return ret;
|
||||
}
|
||||
std::string allocate_code() {
|
||||
std::string ret = code_alloc;
|
||||
for (size_t i = 0; i < code_alloc.size(); i++)
|
||||
if (code_alloc[i]++ == '~')
|
||||
code_alloc[i] = '!';
|
||||
else
|
||||
return ret;
|
||||
code_alloc.push_back('!');
|
||||
return ret;
|
||||
}
|
||||
public:
|
||||
VcdFile(std::ofstream &ofs) : ofs(ofs) {}
|
||||
struct DumpHeader {
|
||||
VcdFile *file;
|
||||
explicit DumpHeader(VcdFile *file) : file(file) {}
|
||||
template <size_t n> void operator()(const char *name, Signal<n> value)
|
||||
{
|
||||
auto it = file->codes.find(name);
|
||||
if(it == file->codes.end())
|
||||
it = file->codes.emplace(name, file->allocate_code()).first;
|
||||
file->ofs << "$var wire " << n << " " << it->second << " " << file->name_mangle(name) << " $end\n";
|
||||
}
|
||||
template <size_t n, size_t m> void operator()(const char *name, Memory<n, m> value) {}
|
||||
};
|
||||
struct Dump {
|
||||
VcdFile *file;
|
||||
explicit Dump(VcdFile *file) : file(file) {}
|
||||
template <size_t n> void operator()(const char *name, Signal<n> value)
|
||||
{
|
||||
if (n == 1) {
|
||||
file->ofs << (value[0] ? '1' : '0');
|
||||
file->ofs << file->codes.at(name) << "\n";
|
||||
} else {
|
||||
file->ofs << "b";
|
||||
for (size_t i = n; i-- > 0;)
|
||||
file->ofs << (value[i] ? '1' : '0');
|
||||
file->ofs << " " << file->codes.at(name) << "\n";
|
||||
}
|
||||
}
|
||||
template <size_t n, size_t m> void operator()(const char *name, Memory<n, m> value) {}
|
||||
};
|
||||
void begin_header() {
|
||||
constexpr int number_timescale = 1;
|
||||
const std::string units_timescale = "us";
|
||||
ofs << "$timescale " << number_timescale << " " << units_timescale << " $end\n";
|
||||
ofs << "$scope module gold $end\n";
|
||||
}
|
||||
void end_header() {
|
||||
ofs << "$enddefinitions $end\n$dumpvars\n";
|
||||
}
|
||||
template<typename... Args> void header(Args ...args) {
|
||||
begin_header();
|
||||
DumpHeader d(this);
|
||||
(args.visit(d), ...);
|
||||
end_header();
|
||||
}
|
||||
void begin_data(int step) {
|
||||
ofs << "#" << step << "\n";
|
||||
}
|
||||
template<typename... Args> void data(int step, Args ...args) {
|
||||
begin_data(step);
|
||||
Dump d(this);
|
||||
(args.visit(d), ...);
|
||||
}
|
||||
DumpHeader dump_header() { return DumpHeader(this); }
|
||||
Dump dump() { return Dump(this); }
|
||||
};
|
||||
|
||||
template <size_t n> Signal<n> random_signal(std::mt19937 &gen)
|
||||
{
|
||||
std::uniform_int_distribution<uint32_t> dist;
|
||||
std::array<uint32_t, (n + 31) / 32> words;
|
||||
for (auto &w : words)
|
||||
w = dist(gen);
|
||||
return Signal<n>::from_array(words);
|
||||
}
|
||||
|
||||
struct Randomize {
|
||||
std::mt19937 &gen;
|
||||
Randomize(std::mt19937 &gen) : gen(gen) {}
|
||||
|
||||
template <size_t n> void operator()(const char *, Signal<n> &signal) { signal = random_signal<n>(gen); }
|
||||
};
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc != 4) {
|
||||
std::cerr << "Usage: " << argv[0] << " <functional_vcd_filename> <steps> <seed>\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
const std::string functional_vcd_filename = argv[1];
|
||||
const int steps = atoi(argv[2]);
|
||||
const uint32_t seed = atoi(argv[3]);
|
||||
|
||||
gold::Inputs inputs;
|
||||
gold::Outputs outputs;
|
||||
gold::State state;
|
||||
gold::State next_state;
|
||||
|
||||
std::ofstream vcd_file(functional_vcd_filename);
|
||||
VcdFile vcd(vcd_file);
|
||||
vcd.header(inputs, outputs, state);
|
||||
|
||||
std::mt19937 gen(seed);
|
||||
|
||||
gold::initialize(state);
|
||||
|
||||
for (int step = 0; step < steps; ++step) {
|
||||
inputs.visit(Randomize(gen));
|
||||
|
||||
gold::eval(inputs, outputs, state, next_state);
|
||||
vcd.data(step, inputs, outputs, state);
|
||||
|
||||
state = next_state;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue