2014-02-15 05:57:28 -06:00
|
|
|
/*
|
|
|
|
* yosys -- Yosys Open SYnthesis Suite
|
|
|
|
*
|
2021-06-07 17:39:36 -05:00
|
|
|
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
|
2015-07-02 04:14:30 -05:00
|
|
|
*
|
2014-02-15 05:57:28 -06:00
|
|
|
* 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.
|
2015-07-02 04:14:30 -05:00
|
|
|
*
|
2014-02-15 05:57:28 -06:00
|
|
|
* 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 "passes/techmap/libparse.h"
|
|
|
|
#include "kernel/register.h"
|
|
|
|
#include "kernel/log.h"
|
|
|
|
|
2014-07-31 06:19:47 -05:00
|
|
|
YOSYS_NAMESPACE_BEGIN
|
2014-02-15 05:57:28 -06:00
|
|
|
|
|
|
|
struct token_t {
|
|
|
|
char type;
|
|
|
|
RTLIL::SigSpec sig;
|
|
|
|
token_t (char t) : type(t) { }
|
|
|
|
token_t (char t, RTLIL::SigSpec s) : type(t), sig(s) { }
|
|
|
|
};
|
|
|
|
|
|
|
|
static RTLIL::SigSpec parse_func_identifier(RTLIL::Module *module, const char *&expr)
|
|
|
|
{
|
|
|
|
log_assert(*expr != 0);
|
|
|
|
|
|
|
|
int id_len = 0;
|
|
|
|
while (('a' <= expr[id_len] && expr[id_len] <= 'z') || ('A' <= expr[id_len] && expr[id_len] <= 'Z') ||
|
2018-11-05 05:33:21 -06:00
|
|
|
('0' <= expr[id_len] && expr[id_len] <= '9') || expr[id_len] == '.' ||
|
|
|
|
expr[id_len] == '_' || expr[id_len] == '[' || expr[id_len] == ']') id_len++;
|
2014-02-15 05:57:28 -06:00
|
|
|
|
|
|
|
if (id_len == 0)
|
|
|
|
log_error("Expected identifier at `%s'.\n", expr);
|
2015-07-02 04:14:30 -05:00
|
|
|
|
2014-02-15 05:57:28 -06:00
|
|
|
if (id_len == 1 && (*expr == '0' || *expr == '1'))
|
|
|
|
return *(expr++) == '0' ? RTLIL::State::S0 : RTLIL::State::S1;
|
|
|
|
|
|
|
|
std::string id = RTLIL::escape_id(std::string(expr, id_len));
|
2014-07-26 18:49:51 -05:00
|
|
|
if (!module->wires_.count(id))
|
2014-08-02 09:03:18 -05:00
|
|
|
log_error("Can't resolve wire name %s.\n", RTLIL::unescape_id(id).c_str());
|
2014-02-15 05:57:28 -06:00
|
|
|
|
|
|
|
expr += id_len;
|
2014-07-26 18:49:51 -05:00
|
|
|
return module->wires_.at(id);
|
2014-02-15 05:57:28 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static RTLIL::SigSpec create_inv_cell(RTLIL::Module *module, RTLIL::SigSpec A)
|
|
|
|
{
|
2020-04-02 11:51:32 -05:00
|
|
|
RTLIL::Cell *cell = module->addCell(NEW_ID, ID($_NOT_));
|
2020-03-12 14:57:01 -05:00
|
|
|
cell->setPort(ID::A, A);
|
|
|
|
cell->setPort(ID::Y, module->addWire(NEW_ID));
|
|
|
|
return cell->getPort(ID::Y);
|
2014-02-15 05:57:28 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static RTLIL::SigSpec create_xor_cell(RTLIL::Module *module, RTLIL::SigSpec A, RTLIL::SigSpec B)
|
|
|
|
{
|
2020-04-02 11:51:32 -05:00
|
|
|
RTLIL::Cell *cell = module->addCell(NEW_ID, ID($_XOR_));
|
2020-03-12 14:57:01 -05:00
|
|
|
cell->setPort(ID::A, A);
|
|
|
|
cell->setPort(ID::B, B);
|
|
|
|
cell->setPort(ID::Y, module->addWire(NEW_ID));
|
|
|
|
return cell->getPort(ID::Y);
|
2014-02-15 05:57:28 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static RTLIL::SigSpec create_and_cell(RTLIL::Module *module, RTLIL::SigSpec A, RTLIL::SigSpec B)
|
|
|
|
{
|
2020-04-02 11:51:32 -05:00
|
|
|
RTLIL::Cell *cell = module->addCell(NEW_ID, ID($_AND_));
|
2020-03-12 14:57:01 -05:00
|
|
|
cell->setPort(ID::A, A);
|
|
|
|
cell->setPort(ID::B, B);
|
|
|
|
cell->setPort(ID::Y, module->addWire(NEW_ID));
|
|
|
|
return cell->getPort(ID::Y);
|
2014-02-15 05:57:28 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static RTLIL::SigSpec create_or_cell(RTLIL::Module *module, RTLIL::SigSpec A, RTLIL::SigSpec B)
|
|
|
|
{
|
2020-04-02 11:51:32 -05:00
|
|
|
RTLIL::Cell *cell = module->addCell(NEW_ID, ID($_OR_));
|
2020-03-12 14:57:01 -05:00
|
|
|
cell->setPort(ID::A, A);
|
|
|
|
cell->setPort(ID::B, B);
|
|
|
|
cell->setPort(ID::Y, module->addWire(NEW_ID));
|
|
|
|
return cell->getPort(ID::Y);
|
2014-02-15 05:57:28 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool parse_func_reduce(RTLIL::Module *module, std::vector<token_t> &stack, token_t next_token)
|
|
|
|
{
|
|
|
|
int top = int(stack.size())-1;
|
|
|
|
|
|
|
|
if (0 <= top-1 && stack[top].type == 0 && stack[top-1].type == '!') {
|
|
|
|
token_t t = token_t(0, create_inv_cell(module, stack[top].sig));
|
|
|
|
stack.pop_back();
|
|
|
|
stack.pop_back();
|
|
|
|
stack.push_back(t);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (0 <= top-1 && stack[top].type == '\'' && stack[top-1].type == 0) {
|
|
|
|
token_t t = token_t(0, create_inv_cell(module, stack[top-1].sig));
|
|
|
|
stack.pop_back();
|
|
|
|
stack.pop_back();
|
|
|
|
stack.push_back(t);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (0 <= top && stack[top].type == 0) {
|
|
|
|
if (next_token.type == '\'')
|
|
|
|
return false;
|
|
|
|
stack[top].type = 1;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (0 <= top-2 && stack[top-2].type == 1 && stack[top-1].type == '^' && stack[top].type == 1) {
|
|
|
|
token_t t = token_t(1, create_xor_cell(module, stack[top-2].sig, stack[top].sig));
|
|
|
|
stack.pop_back();
|
|
|
|
stack.pop_back();
|
|
|
|
stack.pop_back();
|
|
|
|
stack.push_back(t);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (0 <= top && stack[top].type == 1) {
|
|
|
|
if (next_token.type == '^')
|
|
|
|
return false;
|
|
|
|
stack[top].type = 2;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (0 <= top-1 && stack[top-1].type == 2 && stack[top].type == 2) {
|
2014-02-15 12:36:09 -06:00
|
|
|
token_t t = token_t(2, create_and_cell(module, stack[top-1].sig, stack[top].sig));
|
2014-02-15 05:57:28 -06:00
|
|
|
stack.pop_back();
|
|
|
|
stack.pop_back();
|
|
|
|
stack.push_back(t);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (0 <= top-2 && stack[top-2].type == 2 && (stack[top-1].type == '*' || stack[top-1].type == '&') && stack[top].type == 2) {
|
|
|
|
token_t t = token_t(2, create_and_cell(module, stack[top-2].sig, stack[top].sig));
|
|
|
|
stack.pop_back();
|
|
|
|
stack.pop_back();
|
|
|
|
stack.pop_back();
|
|
|
|
stack.push_back(t);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (0 <= top && stack[top].type == 2) {
|
2018-06-15 11:56:44 -05:00
|
|
|
if (next_token.type == '*' || next_token.type == '&' || next_token.type == 0 || next_token.type == '(' || next_token.type == '!')
|
2014-02-15 05:57:28 -06:00
|
|
|
return false;
|
|
|
|
stack[top].type = 3;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (0 <= top-2 && stack[top-2].type == 3 && (stack[top-1].type == '+' || stack[top-1].type == '|') && stack[top].type == 3) {
|
|
|
|
token_t t = token_t(3, create_or_cell(module, stack[top-2].sig, stack[top].sig));
|
|
|
|
stack.pop_back();
|
|
|
|
stack.pop_back();
|
|
|
|
stack.pop_back();
|
|
|
|
stack.push_back(t);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (0 <= top-2 && stack[top-2].type == '(' && stack[top-1].type == 3 && stack[top].type == ')') {
|
|
|
|
token_t t = token_t(0, stack[top-1].sig);
|
|
|
|
stack.pop_back();
|
|
|
|
stack.pop_back();
|
|
|
|
stack.pop_back();
|
|
|
|
stack.push_back(t);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static RTLIL::SigSpec parse_func_expr(RTLIL::Module *module, const char *expr)
|
|
|
|
{
|
|
|
|
const char *orig_expr = expr;
|
|
|
|
std::vector<token_t> stack;
|
|
|
|
|
|
|
|
while (*expr)
|
|
|
|
{
|
|
|
|
if (*expr == ' ' || *expr == '\t' || *expr == '\r' || *expr == '\n' || *expr == '"') {
|
|
|
|
expr++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
token_t next_token(0);
|
2018-05-12 13:53:24 -05:00
|
|
|
if (*expr == '(' || *expr == ')' || *expr == '\'' || *expr == '!' || *expr == '^' || *expr == '*' || *expr == '+' || *expr == '|' || *expr == '&')
|
2014-02-15 05:57:28 -06:00
|
|
|
next_token = token_t(*(expr++));
|
|
|
|
else
|
|
|
|
next_token = token_t(0, parse_func_identifier(module, expr));
|
|
|
|
|
|
|
|
while (parse_func_reduce(module, stack, next_token)) {}
|
|
|
|
stack.push_back(next_token);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (parse_func_reduce(module, stack, token_t('.'))) {}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
for (size_t i = 0; i < stack.size(); i++)
|
|
|
|
if (stack[i].type < 16)
|
|
|
|
log("%3d: %d %s\n", int(i), stack[i].type, log_signal(stack[i].sig));
|
|
|
|
else
|
|
|
|
log("%3d: %c\n", int(i), stack[i].type);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (stack.size() != 1 || stack.back().type != 3)
|
|
|
|
log_error("Parser error in function expr `%s'.\n", orig_expr);
|
|
|
|
|
|
|
|
return stack.back().sig;
|
|
|
|
}
|
|
|
|
|
2014-02-15 12:36:33 -06:00
|
|
|
static void create_ff(RTLIL::Module *module, LibertyAst *node)
|
|
|
|
{
|
2014-07-21 05:35:06 -05:00
|
|
|
RTLIL::SigSpec iq_sig(module->addWire(RTLIL::escape_id(node->args.at(0))));
|
|
|
|
RTLIL::SigSpec iqn_sig(module->addWire(RTLIL::escape_id(node->args.at(1))));
|
2014-02-15 12:36:33 -06:00
|
|
|
|
|
|
|
RTLIL::SigSpec clk_sig, data_sig, clear_sig, preset_sig;
|
|
|
|
bool clk_polarity = true, clear_polarity = true, preset_polarity = true;
|
|
|
|
|
|
|
|
for (auto child : node->children) {
|
|
|
|
if (child->id == "clocked_on")
|
|
|
|
clk_sig = parse_func_expr(module, child->value.c_str());
|
|
|
|
if (child->id == "next_state")
|
|
|
|
data_sig = parse_func_expr(module, child->value.c_str());
|
|
|
|
if (child->id == "clear")
|
|
|
|
clear_sig = parse_func_expr(module, child->value.c_str());
|
|
|
|
if (child->id == "preset")
|
|
|
|
preset_sig = parse_func_expr(module, child->value.c_str());
|
|
|
|
}
|
|
|
|
|
2014-07-22 13:15:14 -05:00
|
|
|
if (clk_sig.size() == 0 || data_sig.size() == 0)
|
2014-08-02 09:03:18 -05:00
|
|
|
log_error("FF cell %s has no next_state and/or clocked_on attribute.\n", log_id(module->name));
|
2014-02-15 12:36:33 -06:00
|
|
|
|
|
|
|
for (bool rerun_invert_rollback = true; rerun_invert_rollback;)
|
|
|
|
{
|
|
|
|
rerun_invert_rollback = false;
|
|
|
|
|
2014-07-26 18:51:45 -05:00
|
|
|
for (auto &it : module->cells_) {
|
2020-04-02 11:51:32 -05:00
|
|
|
if (it.second->type == ID($_NOT_) && it.second->getPort(ID::Y) == clk_sig) {
|
2020-03-12 14:57:01 -05:00
|
|
|
clk_sig = it.second->getPort(ID::A);
|
2014-02-15 12:36:33 -06:00
|
|
|
clk_polarity = !clk_polarity;
|
|
|
|
rerun_invert_rollback = true;
|
|
|
|
}
|
2020-04-02 11:51:32 -05:00
|
|
|
if (it.second->type == ID($_NOT_) && it.second->getPort(ID::Y) == clear_sig) {
|
2020-03-12 14:57:01 -05:00
|
|
|
clear_sig = it.second->getPort(ID::A);
|
2014-02-15 12:36:33 -06:00
|
|
|
clear_polarity = !clear_polarity;
|
|
|
|
rerun_invert_rollback = true;
|
|
|
|
}
|
2020-04-02 11:51:32 -05:00
|
|
|
if (it.second->type == ID($_NOT_) && it.second->getPort(ID::Y) == preset_sig) {
|
2020-03-12 14:57:01 -05:00
|
|
|
preset_sig = it.second->getPort(ID::A);
|
2014-02-15 12:36:33 -06:00
|
|
|
preset_polarity = !preset_polarity;
|
|
|
|
rerun_invert_rollback = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-02 11:51:32 -05:00
|
|
|
RTLIL::Cell *cell = module->addCell(NEW_ID, ID($_NOT_));
|
2020-03-12 14:57:01 -05:00
|
|
|
cell->setPort(ID::A, iq_sig);
|
|
|
|
cell->setPort(ID::Y, iqn_sig);
|
2014-02-15 12:36:33 -06:00
|
|
|
|
2014-07-25 08:05:18 -05:00
|
|
|
cell = module->addCell(NEW_ID, "");
|
2020-04-02 11:51:32 -05:00
|
|
|
cell->setPort(ID::D, data_sig);
|
|
|
|
cell->setPort(ID::Q, iq_sig);
|
|
|
|
cell->setPort(ID::C, clk_sig);
|
2014-02-15 12:36:33 -06:00
|
|
|
|
2014-07-22 13:15:14 -05:00
|
|
|
if (clear_sig.size() == 0 && preset_sig.size() == 0) {
|
2014-02-15 12:36:33 -06:00
|
|
|
cell->type = stringf("$_DFF_%c_", clk_polarity ? 'P' : 'N');
|
|
|
|
}
|
|
|
|
|
2014-07-22 13:15:14 -05:00
|
|
|
if (clear_sig.size() == 1 && preset_sig.size() == 0) {
|
2014-02-15 12:36:33 -06:00
|
|
|
cell->type = stringf("$_DFF_%c%c0_", clk_polarity ? 'P' : 'N', clear_polarity ? 'P' : 'N');
|
2020-04-02 11:51:32 -05:00
|
|
|
cell->setPort(ID::R, clear_sig);
|
2014-02-15 12:36:33 -06:00
|
|
|
}
|
|
|
|
|
2014-07-22 13:15:14 -05:00
|
|
|
if (clear_sig.size() == 0 && preset_sig.size() == 1) {
|
2014-02-15 12:36:33 -06:00
|
|
|
cell->type = stringf("$_DFF_%c%c1_", clk_polarity ? 'P' : 'N', preset_polarity ? 'P' : 'N');
|
2020-04-02 11:51:32 -05:00
|
|
|
cell->setPort(ID::R, preset_sig);
|
2014-02-15 12:36:33 -06:00
|
|
|
}
|
|
|
|
|
2014-07-22 13:15:14 -05:00
|
|
|
if (clear_sig.size() == 1 && preset_sig.size() == 1) {
|
2014-02-15 12:36:33 -06:00
|
|
|
cell->type = stringf("$_DFFSR_%c%c%c_", clk_polarity ? 'P' : 'N', preset_polarity ? 'P' : 'N', clear_polarity ? 'P' : 'N');
|
2020-03-12 14:57:01 -05:00
|
|
|
cell->setPort(ID::S, preset_sig);
|
2020-04-02 11:51:32 -05:00
|
|
|
cell->setPort(ID::R, clear_sig);
|
2014-02-15 12:36:33 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
log_assert(!cell->type.empty());
|
|
|
|
}
|
|
|
|
|
2018-01-03 14:29:55 -06:00
|
|
|
static bool create_latch(RTLIL::Module *module, LibertyAst *node, bool flag_ignore_miss_data_latch)
|
2014-02-15 12:36:33 -06:00
|
|
|
{
|
2014-07-21 05:35:06 -05:00
|
|
|
RTLIL::SigSpec iq_sig(module->addWire(RTLIL::escape_id(node->args.at(0))));
|
|
|
|
RTLIL::SigSpec iqn_sig(module->addWire(RTLIL::escape_id(node->args.at(1))));
|
2014-02-15 12:36:33 -06:00
|
|
|
|
|
|
|
RTLIL::SigSpec enable_sig, data_sig, clear_sig, preset_sig;
|
|
|
|
bool enable_polarity = true, clear_polarity = true, preset_polarity = true;
|
|
|
|
|
|
|
|
for (auto child : node->children) {
|
|
|
|
if (child->id == "enable")
|
|
|
|
enable_sig = parse_func_expr(module, child->value.c_str());
|
|
|
|
if (child->id == "data_in")
|
|
|
|
data_sig = parse_func_expr(module, child->value.c_str());
|
|
|
|
if (child->id == "clear")
|
|
|
|
clear_sig = parse_func_expr(module, child->value.c_str());
|
|
|
|
if (child->id == "preset")
|
|
|
|
preset_sig = parse_func_expr(module, child->value.c_str());
|
|
|
|
}
|
|
|
|
|
2018-01-03 14:29:55 -06:00
|
|
|
if (enable_sig.size() == 0 || data_sig.size() == 0) {
|
|
|
|
if (!flag_ignore_miss_data_latch)
|
|
|
|
log_error("Latch cell %s has no data_in and/or enable attribute.\n", log_id(module->name));
|
|
|
|
else
|
|
|
|
log("Ignored latch cell %s with no data_in and/or enable attribute.\n", log_id(module->name));
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2014-02-15 12:36:33 -06:00
|
|
|
|
|
|
|
for (bool rerun_invert_rollback = true; rerun_invert_rollback;)
|
|
|
|
{
|
|
|
|
rerun_invert_rollback = false;
|
|
|
|
|
2014-07-26 18:51:45 -05:00
|
|
|
for (auto &it : module->cells_) {
|
2020-04-02 11:51:32 -05:00
|
|
|
if (it.second->type == ID($_NOT_) && it.second->getPort(ID::Y) == enable_sig) {
|
2020-03-12 14:57:01 -05:00
|
|
|
enable_sig = it.second->getPort(ID::A);
|
2014-02-15 12:36:33 -06:00
|
|
|
enable_polarity = !enable_polarity;
|
|
|
|
rerun_invert_rollback = true;
|
|
|
|
}
|
2020-04-02 11:51:32 -05:00
|
|
|
if (it.second->type == ID($_NOT_) && it.second->getPort(ID::Y) == clear_sig) {
|
2020-03-12 14:57:01 -05:00
|
|
|
clear_sig = it.second->getPort(ID::A);
|
2014-02-15 12:36:33 -06:00
|
|
|
clear_polarity = !clear_polarity;
|
|
|
|
rerun_invert_rollback = true;
|
|
|
|
}
|
2020-04-02 11:51:32 -05:00
|
|
|
if (it.second->type == ID($_NOT_) && it.second->getPort(ID::Y) == preset_sig) {
|
2020-03-12 14:57:01 -05:00
|
|
|
preset_sig = it.second->getPort(ID::A);
|
2014-02-15 12:36:33 -06:00
|
|
|
preset_polarity = !preset_polarity;
|
|
|
|
rerun_invert_rollback = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-02 11:51:32 -05:00
|
|
|
RTLIL::Cell *cell = module->addCell(NEW_ID, ID($_NOT_));
|
2020-03-12 14:57:01 -05:00
|
|
|
cell->setPort(ID::A, iq_sig);
|
|
|
|
cell->setPort(ID::Y, iqn_sig);
|
2014-02-15 12:36:33 -06:00
|
|
|
|
2014-07-22 13:15:14 -05:00
|
|
|
if (clear_sig.size() == 1)
|
2014-02-15 12:36:33 -06:00
|
|
|
{
|
|
|
|
RTLIL::SigSpec clear_negative = clear_sig;
|
|
|
|
RTLIL::SigSpec clear_enable = clear_sig;
|
|
|
|
|
|
|
|
if (clear_polarity == true || clear_polarity != enable_polarity)
|
|
|
|
{
|
2020-04-02 11:51:32 -05:00
|
|
|
RTLIL::Cell *inv = module->addCell(NEW_ID, ID($_NOT_));
|
2020-03-12 14:57:01 -05:00
|
|
|
inv->setPort(ID::A, clear_sig);
|
|
|
|
inv->setPort(ID::Y, module->addWire(NEW_ID));
|
2014-02-15 12:36:33 -06:00
|
|
|
|
|
|
|
if (clear_polarity == true)
|
2020-03-12 14:57:01 -05:00
|
|
|
clear_negative = inv->getPort(ID::Y);
|
2014-02-15 12:36:33 -06:00
|
|
|
if (clear_polarity != enable_polarity)
|
2020-03-12 14:57:01 -05:00
|
|
|
clear_enable = inv->getPort(ID::Y);
|
2014-02-15 12:36:33 -06:00
|
|
|
}
|
|
|
|
|
2020-04-02 11:51:32 -05:00
|
|
|
RTLIL::Cell *data_gate = module->addCell(NEW_ID, ID($_AND_));
|
2020-03-12 14:57:01 -05:00
|
|
|
data_gate->setPort(ID::A, data_sig);
|
|
|
|
data_gate->setPort(ID::B, clear_negative);
|
|
|
|
data_gate->setPort(ID::Y, data_sig = module->addWire(NEW_ID));
|
2014-02-15 12:36:33 -06:00
|
|
|
|
2020-04-02 11:51:32 -05:00
|
|
|
RTLIL::Cell *enable_gate = module->addCell(NEW_ID, enable_polarity ? ID($_OR_) : ID($_AND_));
|
2020-03-12 14:57:01 -05:00
|
|
|
enable_gate->setPort(ID::A, enable_sig);
|
|
|
|
enable_gate->setPort(ID::B, clear_enable);
|
|
|
|
enable_gate->setPort(ID::Y, data_sig = module->addWire(NEW_ID));
|
2014-02-15 12:36:33 -06:00
|
|
|
}
|
|
|
|
|
2014-07-22 13:15:14 -05:00
|
|
|
if (preset_sig.size() == 1)
|
2014-02-15 12:36:33 -06:00
|
|
|
{
|
|
|
|
RTLIL::SigSpec preset_positive = preset_sig;
|
|
|
|
RTLIL::SigSpec preset_enable = preset_sig;
|
|
|
|
|
|
|
|
if (preset_polarity == false || preset_polarity != enable_polarity)
|
|
|
|
{
|
2020-04-02 11:51:32 -05:00
|
|
|
RTLIL::Cell *inv = module->addCell(NEW_ID, ID($_NOT_));
|
2020-03-12 14:57:01 -05:00
|
|
|
inv->setPort(ID::A, preset_sig);
|
|
|
|
inv->setPort(ID::Y, module->addWire(NEW_ID));
|
2014-02-15 12:36:33 -06:00
|
|
|
|
|
|
|
if (preset_polarity == false)
|
2020-03-12 14:57:01 -05:00
|
|
|
preset_positive = inv->getPort(ID::Y);
|
2014-02-15 12:36:33 -06:00
|
|
|
if (preset_polarity != enable_polarity)
|
2020-03-12 14:57:01 -05:00
|
|
|
preset_enable = inv->getPort(ID::Y);
|
2014-02-15 12:36:33 -06:00
|
|
|
}
|
|
|
|
|
2020-04-02 11:51:32 -05:00
|
|
|
RTLIL::Cell *data_gate = module->addCell(NEW_ID, ID($_OR_));
|
2020-03-12 14:57:01 -05:00
|
|
|
data_gate->setPort(ID::A, data_sig);
|
|
|
|
data_gate->setPort(ID::B, preset_positive);
|
|
|
|
data_gate->setPort(ID::Y, data_sig = module->addWire(NEW_ID));
|
2014-02-15 12:36:33 -06:00
|
|
|
|
2020-04-02 11:51:32 -05:00
|
|
|
RTLIL::Cell *enable_gate = module->addCell(NEW_ID, enable_polarity ? ID($_OR_) : ID($_AND_));
|
2020-03-12 14:57:01 -05:00
|
|
|
enable_gate->setPort(ID::A, enable_sig);
|
|
|
|
enable_gate->setPort(ID::B, preset_enable);
|
|
|
|
enable_gate->setPort(ID::Y, data_sig = module->addWire(NEW_ID));
|
2014-02-15 12:36:33 -06:00
|
|
|
}
|
|
|
|
|
2014-07-25 08:05:18 -05:00
|
|
|
cell = module->addCell(NEW_ID, stringf("$_DLATCH_%c_", enable_polarity ? 'P' : 'N'));
|
2020-04-02 11:51:32 -05:00
|
|
|
cell->setPort(ID::D, data_sig);
|
|
|
|
cell->setPort(ID::Q, iq_sig);
|
|
|
|
cell->setPort(ID::E, enable_sig);
|
2018-01-03 14:29:55 -06:00
|
|
|
|
|
|
|
return true;
|
2014-02-15 12:36:33 -06:00
|
|
|
}
|
|
|
|
|
2016-09-23 06:53:23 -05:00
|
|
|
void parse_type_map(std::map<std::string, std::tuple<int, int, bool>> &type_map, LibertyAst *ast)
|
|
|
|
{
|
|
|
|
for (auto type_node : ast->children)
|
|
|
|
{
|
|
|
|
if (type_node->id != "type" || type_node->args.size() != 1)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
std::string type_name = type_node->args.at(0);
|
|
|
|
int bit_width = -1, bit_from = -1, bit_to = -1;
|
|
|
|
bool upto = false;
|
|
|
|
|
|
|
|
for (auto child : type_node->children)
|
|
|
|
{
|
|
|
|
if (child->id == "base_type" && child->value != "array")
|
|
|
|
goto next_type;
|
|
|
|
|
|
|
|
if (child->id == "data_type" && child->value != "bit")
|
|
|
|
goto next_type;
|
|
|
|
|
|
|
|
if (child->id == "bit_width")
|
|
|
|
bit_width = atoi(child->value.c_str());
|
|
|
|
|
|
|
|
if (child->id == "bit_from")
|
|
|
|
bit_from = atoi(child->value.c_str());
|
|
|
|
|
|
|
|
if (child->id == "bit_to")
|
|
|
|
bit_to = atoi(child->value.c_str());
|
|
|
|
|
|
|
|
if (child->id == "downto" && (child->value == "0" || child->value == "false" || child->value == "FALSE"))
|
|
|
|
upto = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bit_width != (std::max(bit_from, bit_to) - std::min(bit_from, bit_to) + 1))
|
|
|
|
log_error("Incompatible array type '%s': bit_width=%d, bit_from=%d, bit_to=%d.\n",
|
|
|
|
type_name.c_str(), bit_width, bit_from, bit_to);
|
|
|
|
|
|
|
|
type_map[type_name] = std::tuple<int, int, bool>(bit_width, std::min(bit_from, bit_to), upto);
|
|
|
|
next_type:;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-15 05:57:28 -06:00
|
|
|
struct LibertyFrontend : public Frontend {
|
|
|
|
LibertyFrontend() : Frontend("liberty", "read cells from liberty file") { }
|
2020-06-18 18:34:52 -05:00
|
|
|
void help() override
|
2014-02-15 05:57:28 -06:00
|
|
|
{
|
|
|
|
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
|
|
|
log("\n");
|
|
|
|
log(" read_liberty [filename]\n");
|
|
|
|
log("\n");
|
|
|
|
log("Read cells from liberty file as modules into current design.\n");
|
|
|
|
log("\n");
|
|
|
|
log(" -lib\n");
|
|
|
|
log(" only create empty blackbox modules\n");
|
|
|
|
log("\n");
|
2021-11-25 13:13:08 -06:00
|
|
|
log(" -wb\n");
|
|
|
|
log(" mark imported cells as whiteboxes\n");
|
|
|
|
log("\n");
|
2018-05-03 08:25:59 -05:00
|
|
|
log(" -nooverwrite\n");
|
2014-02-15 05:57:28 -06:00
|
|
|
log(" ignore re-definitions of modules. (the default behavior is to\n");
|
2018-05-03 08:25:59 -05:00
|
|
|
log(" create an error message if the existing module is not a blackbox\n");
|
|
|
|
log(" module, and overwrite the existing module if it is a blackbox module.)\n");
|
|
|
|
log("\n");
|
|
|
|
log(" -overwrite\n");
|
|
|
|
log(" overwrite existing modules with the same name\n");
|
2014-02-15 05:57:28 -06:00
|
|
|
log("\n");
|
2014-05-28 09:50:13 -05:00
|
|
|
log(" -ignore_miss_func\n");
|
|
|
|
log(" ignore cells with missing function specification of outputs\n");
|
|
|
|
log("\n");
|
|
|
|
log(" -ignore_miss_dir\n");
|
|
|
|
log(" ignore cells with a missing or invalid direction\n");
|
|
|
|
log(" specification on a pin\n");
|
|
|
|
log("\n");
|
2018-01-03 14:29:55 -06:00
|
|
|
log(" -ignore_miss_data_latch\n");
|
|
|
|
log(" ignore latches with missing data and/or enable pins\n");
|
|
|
|
log("\n");
|
2014-02-15 05:57:28 -06:00
|
|
|
log(" -setattr <attribute_name>\n");
|
|
|
|
log(" set the specified attribute (to the value 1) on all loaded modules\n");
|
|
|
|
log("\n");
|
|
|
|
}
|
2020-06-18 18:34:52 -05:00
|
|
|
void execute(std::istream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
|
2014-02-15 05:57:28 -06:00
|
|
|
{
|
|
|
|
bool flag_lib = false;
|
2021-11-25 13:13:08 -06:00
|
|
|
bool flag_wb = false;
|
2018-05-03 08:25:59 -05:00
|
|
|
bool flag_nooverwrite = false;
|
|
|
|
bool flag_overwrite = false;
|
2014-05-28 09:50:13 -05:00
|
|
|
bool flag_ignore_miss_func = false;
|
|
|
|
bool flag_ignore_miss_dir = false;
|
2018-01-03 14:29:55 -06:00
|
|
|
bool flag_ignore_miss_data_latch = false;
|
2014-02-15 05:57:28 -06:00
|
|
|
std::vector<std::string> attributes;
|
|
|
|
|
2016-04-21 16:28:37 -05:00
|
|
|
log_header(design, "Executing Liberty frontend.\n");
|
2014-02-15 05:57:28 -06:00
|
|
|
|
|
|
|
size_t argidx;
|
|
|
|
for (argidx = 1; argidx < args.size(); argidx++) {
|
|
|
|
std::string arg = args[argidx];
|
|
|
|
if (arg == "-lib") {
|
|
|
|
flag_lib = true;
|
|
|
|
continue;
|
|
|
|
}
|
2021-11-25 13:13:08 -06:00
|
|
|
if (arg == "-wb") {
|
|
|
|
flag_wb = true;
|
|
|
|
continue;
|
|
|
|
}
|
2018-05-03 08:25:59 -05:00
|
|
|
if (arg == "-ignore_redef" || arg == "-nooverwrite") {
|
|
|
|
flag_nooverwrite = true;
|
|
|
|
flag_overwrite = false;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (arg == "-overwrite") {
|
|
|
|
flag_nooverwrite = false;
|
|
|
|
flag_overwrite = true;
|
2014-02-15 05:57:28 -06:00
|
|
|
continue;
|
|
|
|
}
|
2014-05-28 09:50:13 -05:00
|
|
|
if (arg == "-ignore_miss_func") {
|
|
|
|
flag_ignore_miss_func = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (arg == "-ignore_miss_dir") {
|
|
|
|
flag_ignore_miss_dir = true;
|
|
|
|
continue;
|
|
|
|
}
|
2018-01-03 14:29:55 -06:00
|
|
|
if (arg == "-ignore_miss_data_latch") {
|
|
|
|
flag_ignore_miss_data_latch = true;
|
|
|
|
continue;
|
|
|
|
}
|
2014-02-15 05:57:28 -06:00
|
|
|
if (arg == "-setattr" && argidx+1 < args.size()) {
|
|
|
|
attributes.push_back(RTLIL::escape_id(args[++argidx]));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
extra_args(f, filename, args, argidx);
|
|
|
|
|
2021-11-25 13:13:08 -06:00
|
|
|
if (flag_wb && flag_lib)
|
|
|
|
log_error("-wb and -lib cannot be specified together!\n");
|
|
|
|
|
2014-08-23 08:03:55 -05:00
|
|
|
LibertyParser parser(*f);
|
2014-02-15 05:57:28 -06:00
|
|
|
int cell_count = 0;
|
|
|
|
|
2016-09-23 06:53:23 -05:00
|
|
|
std::map<std::string, std::tuple<int, int, bool>> global_type_map;
|
|
|
|
parse_type_map(global_type_map, parser.ast);
|
2016-09-18 11:48:59 -05:00
|
|
|
|
2014-02-15 05:57:28 -06:00
|
|
|
for (auto cell : parser.ast->children)
|
|
|
|
{
|
|
|
|
if (cell->id != "cell" || cell->args.size() != 1)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
std::string cell_name = RTLIL::escape_id(cell->args.at(0));
|
|
|
|
|
2014-07-31 07:11:39 -05:00
|
|
|
if (design->has(cell_name)) {
|
2018-05-03 08:25:59 -05:00
|
|
|
Module *existing_mod = design->module(cell_name);
|
2020-04-02 11:51:32 -05:00
|
|
|
if (!flag_nooverwrite && !flag_overwrite && !existing_mod->get_bool_attribute(ID::blackbox)) {
|
2019-07-16 04:03:30 -05:00
|
|
|
log_error("Re-definition of cell/module %s!\n", log_id(cell_name));
|
2018-05-03 08:25:59 -05:00
|
|
|
} else if (flag_nooverwrite) {
|
|
|
|
log("Ignoring re-definition of module %s.\n", log_id(cell_name));
|
2014-02-15 05:57:28 -06:00
|
|
|
continue;
|
2018-05-03 08:25:59 -05:00
|
|
|
} else {
|
2020-04-02 11:51:32 -05:00
|
|
|
log("Replacing existing%s module %s.\n", existing_mod->get_bool_attribute(ID::blackbox) ? " blackbox" : "", log_id(cell_name));
|
2018-05-03 08:25:59 -05:00
|
|
|
design->remove(existing_mod);
|
|
|
|
}
|
2014-02-15 05:57:28 -06:00
|
|
|
}
|
|
|
|
|
2014-08-02 09:03:18 -05:00
|
|
|
// log("Processing cell type %s.\n", RTLIL::unescape_id(cell_name).c_str());
|
2014-02-15 05:57:28 -06:00
|
|
|
|
2016-09-23 06:53:23 -05:00
|
|
|
std::map<std::string, std::tuple<int, int, bool>> type_map = global_type_map;
|
|
|
|
parse_type_map(type_map, cell);
|
|
|
|
|
2014-02-15 05:57:28 -06:00
|
|
|
RTLIL::Module *module = new RTLIL::Module;
|
|
|
|
module->name = cell_name;
|
|
|
|
|
2014-07-16 11:12:16 -05:00
|
|
|
if (flag_lib)
|
2020-04-02 11:51:32 -05:00
|
|
|
module->set_bool_attribute(ID::blackbox);
|
2014-07-16 11:12:16 -05:00
|
|
|
|
2021-11-25 13:13:08 -06:00
|
|
|
if (flag_wb)
|
|
|
|
module->set_bool_attribute(ID::whitebox);
|
|
|
|
|
2014-02-15 05:57:28 -06:00
|
|
|
for (auto &attr : attributes)
|
|
|
|
module->attributes[attr] = 1;
|
|
|
|
|
2014-02-15 12:36:33 -06:00
|
|
|
for (auto node : cell->children)
|
2016-09-18 11:48:59 -05:00
|
|
|
{
|
2014-02-15 12:36:33 -06:00
|
|
|
if (node->id == "pin" && node->args.size() == 1) {
|
|
|
|
LibertyAst *dir = node->find("direction");
|
2014-07-16 11:12:46 -05:00
|
|
|
if (!dir || (dir->value != "input" && dir->value != "output" && dir->value != "inout" && dir->value != "internal"))
|
2014-05-28 09:50:13 -05:00
|
|
|
{
|
|
|
|
if (!flag_ignore_miss_dir)
|
|
|
|
{
|
2016-09-18 11:48:59 -05:00
|
|
|
log_error("Missing or invalid direction for pin %s on cell %s.\n", node->args.at(0).c_str(), log_id(module->name));
|
2014-05-28 09:50:13 -05:00
|
|
|
} else {
|
2014-08-02 09:03:18 -05:00
|
|
|
log("Ignoring cell %s with missing or invalid direction for pin %s.\n", log_id(module->name), node->args.at(0).c_str());
|
2014-05-28 09:50:13 -05:00
|
|
|
delete module;
|
|
|
|
goto skip_cell;
|
|
|
|
}
|
|
|
|
}
|
2014-02-15 12:36:33 -06:00
|
|
|
if (!flag_lib || dir->value != "internal")
|
2014-07-21 05:35:06 -05:00
|
|
|
module->addWire(RTLIL::escape_id(node->args.at(0)));
|
2014-02-15 12:36:33 -06:00
|
|
|
}
|
2014-02-15 05:57:28 -06:00
|
|
|
|
2016-09-18 11:48:59 -05:00
|
|
|
if (node->id == "bus" && node->args.size() == 1)
|
|
|
|
{
|
|
|
|
if (!flag_lib)
|
|
|
|
log_error("Error in cell %s: bus interfaces are only supported in -lib mode.\n", log_id(cell_name));
|
|
|
|
|
|
|
|
LibertyAst *dir = node->find("direction");
|
|
|
|
|
2018-02-15 10:36:08 -06:00
|
|
|
if (dir == nullptr) {
|
|
|
|
LibertyAst *pin = node->find("pin");
|
|
|
|
if (pin != nullptr)
|
|
|
|
dir = pin->find("direction");
|
|
|
|
}
|
|
|
|
|
2016-09-18 11:48:59 -05:00
|
|
|
if (!dir || (dir->value != "input" && dir->value != "output" && dir->value != "inout" && dir->value != "internal"))
|
|
|
|
log_error("Missing or invalid direction for bus %s on cell %s.\n", node->args.at(0).c_str(), log_id(module->name));
|
|
|
|
|
|
|
|
if (dir->value == "internal")
|
|
|
|
continue;
|
|
|
|
|
|
|
|
LibertyAst *bus_type_node = node->find("bus_type");
|
|
|
|
|
|
|
|
if (!bus_type_node || !type_map.count(bus_type_node->value))
|
2018-12-07 13:14:07 -06:00
|
|
|
log_error("Unknown or unsupported type for bus interface %s on cell %s.\n",
|
2016-09-18 11:48:59 -05:00
|
|
|
node->args.at(0).c_str(), log_id(cell_name));
|
|
|
|
|
|
|
|
int bus_type_width = std::get<0>(type_map.at(bus_type_node->value));
|
|
|
|
int bus_type_offset = std::get<1>(type_map.at(bus_type_node->value));
|
|
|
|
bool bus_type_upto = std::get<2>(type_map.at(bus_type_node->value));
|
|
|
|
|
|
|
|
Wire *wire = module->addWire(RTLIL::escape_id(node->args.at(0)), bus_type_width);
|
|
|
|
wire->start_offset = bus_type_offset;
|
|
|
|
wire->upto = bus_type_upto;
|
|
|
|
|
|
|
|
if (dir->value == "input" || dir->value == "inout")
|
|
|
|
wire->port_input = true;
|
|
|
|
|
|
|
|
if (dir->value == "output" || dir->value == "inout")
|
|
|
|
wire->port_output = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-17 05:23:36 -05:00
|
|
|
if (!flag_lib)
|
2014-02-15 12:36:33 -06:00
|
|
|
{
|
2018-10-17 05:23:36 -05:00
|
|
|
// some liberty files do not put ff/latch at the beginning of a cell
|
|
|
|
// try to find "ff" or "latch" and create FF/latch _before_ processing all other nodes
|
|
|
|
for (auto node : cell->children)
|
|
|
|
{
|
2014-02-15 12:36:33 -06:00
|
|
|
if (node->id == "ff" && node->args.size() == 2)
|
|
|
|
create_ff(module, node);
|
|
|
|
if (node->id == "latch" && node->args.size() == 2)
|
2018-01-03 14:29:55 -06:00
|
|
|
if (!create_latch(module, node, flag_ignore_miss_data_latch)) {
|
|
|
|
delete module;
|
|
|
|
goto skip_cell;
|
|
|
|
}
|
2014-02-15 12:36:33 -06:00
|
|
|
}
|
2018-10-13 12:42:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
for (auto node : cell->children)
|
|
|
|
{
|
2014-02-15 12:36:33 -06:00
|
|
|
if (node->id == "pin" && node->args.size() == 1)
|
|
|
|
{
|
|
|
|
LibertyAst *dir = node->find("direction");
|
2014-02-15 05:57:28 -06:00
|
|
|
|
2014-02-15 12:36:33 -06:00
|
|
|
if (flag_lib && dir->value == "internal")
|
|
|
|
continue;
|
2014-02-15 05:57:28 -06:00
|
|
|
|
2014-07-26 18:49:51 -05:00
|
|
|
RTLIL::Wire *wire = module->wires_.at(RTLIL::escape_id(node->args.at(0)));
|
2014-02-15 05:57:28 -06:00
|
|
|
|
2014-07-16 11:12:46 -05:00
|
|
|
if (dir && dir->value == "inout") {
|
|
|
|
wire->port_input = true;
|
|
|
|
wire->port_output = true;
|
|
|
|
}
|
|
|
|
|
2014-02-15 12:36:33 -06:00
|
|
|
if (dir && dir->value == "input") {
|
|
|
|
wire->port_input = true;
|
|
|
|
continue;
|
|
|
|
}
|
2014-02-15 05:57:28 -06:00
|
|
|
|
2014-02-15 12:36:33 -06:00
|
|
|
if (dir && dir->value == "output")
|
|
|
|
wire->port_output = true;
|
2014-02-15 05:57:28 -06:00
|
|
|
|
2014-02-15 12:36:33 -06:00
|
|
|
if (flag_lib)
|
|
|
|
continue;
|
2014-02-15 05:57:28 -06:00
|
|
|
|
2014-02-15 12:36:33 -06:00
|
|
|
LibertyAst *func = node->find("function");
|
|
|
|
if (func == NULL)
|
2014-05-28 09:50:13 -05:00
|
|
|
{
|
|
|
|
if (!flag_ignore_miss_func)
|
|
|
|
{
|
2014-08-02 09:03:18 -05:00
|
|
|
log_error("Missing function on output %s of cell %s.\n", log_id(wire->name), log_id(module->name));
|
2014-05-28 09:50:13 -05:00
|
|
|
} else {
|
2014-08-02 09:03:18 -05:00
|
|
|
log("Ignoring cell %s with missing function on output %s.\n", log_id(module->name), log_id(wire->name));
|
2014-05-28 09:50:13 -05:00
|
|
|
delete module;
|
|
|
|
goto skip_cell;
|
|
|
|
}
|
|
|
|
}
|
2014-02-15 05:57:28 -06:00
|
|
|
|
2014-02-15 12:36:33 -06:00
|
|
|
RTLIL::SigSpec out_sig = parse_func_expr(module, func->value.c_str());
|
2014-07-26 07:32:50 -05:00
|
|
|
module->connect(RTLIL::SigSig(wire, out_sig));
|
2014-02-15 12:36:33 -06:00
|
|
|
}
|
2014-02-15 05:57:28 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
module->fixup_ports();
|
2014-07-31 07:11:39 -05:00
|
|
|
design->add(module);
|
2014-05-28 09:50:13 -05:00
|
|
|
cell_count++;
|
|
|
|
skip_cell:;
|
2014-02-15 05:57:28 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
log("Imported %d cell types from liberty file.\n", cell_count);
|
|
|
|
}
|
|
|
|
} LibertyFrontend;
|
|
|
|
|
2014-07-31 06:19:47 -05:00
|
|
|
YOSYS_NAMESPACE_END
|
|
|
|
|