mirror of
https://github.com/YosysHQ/yosys
synced 2025-11-07 14:55:07 +00:00
Merge pull request #5467 from YosysHQ/emil/liberty-unquoted-expressions
libparse: support unquoted expressions
This commit is contained in:
commit
a16fc9b4f3
12 changed files with 352 additions and 120 deletions
|
|
@ -127,7 +127,7 @@ static bool parse_next_state(const LibertyAst *cell, const LibertyAst *attr, std
|
|||
return false;
|
||||
}
|
||||
|
||||
auto pin_names = pool<std::string>{};
|
||||
auto pin_names = std::unordered_set<std::string>{};
|
||||
tree.get_pin_names(pin_names);
|
||||
|
||||
// from the `ff` block, we know the flop output signal name for loopback.
|
||||
|
|
@ -156,7 +156,7 @@ static bool parse_next_state(const LibertyAst *cell, const LibertyAst *attr, std
|
|||
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>{};
|
||||
auto values = std::unordered_map<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));
|
||||
|
|
|
|||
|
|
@ -25,9 +25,22 @@
|
|||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
#ifndef FILTERLIB
|
||||
#ifdef FILTERLIB
|
||||
#undef log_assert
|
||||
void log_assert(bool cond) {
|
||||
if (!cond)
|
||||
fprintf(stderr, "Unspecified assertion failed\n");
|
||||
}
|
||||
void warn(std::string str) {
|
||||
std::cerr << str;
|
||||
}
|
||||
#else
|
||||
#include "kernel/log.h"
|
||||
void warn(std::string str) {
|
||||
Yosys::log_formatted_warning("", str);
|
||||
}
|
||||
#endif
|
||||
|
||||
using namespace Yosys;
|
||||
|
|
@ -162,13 +175,15 @@ void LibertyAst::dump(FILE *f, sieve &blacklist, sieve &whitelist, std::string i
|
|||
fprintf(f, " ;\n");
|
||||
}
|
||||
|
||||
#ifndef FILTERLIB
|
||||
|
||||
// binary operators excluding ' '
|
||||
bool LibertyExpression::is_nice_binop(char c) {
|
||||
bool LibertyExpression::char_is_nice_binop(char c) {
|
||||
return c == '*' || c == '&' || c == '^' || c == '+' || c == '|';
|
||||
}
|
||||
|
||||
bool LibertyExpression::is_binop() {
|
||||
return kind == AND || kind == OR || kind == XOR;
|
||||
}
|
||||
|
||||
// https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html
|
||||
LibertyExpression LibertyExpression::parse(Lexer &s, int min_prio) {
|
||||
if (s.empty())
|
||||
|
|
@ -177,7 +192,7 @@ LibertyExpression LibertyExpression::parse(Lexer &s, int min_prio) {
|
|||
char c = s.peek();
|
||||
auto lhs = LibertyExpression{};
|
||||
|
||||
while (isspace(c)) {
|
||||
while (isspace(c) || c == '"') {
|
||||
if (s.empty())
|
||||
return lhs;
|
||||
s.next();
|
||||
|
|
@ -191,7 +206,9 @@ LibertyExpression LibertyExpression::parse(Lexer &s, int min_prio) {
|
|||
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());
|
||||
std::stringstream ss;
|
||||
ss << "expected ')' instead of " << s.peek() << " while parsing Liberty expression '" << s.full_expr() << "'\n";
|
||||
warn(ss.str());
|
||||
return lhs;
|
||||
}
|
||||
s.next();
|
||||
|
|
@ -200,10 +217,11 @@ LibertyExpression LibertyExpression::parse(Lexer &s, int min_prio) {
|
|||
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());
|
||||
std::stringstream ss;
|
||||
ss << "unrecognised character " << c << " while parsing Liberty expression " << s.full_expr() << "\n";
|
||||
warn(ss.str());
|
||||
return lhs;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (s.empty())
|
||||
break;
|
||||
|
|
@ -246,9 +264,10 @@ LibertyExpression LibertyExpression::parse(Lexer &s, int min_prio) {
|
|||
s.next();
|
||||
c = s.peek();
|
||||
}
|
||||
if (is_nice_binop(c)) {
|
||||
if (char_is_nice_binop(c) || c == ')' || c == '\'' || c == '\"') {
|
||||
// We found a real binop, so this space wasn't an AND
|
||||
// and we just discard it as meaningless whitespace
|
||||
// Tail operators also imply this isn't an AND
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -286,7 +305,7 @@ LibertyExpression LibertyExpression::parse(Lexer &s, int min_prio) {
|
|||
return lhs;
|
||||
}
|
||||
|
||||
void LibertyExpression::get_pin_names(pool<std::string>& names) {
|
||||
void LibertyExpression::get_pin_names(std::unordered_set<std::string>& names) {
|
||||
if (kind == Kind::PIN) {
|
||||
names.insert(name);
|
||||
} else {
|
||||
|
|
@ -295,7 +314,7 @@ void LibertyExpression::get_pin_names(pool<std::string>& names) {
|
|||
}
|
||||
}
|
||||
|
||||
bool LibertyExpression::eval(dict<std::string, bool>& values) {
|
||||
bool LibertyExpression::eval(std::unordered_map<std::string, bool>& values) {
|
||||
bool result = false;
|
||||
switch (kind) {
|
||||
case Kind::AND:
|
||||
|
|
@ -324,7 +343,7 @@ bool LibertyExpression::eval(dict<std::string, bool>& values) {
|
|||
return false;
|
||||
}
|
||||
|
||||
std::string LibertyExpression::str(int indent)
|
||||
std::string LibertyExpression::sexpr_str(int indent)
|
||||
{
|
||||
std::string prefix;
|
||||
switch (kind) {
|
||||
|
|
@ -355,16 +374,55 @@ std::string LibertyExpression::str(int indent)
|
|||
if (!first) {
|
||||
prefix += "\n" + std::string(indent + add_indent, ' ');
|
||||
}
|
||||
prefix += child.str(indent + add_indent);
|
||||
prefix += child.sexpr_str(indent + add_indent);
|
||||
first = false;
|
||||
}
|
||||
prefix += ")";
|
||||
return prefix;
|
||||
}
|
||||
|
||||
#endif
|
||||
std::string LibertyExpression::vlog_str()
|
||||
{
|
||||
std::string prefix;
|
||||
if (kind != PIN)
|
||||
prefix += "(";
|
||||
if (is_binop()) {
|
||||
log_assert(children.size() == 2);
|
||||
prefix += children[0].vlog_str();
|
||||
switch (kind) {
|
||||
case AND:
|
||||
prefix += "&";
|
||||
break;
|
||||
case OR:
|
||||
prefix += "|";
|
||||
break;
|
||||
case XOR:
|
||||
prefix += "^";
|
||||
break;
|
||||
default:
|
||||
log_assert(false);
|
||||
}
|
||||
prefix += children[1].vlog_str();
|
||||
} else {
|
||||
switch (kind) {
|
||||
case NOT:
|
||||
log_assert(children.size() == 1);
|
||||
prefix += "~";
|
||||
prefix += children[0].vlog_str();
|
||||
break;
|
||||
case PIN:
|
||||
prefix += name;
|
||||
break;
|
||||
default:
|
||||
log_assert(false);
|
||||
}
|
||||
}
|
||||
if (kind != PIN)
|
||||
prefix += ")";
|
||||
return prefix;
|
||||
}
|
||||
|
||||
int LibertyParser::lexer(std::string &str)
|
||||
int LibertyParser::lexer_inner(std::string &str)
|
||||
{
|
||||
int c;
|
||||
|
||||
|
|
@ -390,11 +448,9 @@ int LibertyParser::lexer(std::string &str)
|
|||
|
||||
if (str == "+" || str == "-") {
|
||||
/* Single operator is not an identifier */
|
||||
// fprintf(stderr, "LEX: char >>%s<<\n", str.c_str());
|
||||
return str[0];
|
||||
}
|
||||
else {
|
||||
// fprintf(stderr, "LEX: identifier >>%s<<\n", str.c_str());
|
||||
return 'v';
|
||||
}
|
||||
}
|
||||
|
|
@ -402,24 +458,25 @@ int LibertyParser::lexer(std::string &str)
|
|||
// if it wasn't an identifer, number of array range,
|
||||
// maybe it's a string?
|
||||
if (c == '"') {
|
||||
f.consume(1);
|
||||
size_t i = 0;
|
||||
while (true) {
|
||||
c = f.peek(i);
|
||||
line += (c == '\n');
|
||||
if (c != '"')
|
||||
if (c != '"' && c != EOF)
|
||||
i += 1;
|
||||
else
|
||||
break;
|
||||
}
|
||||
str.clear();
|
||||
#ifdef FILTERLIB
|
||||
f.unget();
|
||||
str.append(f.buffered_data(), f.buffered_data() + i + 2);
|
||||
f.consume(i + 2);
|
||||
#else
|
||||
str.append(f.buffered_data(), f.buffered_data() + i);
|
||||
f.consume(i + 1);
|
||||
str.append(f.buffered_data(), f.buffered_data() + i + 1);
|
||||
// Usage in filterlib is expected to retain quotes
|
||||
// but yosys expects to get unquoted
|
||||
#ifdef FILTERLIB
|
||||
str = "\"" + str + "\"";
|
||||
#endif
|
||||
f.consume(i + 2);
|
||||
return 'v';
|
||||
}
|
||||
|
||||
|
|
@ -442,13 +499,12 @@ int LibertyParser::lexer(std::string &str)
|
|||
return lexer(str);
|
||||
}
|
||||
f.unget();
|
||||
// fprintf(stderr, "LEX: char >>/<<\n");
|
||||
return '/'; // a single '/' charater.
|
||||
}
|
||||
|
||||
// check for a backslash
|
||||
if (c == '\\') {
|
||||
c = f.get();
|
||||
c = f.get();
|
||||
if (c == '\r')
|
||||
c = f.get();
|
||||
if (c == '\n') {
|
||||
|
|
@ -467,14 +523,22 @@ int LibertyParser::lexer(std::string &str)
|
|||
|
||||
// anything else, such as ';' will get passed
|
||||
// through as literal items.
|
||||
|
||||
// if (c >= 32 && c < 255)
|
||||
// fprintf(stderr, "LEX: char >>%c<<\n", c);
|
||||
// else
|
||||
// fprintf(stderr, "LEX: char %d\n", c);
|
||||
return c;
|
||||
}
|
||||
|
||||
int LibertyParser::lexer(std::string &str)
|
||||
{
|
||||
int ret = lexer_inner(str);
|
||||
// if (ret >= 32 && ret < 255) {
|
||||
// fprintf(stdout, "LEX: ret >>%c<<\n", ret);
|
||||
// } else if (ret == 'v') {
|
||||
// fprintf(stdout, "LEX: ret v str %s\n", str.c_str());
|
||||
// } else {
|
||||
// fprintf(stdout, "LEX: ret %d\n", ret);
|
||||
// }
|
||||
return ret;
|
||||
}
|
||||
|
||||
void LibertyParser::report_unexpected_token(int tok)
|
||||
{
|
||||
std::string eReport;
|
||||
|
|
@ -545,6 +609,25 @@ void LibertyParser::parse_vector_range(int tok)
|
|||
}
|
||||
}
|
||||
|
||||
// Consume into out_str any string-ish tokens, seperated with spaces
|
||||
// to cope with abuse of the underdefined spec by real world PDKs
|
||||
// enabled by proprietary implementations.
|
||||
// Sorry.
|
||||
int LibertyParser::consume_wrecked_str(int tok, std::string& out_str) {
|
||||
std::string str = "";
|
||||
while (tok != ';' && tok != EOF && tok != 'n') {
|
||||
out_str += " ";
|
||||
if (tok == 'v')
|
||||
out_str += str;
|
||||
else
|
||||
out_str += tok;
|
||||
tok = lexer(str);
|
||||
}
|
||||
if (tok == EOF)
|
||||
error("wrecked string EOF");
|
||||
return tok;
|
||||
}
|
||||
|
||||
LibertyAst *LibertyParser::parse(bool top_level)
|
||||
{
|
||||
std::string str;
|
||||
|
|
@ -591,7 +674,14 @@ LibertyAst *LibertyParser::parse(bool top_level)
|
|||
if (tok == '[') {
|
||||
parse_vector_range(tok);
|
||||
tok = lexer(str);
|
||||
} else {
|
||||
// Hack for when an expression string is unquoted
|
||||
tok = consume_wrecked_str(tok, ast->value);
|
||||
}
|
||||
} else if (tok == '(') {
|
||||
// Hack for when an expression string is unquoted and starts with
|
||||
// parentheses
|
||||
tok = consume_wrecked_str(tok, ast->value);
|
||||
}
|
||||
while (tok == '+' || tok == '-' || tok == '*' || tok == '/' || tok == '!') {
|
||||
ast->value += tok;
|
||||
|
|
@ -601,7 +691,7 @@ LibertyAst *LibertyParser::parse(bool top_level)
|
|||
ast->value += str;
|
||||
tok = lexer(str);
|
||||
}
|
||||
|
||||
|
||||
// In a liberty file, all key : value pairs should end in ';'
|
||||
// However, there are some liberty files in the wild that
|
||||
// just have a newline. We'll be kind and accept a newline
|
||||
|
|
@ -621,11 +711,11 @@ LibertyAst *LibertyParser::parse(bool top_level)
|
|||
continue;
|
||||
if (tok == ')')
|
||||
break;
|
||||
|
||||
|
||||
if (tok == '[')
|
||||
{
|
||||
parse_vector_range(tok);
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
if (tok == 'n')
|
||||
continue;
|
||||
|
|
@ -727,42 +817,13 @@ const LibertyAst *find_non_null(const LibertyAst *node, const char *name)
|
|||
|
||||
std::string func2vl(std::string str)
|
||||
{
|
||||
for (size_t pos = str.find_first_of("\" \t"); pos != std::string::npos; pos = str.find_first_of("\" \t")) {
|
||||
char c_left = pos > 0 ? str[pos-1] : ' ';
|
||||
char c_right = pos+1 < str.size() ? str[pos+1] : ' ';
|
||||
if (std::string("\" \t*+").find(c_left) != std::string::npos)
|
||||
str.erase(pos, 1);
|
||||
else if (std::string("\" \t*+").find(c_right) != std::string::npos)
|
||||
str.erase(pos, 1);
|
||||
else
|
||||
str[pos] = '*';
|
||||
}
|
||||
|
||||
std::vector<size_t> group_start;
|
||||
for (size_t pos = 0; pos < str.size(); pos++) {
|
||||
if (str[pos] == '(')
|
||||
group_start.push_back(pos);
|
||||
if (str[pos] == ')' && group_start.size() > 0) {
|
||||
if (pos+1 < str.size() && str[pos+1] == '\'') {
|
||||
std::string group = str.substr(group_start.back(), pos-group_start.back()+1);
|
||||
str[group_start.back()] = '~';
|
||||
str.replace(group_start.back()+1, group.size(), group);
|
||||
pos++;
|
||||
}
|
||||
group_start.pop_back();
|
||||
}
|
||||
if (str[pos] == '\'' && pos > 0) {
|
||||
size_t start = str.find_last_of("()'*+^&| ", pos-1)+1;
|
||||
std::string group = str.substr(start, pos-start);
|
||||
str[start] = '~';
|
||||
str.replace(start+1, group.size(), group);
|
||||
}
|
||||
if (str[pos] == '*')
|
||||
str[pos] = '&';
|
||||
if (str[pos] == '+')
|
||||
str[pos] = '|';
|
||||
}
|
||||
auto helper = LibertyExpression::Lexer(str);
|
||||
return LibertyExpression::parse(helper).vlog_str();
|
||||
}
|
||||
|
||||
std::string vlog_identifier(std::string str)
|
||||
{
|
||||
str.erase(std::remove(str.begin(), str.end(), '\"'), str.end());
|
||||
return str;
|
||||
}
|
||||
|
||||
|
|
@ -772,11 +833,13 @@ void event2vl(const LibertyAst *ast, std::string &edge, std::string &expr)
|
|||
expr.clear();
|
||||
|
||||
if (ast != NULL) {
|
||||
expr = func2vl(ast->value);
|
||||
if (expr.size() > 0 && expr[0] == '~')
|
||||
edge = "negedge " + expr.substr(1);
|
||||
auto helper = LibertyExpression::Lexer(ast->value);
|
||||
auto parsed = LibertyExpression::parse(helper);
|
||||
expr = parsed.vlog_str();
|
||||
if (parsed.kind == LibertyExpression::Kind::NOT)
|
||||
edge = "negedge " + parsed.children[0].vlog_str();
|
||||
else
|
||||
edge = "posedge " + expr;
|
||||
edge = "posedge " + parsed.vlog_str();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -806,13 +869,13 @@ void gen_verilogsim_cell(const LibertyAst *ast)
|
|||
return;
|
||||
|
||||
CHECK_NV(ast->args.size(), == 1);
|
||||
printf("module %s (", ast->args[0].c_str());
|
||||
printf("module %s (", vlog_identifier(ast->args[0]).c_str());
|
||||
bool first = true;
|
||||
for (auto child : ast->children) {
|
||||
if (child->id != "pin")
|
||||
continue;
|
||||
CHECK_NV(child->args.size(), == 1);
|
||||
printf("%s%s", first ? "" : ", ", child->args[0].c_str());
|
||||
printf("%s%s", first ? "" : ", ", vlog_identifier(child->args[0]).c_str());
|
||||
first = false;
|
||||
}
|
||||
printf(");\n");
|
||||
|
|
@ -823,7 +886,7 @@ void gen_verilogsim_cell(const LibertyAst *ast)
|
|||
printf(" reg ");
|
||||
first = true;
|
||||
for (auto arg : child->args) {
|
||||
printf("%s%s", first ? "" : ", ", arg.c_str());
|
||||
printf("%s%s", first ? "" : ", ", vlog_identifier(arg).c_str());
|
||||
first = false;
|
||||
}
|
||||
printf(";\n");
|
||||
|
|
@ -835,9 +898,10 @@ void gen_verilogsim_cell(const LibertyAst *ast)
|
|||
CHECK_NV(child->args.size(), == 1);
|
||||
const LibertyAst *dir = find_non_null(child, "direction");
|
||||
const LibertyAst *func = child->find("function");
|
||||
printf(" %s %s;\n", dir->value.c_str(), child->args[0].c_str());
|
||||
std::string var = vlog_identifier(child->args[0]);
|
||||
printf(" %s %s;\n", dir->value.c_str(), var.c_str());
|
||||
if (func != NULL)
|
||||
printf(" assign %s = %s; // %s\n", child->args[0].c_str(), func2vl(func->value).c_str(), func->value.c_str());
|
||||
printf(" assign %s = %s; // %s\n", var.c_str(), func2vl(func->value).c_str(), func->value.c_str());
|
||||
}
|
||||
|
||||
for (auto child : ast->children)
|
||||
|
|
@ -845,8 +909,8 @@ void gen_verilogsim_cell(const LibertyAst *ast)
|
|||
if (child->id != "ff" || child->args.size() != 2)
|
||||
continue;
|
||||
|
||||
std::string iq_var = child->args[0];
|
||||
std::string iqn_var = child->args[1];
|
||||
std::string iq_var = vlog_identifier(child->args[0]);
|
||||
std::string iqn_var = vlog_identifier(child->args[1]);
|
||||
|
||||
std::string clock_edge, clock_expr;
|
||||
event2vl(child->find("clocked_on"), clock_edge, clock_expr);
|
||||
|
|
@ -909,8 +973,8 @@ void gen_verilogsim_cell(const LibertyAst *ast)
|
|||
if (child->id != "latch" || child->args.size() != 2)
|
||||
continue;
|
||||
|
||||
std::string iq_var = child->args[0];
|
||||
std::string iqn_var = child->args[1];
|
||||
std::string iq_var = vlog_identifier(child->args[0]);
|
||||
std::string iqn_var = vlog_identifier(child->args[1]);
|
||||
|
||||
std::string enable_edge, enable_expr;
|
||||
event2vl(child->find("enable"), enable_edge, enable_expr);
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ namespace Yosys
|
|||
}
|
||||
|
||||
std::string pin() {
|
||||
auto length = s.find_first_of("\t()'!^*& +|");
|
||||
auto length = s.find_first_of("\t()'!^*& +|\"");
|
||||
if (length == std::string::npos) {
|
||||
// nothing found so use size of s
|
||||
length = s.size();
|
||||
|
|
@ -91,11 +91,13 @@ namespace Yosys
|
|||
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);
|
||||
std::string str(int indent = 0);
|
||||
void get_pin_names(std::unordered_set<std::string>& names);
|
||||
bool eval(std::unordered_map<std::string, bool>& values);
|
||||
std::string sexpr_str(int indent = 0);
|
||||
std::string vlog_str();
|
||||
private:
|
||||
static bool is_nice_binop(char c);
|
||||
static bool char_is_nice_binop(char c);
|
||||
bool is_binop();
|
||||
};
|
||||
|
||||
class LibertyInputStream {
|
||||
|
|
@ -170,10 +172,12 @@ namespace Yosys
|
|||
'n': newline
|
||||
anything else is a single character.
|
||||
*/
|
||||
int lexer_inner(std::string &str);
|
||||
int lexer(std::string &str);
|
||||
|
||||
void report_unexpected_token(int tok);
|
||||
void parse_vector_range(int tok);
|
||||
int consume_wrecked_str(int tok, std::string& out_str);
|
||||
LibertyAst *parse(bool top_level);
|
||||
void error() const;
|
||||
void error(const std::string &str) const;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue