mirror of https://github.com/YosysHQ/yosys.git
dfflibmap: move expression code into libparse
This commit is contained in:
parent
08ed2c765e
commit
4f40187759
|
@ -26,248 +26,6 @@
|
|||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
struct string_helper {
|
||||
std::string s, expr;
|
||||
|
||||
string_helper(std::string s) : s{s}, expr{s} {}
|
||||
|
||||
bool empty() {
|
||||
return s.empty();
|
||||
}
|
||||
|
||||
char peek() {
|
||||
return s[0];
|
||||
}
|
||||
|
||||
char next() {
|
||||
char c = s[0];
|
||||
s = s.substr(1, s.size());
|
||||
return c;
|
||||
}
|
||||
|
||||
std::string pin() {
|
||||
auto length = s.find_first_of("\t()'!^*& +|");
|
||||
auto pin = s.substr(0, length);
|
||||
s = s.substr(length, s.size());
|
||||
return pin;
|
||||
}
|
||||
|
||||
std::string full_expr() {
|
||||
return expr;
|
||||
}
|
||||
};
|
||||
|
||||
enum expression_kind {
|
||||
AND,
|
||||
OR,
|
||||
NOT,
|
||||
XOR,
|
||||
// the standard specifies constants, but they're probably rare in practice.
|
||||
PIN,
|
||||
EMPTY
|
||||
};
|
||||
|
||||
struct expression_tree {
|
||||
expression_kind kind;
|
||||
std::string name;
|
||||
std::vector<expression_tree> children;
|
||||
|
||||
expression_tree() : kind(expression_kind::EMPTY) {}
|
||||
|
||||
void get_pin_names(pool<std::string>& names) {
|
||||
if (kind == expression_kind::PIN) {
|
||||
names.insert(name);
|
||||
} else {
|
||||
for (auto& child : children)
|
||||
child.get_pin_names(names);
|
||||
}
|
||||
}
|
||||
|
||||
bool eval(dict<std::string, bool>& values) {
|
||||
bool result = false;
|
||||
switch (kind) {
|
||||
case expression_kind::AND:
|
||||
result = true;
|
||||
for (auto& child : children)
|
||||
result &= child.eval(values);
|
||||
return result;
|
||||
case expression_kind::OR:
|
||||
result = false;
|
||||
for (auto& child : children)
|
||||
result |= child.eval(values);
|
||||
return result;
|
||||
case expression_kind::NOT:
|
||||
log_assert(children.size() == 1);
|
||||
return !children[0].eval(values);
|
||||
case expression_kind::XOR:
|
||||
result = false;
|
||||
for (auto& child : children)
|
||||
result ^= child.eval(values);
|
||||
return result;
|
||||
case expression_kind::PIN:
|
||||
return values.at(name);
|
||||
case expression_kind::EMPTY:
|
||||
log_assert(false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string enable_pin(const std::string& ff_output, std::string &data_name, bool &data_not_inverted, bool &enable_not_inverted) {
|
||||
auto pin_name_pool = pool<std::string>{};
|
||||
get_pin_names(pin_name_pool);
|
||||
if (pin_name_pool.size() != 3)
|
||||
return "";
|
||||
if (!pin_name_pool.count(ff_output))
|
||||
return "";
|
||||
pin_name_pool.erase(ff_output);
|
||||
auto pins = std::vector<std::string>(pin_name_pool.begin(), pin_name_pool.end());
|
||||
int lut = 0;
|
||||
for (int n = 0; n < 8; n++) {
|
||||
auto values = dict<std::string, bool>{};
|
||||
values.insert(std::make_pair(pins[0], (n & 1) == 1));
|
||||
values.insert(std::make_pair(pins[1], (n & 2) == 2));
|
||||
values.insert(std::make_pair(ff_output, (n & 4) == 4));
|
||||
if (eval(values))
|
||||
lut |= 1 << n;
|
||||
}
|
||||
// the ff output Q is in a known bit location, so we now just have to compare the LUT mask to known values to find the enable pin and polarity.
|
||||
if (lut == 0xD8) {
|
||||
data_not_inverted = true;
|
||||
data_name = pins[1];
|
||||
enable_not_inverted = true;
|
||||
return pins[0];
|
||||
}
|
||||
if (lut == 0xB8) {
|
||||
data_not_inverted = true;
|
||||
data_name = pins[0];
|
||||
enable_not_inverted = true;
|
||||
return pins[1];
|
||||
}
|
||||
if (lut == 0xE4) {
|
||||
data_not_inverted = true;
|
||||
data_name = pins[1];
|
||||
enable_not_inverted = false;
|
||||
return pins[0];
|
||||
}
|
||||
if (lut == 0xE2) {
|
||||
data_not_inverted = true;
|
||||
data_name = pins[0];
|
||||
enable_not_inverted = false;
|
||||
return pins[1];
|
||||
}
|
||||
// this does not match an enable flop.
|
||||
data_not_inverted = false;
|
||||
data_name = "";
|
||||
enable_not_inverted = false;
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
// https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html
|
||||
expression_tree parse_expression(string_helper &s, int min_prio = 0) {
|
||||
if (s.empty())
|
||||
return expression_tree{};
|
||||
|
||||
char c = s.peek();
|
||||
auto lhs = expression_tree{};
|
||||
|
||||
while (isspace(c)) {
|
||||
if (s.empty())
|
||||
return lhs;
|
||||
s.next();
|
||||
c = s.peek();
|
||||
}
|
||||
|
||||
if (isalpha(c)) { // pin
|
||||
lhs.kind = expression_kind::PIN;
|
||||
lhs.name = s.pin();
|
||||
} else if (c == '(') { // parens
|
||||
s.next();
|
||||
lhs = parse_expression(s);
|
||||
if (s.peek() != ')') {
|
||||
log_warning("expected ')' instead of '%c' while parsing Liberty expression '%s'\n", s.peek(), s.full_expr().c_str());
|
||||
return lhs;
|
||||
}
|
||||
s.next();
|
||||
} else if (c == '!') { // prefix NOT
|
||||
s.next();
|
||||
lhs.kind = expression_kind::NOT;
|
||||
lhs.children.push_back(parse_expression(s, 7));
|
||||
} else {
|
||||
log_warning("unrecognised character '%c' while parsing Liberty expression '%s'\n", c, s.full_expr().c_str());
|
||||
return lhs;
|
||||
}
|
||||
|
||||
do {
|
||||
if (s.empty())
|
||||
break;
|
||||
|
||||
c = s.peek();
|
||||
|
||||
while (isspace(c)) {
|
||||
if (s.empty())
|
||||
return lhs;
|
||||
s.next();
|
||||
c = s.peek();
|
||||
}
|
||||
|
||||
if (c == '\'') { // postfix NOT
|
||||
if (min_prio > 7)
|
||||
break;
|
||||
s.next();
|
||||
|
||||
auto n = expression_tree{};
|
||||
n.kind = expression_kind::NOT;
|
||||
n.children.push_back(lhs);
|
||||
lhs = std::move(n);
|
||||
|
||||
continue;
|
||||
} else if (c == '^') { // infix XOR
|
||||
if (min_prio > 5)
|
||||
break;
|
||||
s.next();
|
||||
|
||||
auto rhs = parse_expression(s, 6);
|
||||
auto n = expression_tree{};
|
||||
n.kind = expression_kind::XOR;
|
||||
n.children.push_back(lhs);
|
||||
n.children.push_back(rhs);
|
||||
lhs = std::move(n);
|
||||
|
||||
continue;
|
||||
} else if (c == '&' || c == '*') { // infix AND
|
||||
// technically space should be considered infix AND. it seems rare in practice.
|
||||
if (min_prio > 3)
|
||||
break;
|
||||
s.next();
|
||||
|
||||
auto rhs = parse_expression(s, 4);
|
||||
auto n = expression_tree{};
|
||||
n.kind = expression_kind::AND;
|
||||
n.children.push_back(lhs);
|
||||
n.children.push_back(rhs);
|
||||
lhs = std::move(n);
|
||||
|
||||
continue;
|
||||
} else if (c == '+' || c == '|') { // infix OR
|
||||
if (min_prio > 1)
|
||||
break;
|
||||
s.next();
|
||||
|
||||
auto rhs = parse_expression(s, 2);
|
||||
auto n = expression_tree{};
|
||||
n.kind = expression_kind::OR;
|
||||
n.children.push_back(lhs);
|
||||
n.children.push_back(rhs);
|
||||
lhs = std::move(n);
|
||||
|
||||
continue;
|
||||
}
|
||||
} while (false);
|
||||
|
||||
return lhs;
|
||||
}
|
||||
|
||||
struct cell_mapping {
|
||||
IdString cell_name;
|
||||
std::map<std::string, char> ports;
|
||||
|
@ -325,10 +83,13 @@ static void logmap_all()
|
|||
|
||||
static bool parse_next_state(const LibertyAst *cell, const LibertyAst *attr, std::string &data_name, bool &data_not_inverted, std::string &enable_name, bool &enable_not_inverted)
|
||||
{
|
||||
static pool<std::string> warned_cells{};
|
||||
|
||||
if (cell == nullptr || attr == nullptr || attr->value.empty())
|
||||
return false;
|
||||
|
||||
std::string expr = attr->value;
|
||||
auto expr = attr->value;
|
||||
auto cell_name = cell->args[0];
|
||||
|
||||
for (size_t pos = expr.find_first_of("\" \t"); pos != std::string::npos; pos = expr.find_first_of("\" \t"))
|
||||
expr.erase(pos, 1);
|
||||
|
@ -350,11 +111,14 @@ static bool parse_next_state(const LibertyAst *cell, const LibertyAst *attr, std
|
|||
return true;
|
||||
|
||||
// the next_state variable isn't just a pin name; perhaps this is an enable?
|
||||
auto helper = string_helper(expr);
|
||||
auto tree = parse_expression(helper);
|
||||
auto helper = LibertyExpression::Lexer(expr);
|
||||
auto tree = LibertyExpression::parse(helper);
|
||||
|
||||
if (tree.kind == expression_kind::EMPTY) {
|
||||
log_warning("Invalid expression '%s' in next_state attribute of cell '%s' - skipping.\n", expr.c_str(), cell->args[0].c_str());
|
||||
if (tree.kind == LibertyExpression::Kind::EMPTY) {
|
||||
if (!warned_cells.count(cell_name)) {
|
||||
log_warning("Invalid expression '%s' in next_state attribute of cell '%s' - skipping.\n", expr.c_str(), cell_name.c_str());
|
||||
warned_cells.insert(cell_name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -370,17 +134,60 @@ static bool parse_next_state(const LibertyAst *cell, const LibertyAst *attr, std
|
|||
// This test is redundant with the one in enable_pin, but we're in a
|
||||
// position that gives better diagnostics here.
|
||||
if (!pin_names.count(ff_output)) {
|
||||
log_warning("Inference failed on expression '%s' in next_state attribute of cell '%s' because it does not contain ff output '%s' - skipping.\n", expr.c_str(), cell->args[0].c_str(), ff_output.c_str());
|
||||
if (!warned_cells.count(cell_name)) {
|
||||
log_warning("Inference failed on expression '%s' in next_state attribute of cell '%s' because it does not contain ff output '%s' - skipping.\n", expr.c_str(), cell_name.c_str(), ff_output.c_str());
|
||||
warned_cells.insert(cell_name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
enable_name = tree.enable_pin(ff_output, data_name, data_not_inverted, enable_not_inverted);
|
||||
if (enable_name.empty()) {
|
||||
log_warning("Inference failed on expression '%s' in next_state attribute of cell '%s' because it does not evaluate to an enable flop - skipping.\n", expr.c_str(), cell->args[0].c_str());
|
||||
return false;
|
||||
data_not_inverted = true;
|
||||
data_name = "";
|
||||
enable_not_inverted = true;
|
||||
enable_name = "";
|
||||
|
||||
if (pin_names.size() == 3 && pin_names.count(ff_output)) {
|
||||
pin_names.erase(ff_output);
|
||||
auto pins = std::vector<std::string>(pin_names.begin(), pin_names.end());
|
||||
int lut = 0;
|
||||
for (int n = 0; n < 8; n++) {
|
||||
auto values = dict<std::string, bool>{};
|
||||
values.insert(std::make_pair(pins[0], (n & 1) == 1));
|
||||
values.insert(std::make_pair(pins[1], (n & 2) == 2));
|
||||
values.insert(std::make_pair(ff_output, (n & 4) == 4));
|
||||
if (tree.eval(values))
|
||||
lut |= 1 << n;
|
||||
}
|
||||
// the ff output Q is in a known bit location, so we now just have to compare the LUT mask to known values to find the enable pin and polarity.
|
||||
if (lut == 0xD8) {
|
||||
data_name = pins[1];
|
||||
enable_name = pins[0];
|
||||
return true;
|
||||
}
|
||||
if (lut == 0xB8) {
|
||||
data_name = pins[0];
|
||||
enable_name = pins[1];
|
||||
return true;
|
||||
}
|
||||
enable_not_inverted = false;
|
||||
if (lut == 0xE4) {
|
||||
data_name = pins[1];
|
||||
enable_name = pins[0];
|
||||
return true;
|
||||
}
|
||||
if (lut == 0xE2) {
|
||||
data_name = pins[0];
|
||||
enable_name = pins[1];
|
||||
return true;
|
||||
}
|
||||
// this does not match an enable flop.
|
||||
}
|
||||
|
||||
return true;
|
||||
if (!warned_cells.count(cell_name)) {
|
||||
log_warning("Inference failed on expression '%s' in next_state attribute of cell '%s' because it does not evaluate to an enable flop - skipping.\n", expr.c_str(), cell_name.c_str());
|
||||
warned_cells.insert(cell_name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool parse_pin(const LibertyAst *cell, const LibertyAst *attr, std::string &pin_name, bool &pin_pol)
|
||||
|
@ -556,6 +363,8 @@ static void find_cell_sr(const LibertyAst *ast, IdString cell_type, bool clkpol,
|
|||
bool best_cell_noninv = false;
|
||||
double best_cell_area = 0;
|
||||
|
||||
log_assert(!enapol && "set/reset cell with enable is unimplemented due to lack of cells for testing");
|
||||
|
||||
if (ast->id != "library")
|
||||
log_error("Format error in liberty file.\n");
|
||||
|
||||
|
|
|
@ -80,6 +80,152 @@ void LibertyAst::dump(FILE *f, sieve &blacklist, sieve &whitelist, std::string i
|
|||
fprintf(f, " ;\n");
|
||||
}
|
||||
|
||||
#ifndef FILTERLIB
|
||||
// https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html
|
||||
LibertyExpression LibertyExpression::parse(Lexer &s, int min_prio) {
|
||||
if (s.empty())
|
||||
return LibertyExpression{};
|
||||
|
||||
char c = s.peek();
|
||||
auto lhs = LibertyExpression{};
|
||||
|
||||
while (isspace(c)) {
|
||||
if (s.empty())
|
||||
return lhs;
|
||||
s.next();
|
||||
c = s.peek();
|
||||
}
|
||||
|
||||
if (isalpha(c)) { // pin
|
||||
lhs.kind = Kind::PIN;
|
||||
lhs.name = s.pin();
|
||||
} else if (c == '(') { // parens
|
||||
s.next();
|
||||
lhs = parse(s);
|
||||
if (s.peek() != ')') {
|
||||
log_warning("expected ')' instead of '%c' while parsing Liberty expression '%s'\n", s.peek(), s.full_expr().c_str());
|
||||
return lhs;
|
||||
}
|
||||
s.next();
|
||||
} else if (c == '!') { // prefix NOT
|
||||
s.next();
|
||||
lhs.kind = Kind::NOT;
|
||||
lhs.children.push_back(parse(s, 7));
|
||||
} else {
|
||||
log_warning("unrecognised character '%c' while parsing Liberty expression '%s'\n", c, s.full_expr().c_str());
|
||||
return lhs;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (s.empty())
|
||||
break;
|
||||
|
||||
c = s.peek();
|
||||
|
||||
while (isspace(c)) {
|
||||
if (s.empty())
|
||||
return lhs;
|
||||
s.next();
|
||||
c = s.peek();
|
||||
}
|
||||
|
||||
if (c == '\'') { // postfix NOT
|
||||
if (min_prio > 7)
|
||||
break;
|
||||
s.next();
|
||||
|
||||
auto n = LibertyExpression{};
|
||||
n.kind = Kind::NOT;
|
||||
n.children.push_back(lhs);
|
||||
lhs = std::move(n);
|
||||
|
||||
continue;
|
||||
} else if (c == '^') { // infix XOR
|
||||
if (min_prio > 5)
|
||||
break;
|
||||
s.next();
|
||||
|
||||
auto rhs = parse(s, 6);
|
||||
auto n = LibertyExpression{};
|
||||
n.kind = Kind::XOR;
|
||||
n.children.push_back(lhs);
|
||||
n.children.push_back(rhs);
|
||||
lhs = std::move(n);
|
||||
|
||||
continue;
|
||||
} else if (c == '&' || c == '*') { // infix AND
|
||||
// technically space should be considered infix AND. it seems rare in practice.
|
||||
if (min_prio > 3)
|
||||
break;
|
||||
s.next();
|
||||
|
||||
auto rhs = parse(s, 4);
|
||||
auto n = LibertyExpression{};
|
||||
n.kind = Kind::AND;
|
||||
n.children.push_back(lhs);
|
||||
n.children.push_back(rhs);
|
||||
lhs = std::move(n);
|
||||
|
||||
continue;
|
||||
} else if (c == '+' || c == '|') { // infix OR
|
||||
if (min_prio > 1)
|
||||
break;
|
||||
s.next();
|
||||
|
||||
auto rhs = parse(s, 2);
|
||||
auto n = LibertyExpression{};
|
||||
n.kind = Kind::OR;
|
||||
n.children.push_back(lhs);
|
||||
n.children.push_back(rhs);
|
||||
lhs = std::move(n);
|
||||
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return lhs;
|
||||
}
|
||||
|
||||
void LibertyExpression::get_pin_names(pool<std::string>& names) {
|
||||
if (kind == Kind::PIN) {
|
||||
names.insert(name);
|
||||
} else {
|
||||
for (auto& child : children)
|
||||
child.get_pin_names(names);
|
||||
}
|
||||
}
|
||||
|
||||
bool LibertyExpression::eval(dict<std::string, bool>& values) {
|
||||
bool result = false;
|
||||
switch (kind) {
|
||||
case Kind::AND:
|
||||
result = true;
|
||||
for (auto& child : children)
|
||||
result &= child.eval(values);
|
||||
return result;
|
||||
case Kind::OR:
|
||||
result = false;
|
||||
for (auto& child : children)
|
||||
result |= child.eval(values);
|
||||
return result;
|
||||
case Kind::NOT:
|
||||
log_assert(children.size() == 1);
|
||||
return !children[0].eval(values);
|
||||
case Kind::XOR:
|
||||
result = false;
|
||||
for (auto& child : children)
|
||||
result ^= child.eval(values);
|
||||
return result;
|
||||
case Kind::PIN:
|
||||
return values.at(name);
|
||||
case Kind::EMPTY:
|
||||
log_assert(false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
int LibertyParser::lexer(std::string &str)
|
||||
{
|
||||
int c;
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#ifndef LIBPARSE_H
|
||||
#define LIBPARSE_H
|
||||
|
||||
#include "kernel/yosys.h"
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
@ -39,6 +40,52 @@ namespace Yosys
|
|||
void dump(FILE *f, sieve &blacklist, sieve &whitelist, std::string indent = "", std::string path = "", bool path_ok = false) const;
|
||||
};
|
||||
|
||||
struct LibertyExpression
|
||||
{
|
||||
struct Lexer {
|
||||
std::string s, expr;
|
||||
|
||||
Lexer(std::string s) : s{s}, expr{s} {}
|
||||
|
||||
bool empty() { return s.empty();}
|
||||
char peek() { return s[0]; }
|
||||
std::string full_expr() { return expr; }
|
||||
|
||||
char next() {
|
||||
char c = s[0];
|
||||
s = s.substr(1, s.size());
|
||||
return c;
|
||||
}
|
||||
|
||||
std::string pin() {
|
||||
auto length = s.find_first_of("\t()'!^*& +|");
|
||||
auto pin = s.substr(0, length);
|
||||
s = s.substr(length, s.size());
|
||||
return pin;
|
||||
}
|
||||
};
|
||||
|
||||
enum Kind {
|
||||
AND,
|
||||
OR,
|
||||
NOT,
|
||||
XOR,
|
||||
// the standard specifies constants, but they're probably rare in practice.
|
||||
PIN,
|
||||
EMPTY
|
||||
};
|
||||
|
||||
Kind kind;
|
||||
std::string name;
|
||||
std::vector<LibertyExpression> children;
|
||||
|
||||
LibertyExpression() : kind(Kind::EMPTY) {}
|
||||
|
||||
static LibertyExpression parse(Lexer &s, int min_prio = 0);
|
||||
void get_pin_names(pool<std::string>& names);
|
||||
bool eval(dict<std::string, bool>& values);
|
||||
};
|
||||
|
||||
class LibertyParser
|
||||
{
|
||||
private:
|
||||
|
|
Loading…
Reference in New Issue