/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Clifford Wolf * * 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. * */ // Currently supported SVA sequence and property syntax: // http://symbiyosys.readthedocs.io/en/latest/verific.html // // Next gen property syntax: // basic_property // [antecedent_condition] property // [antecedent_condition] always.. property // [antecedent_condition] eventually.. basic_property // [antecedent_condition] property until.. expression // [antecedent_condition] basic_property until.. basic_property (assert/assume only) // // antecedent_condition: // sequence |-> // sequence |=> // // basic_property: // sequence // not basic_property // sequence #-# basic_property // sequence #=# basic_property // basic_property or basic_property (cover only) // basic_property and basic_property (assert/assume only) // basic_property implies basic_property // basic_property iff basic_property // // sequence: // expression // sequence ##N sequence // sequence ##[*] sequence // sequence ##[+] sequence // sequence ##[N:M] sequence // sequence ##[N:$] sequence // sequence [*] // sequence [+] // sequence [*N] // sequence [*N:M] // sequence [*N:$] // sequence or sequence // sequence and sequence // expression throughout sequence // sequence intersect sequence // sequence within sequence // first_match( sequence ) // expression [=N] // expression [=N:M] // expression [=N:$] // expression [->N] // expression [->N:M] // expression [->N:$] #include "kernel/yosys.h" #include "frontends/verific/verific.h" USING_YOSYS_NAMESPACE #ifdef VERIFIC_NAMESPACE using namespace Verific; #endif PRIVATE_NAMESPACE_BEGIN // Non-deterministic FSM struct SvaNFsmNode { // Edge: Activate the target node if ctrl signal is true, consumes clock cycle // Link: Activate the target node if ctrl signal is true, doesn't consume clock cycle vector> edges, links; bool is_cond_node; }; // Non-deterministic FSM after resolving links struct SvaUFsmNode { // Edge: Activate the target node if all bits in ctrl signal are true, consumes clock cycle // Accept: This node functions as an accept node if all bits in ctrl signal are true vector> edges; vector accept, cond; bool reachable; }; // Deterministic FSM struct SvaDFsmNode { // A DFSM state corresponds to a set of NFSM states. We represent DFSM states as sorted vectors // of NFSM state node ids. Edge/accept controls are constants matched against the ctrl sigspec. SigSpec ctrl; vector, Const>> edges; vector accept, reject; // additional temp data for getReject() Wire *ffoutwire; SigBit statesig; SigSpec nextstate; // additional temp data for getDFsm() int outnode; }; struct SvaFsm { Module *module; VerificClocking clocking; SigBit trigger_sig = State::S1, disable_sig; SigBit throughout_sig = State::S1; bool in_cond_mode = false; vector disable_stack; vector throughout_stack; int startNode, acceptNode, condNode; vector nodes; vector unodes; dict, SvaDFsmNode> dnodes; dict, SigBit> cond_eq_cache; bool materialized = false; SigBit final_accept_sig = State::Sx; SigBit final_reject_sig = State::Sx; SvaFsm(const VerificClocking &clking, SigBit trig = State::S1) { module = clking.module; clocking = clking; trigger_sig = trig; startNode = createNode(); acceptNode = createNode(); in_cond_mode = true; condNode = createNode(); in_cond_mode = false; } void pushDisable(SigBit sig) { log_assert(!materialized); disable_stack.push_back(disable_sig); if (disable_sig == State::S0) disable_sig = sig; else disable_sig = module->Or(NEW_ID, disable_sig, sig); } void popDisable() { log_assert(!materialized); log_assert(!disable_stack.empty()); disable_sig = disable_stack.back(); disable_stack.pop_back(); } void pushThroughout(SigBit sig) { log_assert(!materialized); throughout_stack.push_back(throughout_sig); if (throughout_sig == State::S1) throughout_sig = sig; else throughout_sig = module->And(NEW_ID, throughout_sig, sig); } void popThroughout() { log_assert(!materialized); log_assert(!throughout_stack.empty()); throughout_sig = throughout_stack.back(); throughout_stack.pop_back(); } int createNode(int link_node = -1) { log_assert(!materialized); int idx = GetSize(nodes); nodes.push_back(SvaNFsmNode()); nodes.back().is_cond_node = in_cond_mode; if (link_node >= 0) createLink(link_node, idx); return idx; } int createStartNode() { return createNode(startNode); } void createEdge(int from_node, int to_node, SigBit ctrl = State::S1) { log_assert(!materialized); log_assert(0 <= from_node && from_node < GetSize(nodes)); log_assert(0 <= to_node && to_node < GetSize(nodes)); log_assert(from_node != acceptNode); log_assert(to_node != acceptNode); log_assert(from_node != condNode); log_assert(to_node != condNode); log_assert(to_node != startNode); if (from_node != startNode) log_assert(nodes.at(from_node).is_cond_node == nodes.at(to_node).is_cond_node); if (throughout_sig != State::S1) { if (ctrl != State::S1) ctrl = module->And(NEW_ID, throughout_sig, ctrl); else ctrl = throughout_sig; } nodes[from_node].edges.push_back(make_pair(to_node, ctrl)); } void createLink(int from_node, int to_node, SigBit ctrl = State::S1) { log_assert(!materialized); log_assert(0 <= from_node && from_node < GetSize(nodes)); log_assert(0 <= to_node && to_node < GetSize(nodes)); log_assert(from_node != acceptNode); log_assert(from_node != condNode); log_assert(to_node != startNode); if (from_node != startNode) log_assert(nodes.at(from_node).is_cond_node == nodes.at(to_node).is_cond_node); if (throughout_sig != State::S1) { if (ctrl != State::S1) ctrl = module->And(NEW_ID, throughout_sig, ctrl); else ctrl = throughout_sig; } nodes[from_node].links.push_back(make_pair(to_node, ctrl)); } void make_link_order(vector &order, int node, int min) { order[node] = std::max(order[node], min); for (auto &it : nodes[node].links) make_link_order(order, it.first, order[node]+1); } // ---------------------------------------------------- // Generating NFSM circuit to acquire accept signal SigBit getAccept() { log_assert(!materialized); materialized = true; vector state_wire(GetSize(nodes)); vector state_sig(GetSize(nodes)); vector next_state_sig(GetSize(nodes)); // Create state signals { SigBit not_disable = State::S1; if (disable_sig != State::S0) not_disable = module->Not(NEW_ID, disable_sig); for (int i = 0; i < GetSize(nodes); i++) { Wire *w = module->addWire(NEW_ID); state_wire[i] = w; state_sig[i] = w; if (i == startNode) state_sig[i] = module->Or(NEW_ID, state_sig[i], trigger_sig); if (disable_sig != State::S0) state_sig[i] = module->And(NEW_ID, state_sig[i], not_disable); } } // Follow Links { vector node_order(GetSize(nodes)); vector> order_to_nodes; for (int i = 0; i < GetSize(nodes); i++) make_link_order(node_order, i, 0); for (int i = 0; i < GetSize(nodes); i++) { if (node_order[i] >= GetSize(order_to_nodes)) order_to_nodes.resize(node_order[i]+1); order_to_nodes[node_order[i]].push_back(i); } for (int order = 0; order < GetSize(order_to_nodes); order++) for (int node : order_to_nodes[order]) { for (auto &it : nodes[node].links) { int target = it.first; SigBit ctrl = state_sig[node]; if (it.second != State::S1) ctrl = module->And(NEW_ID, ctrl, it.second); state_sig[target] = module->Or(NEW_ID, state_sig[target], ctrl); } } } // Construct activations { vector activate_sig(GetSize(nodes)); vector activate_bit(GetSize(nodes)); for (int i = 0; i < GetSize(nodes); i++) { for (auto &it : nodes[i].edges) activate_sig[it.first].append(module->And(NEW_ID, state_sig[i], it.second)); } for (int i = 0; i < GetSize(nodes); i++) { if (GetSize(activate_sig[i]) == 0) next_state_sig[i] = State::S0; else if (GetSize(activate_sig[i]) == 1) next_state_sig[i] = activate_sig[i]; else next_state_sig[i] = module->ReduceOr(NEW_ID, activate_sig[i]); } } // Create state FFs for (int i = 0; i < GetSize(nodes); i++) { if (next_state_sig[i] != State::S0) { clocking.addDff(NEW_ID, next_state_sig[i], state_wire[i], Const(0, 1)); } else { module->connect(state_wire[i], State::S0); } } final_accept_sig = state_sig[acceptNode]; return final_accept_sig; } // ---------------------------------------------------- // Generating quantifier-based NFSM circuit to acquire reject signal SigBit getAnyAllRejectWorker(bool /* allMode */) { // FIXME log_abort(); } SigBit getAnyReject() { return getAnyAllRejectWorker(false); } SigBit getAllReject() { return getAnyAllRejectWorker(true); } // ---------------------------------------------------- // Generating DFSM circuit to acquire reject signal void node_to_unode(int node, int unode, SigSpec ctrl) { if (node == acceptNode) unodes[unode].accept.push_back(ctrl); if (node == condNode) unodes[unode].cond.push_back(ctrl); for (auto &it : nodes[node].edges) { if (it.second != State::S1) { SigSpec s = {ctrl, it.second}; s.sort_and_unify(); unodes[unode].edges.push_back(make_pair(it.first, s)); } else { unodes[unode].edges.push_back(make_pair(it.first, ctrl)); } } for (auto &it : nodes[node].links) { if (it.second != State::S1) { SigSpec s = {ctrl, it.second}; s.sort_and_unify(); node_to_unode(it.first, unode, s); } else { node_to_unode(it.first, unode, ctrl); } } } void mark_reachable_unode(int unode) { if (unodes[unode].reachable) return; unodes[unode].reachable = true; for (auto &it : unodes[unode].edges) mark_reachable_unode(it.first); } void usortint(vector &vec) { vector newvec; std::sort(vec.begin(), vec.end()); for (int i = 0; i < GetSize(vec); i++) if (i == GetSize(vec)-1 || vec[i] != vec[i+1]) newvec.push_back(vec[i]); vec.swap(newvec); } bool cmp_ctrl(const pool &ctrl_bits, const SigSpec &ctrl) { for (int i = 0; i < GetSize(ctrl); i++) if (ctrl_bits.count(ctrl[i]) == 0) return false; return true; } void create_dnode(const vector &state, bool firstmatch, bool condaccept) { if (dnodes.count(state) != 0) return; SvaDFsmNode dnode; dnodes[state] = SvaDFsmNode(); for (int unode : state) { log_assert(unodes[unode].reachable); for (auto &it : unodes[unode].edges) dnode.ctrl.append(it.second); for (auto &it : unodes[unode].accept) dnode.ctrl.append(it); for (auto &it : unodes[unode].cond) dnode.ctrl.append(it); } dnode.ctrl.sort_and_unify(); if (GetSize(dnode.ctrl) > 16) { if (verific_verbose >= 2) { log(" detected state explosion in DFSM generation:\n"); dump(); log(" ctrl signal: %s\n", log_signal(dnode.ctrl)); } log_error("SVA DFSM state ctrl signal has %d (>16) bits. Stopping to prevent exponential design size explosion.\n", GetSize(dnode.ctrl)); } for (int i = 0; i < (1 << GetSize(dnode.ctrl)); i++) { Const ctrl_val(i, GetSize(dnode.ctrl)); pool ctrl_bits; for (int i = 0; i < GetSize(dnode.ctrl); i++) if (ctrl_val[i] == State::S1) ctrl_bits.insert(dnode.ctrl[i]); vector new_state; bool accept = false, cond = false; for (int unode : state) { for (auto &it : unodes[unode].accept) if (cmp_ctrl(ctrl_bits, it)) accept = true; for (auto &it : unodes[unode].cond) if (cmp_ctrl(ctrl_bits, it)) cond = true; } bool new_state_cond = false; bool new_state_noncond = false; if (accept && condaccept) accept = cond; if (!accept || !firstmatch) { for (int unode : state) for (auto &it : unodes[unode].edges) if (cmp_ctrl(ctrl_bits, it.second)) { if (nodes.at(it.first).is_cond_node) new_state_cond = true; else new_state_noncond = true; new_state.push_back(it.first); } } if (accept) dnode.accept.push_back(ctrl_val); if (condaccept && (!new_state_cond || !new_state_noncond)) new_state.clear(); if (new_state.empty()) { if (!accept) dnode.reject.push_back(ctrl_val); } else { usortint(new_state); dnode.edges.push_back(make_pair(new_state, ctrl_val)); create_dnode(new_state, firstmatch, condaccept); } } dnodes[state] = dnode; } void optimize_cond(vector &values) { bool did_something = true; while (did_something) { did_something = false; for (int i = 0; i < GetSize(values); i++) for (int j = 0; j < GetSize(values); j++) { if (i == j) continue; log_assert(GetSize(values[i]) == GetSize(values[j])); int delta_pos = -1; bool i_within_j = true; bool j_within_i = true; for (int k = 0; k < GetSize(values[i]); k++) { if (values[i][k] == State::Sa && values[j][k] != State::Sa) { i_within_j = false; continue; } if (values[i][k] != State::Sa && values[j][k] == State::Sa) { j_within_i = false; continue; } if (values[i][k] == values[j][k]) continue; if (delta_pos >= 0) goto next_pair; delta_pos = k; } if (delta_pos >= 0 && i_within_j && j_within_i) { did_something = true; values[i][delta_pos] = State::Sa; values[j] = values.back(); values.pop_back(); goto next_pair; } if (delta_pos < 0 && i_within_j) { did_something = true; values[i] = values.back(); values.pop_back(); goto next_pair; } if (delta_pos < 0 && j_within_i) { did_something = true; values[j] = values.back(); values.pop_back(); goto next_pair; } next_pair:; } } } SigBit make_cond_eq(const SigSpec &ctrl, const Const &value, SigBit enable = State::S1) { SigSpec sig_a, sig_b; log_assert(GetSize(ctrl) == GetSize(value)); for (int i = 0; i < GetSize(ctrl); i++) if (value[i] != State::Sa) { sig_a.append(ctrl[i]); sig_b.append(value[i]); } if (GetSize(sig_a) == 0) return enable; if (enable != State::S1) { sig_a.append(enable); sig_b.append(State::S1); } auto key = make_pair(sig_a, sig_b); if (cond_eq_cache.count(key) == 0) { if (sig_b == State::S1) cond_eq_cache[key] = sig_a; else if (sig_b == State::S0) cond_eq_cache[key] = module->Not(NEW_ID, sig_a); else cond_eq_cache[key] = module->Eq(NEW_ID, sig_a, sig_b); if (verific_verbose >= 2) { log(" Cond: %s := %s == %s\n", log_signal(cond_eq_cache[key]), log_signal(sig_a), log_signal(sig_b)); } } return cond_eq_cache.at(key); } void getFirstAcceptReject(SigBit *accept_p, SigBit *reject_p) { log_assert(!materialized); materialized = true; // Create unlinked NFSM unodes.resize(GetSize(nodes)); for (int node = 0; node < GetSize(nodes); node++) node_to_unode(node, node, SigSpec()); mark_reachable_unode(startNode); // Create DFSM create_dnode(vector{startNode}, true, false); dnodes.sort(); // Create DFSM Circuit SigSpec accept_sig, reject_sig; for (auto &it : dnodes) { SvaDFsmNode &dnode = it.second; dnode.ffoutwire = module->addWire(NEW_ID); dnode.statesig = dnode.ffoutwire; if (it.first == vector{startNode}) dnode.statesig = module->Or(NEW_ID, dnode.statesig, trigger_sig); } for (auto &it : dnodes) { SvaDFsmNode &dnode = it.second; dict, vector> edge_cond; for (auto &edge : dnode.edges) edge_cond[edge.first].push_back(edge.second); for (auto &it : edge_cond) { optimize_cond(it.second); for (auto &value : it.second) dnodes.at(it.first).nextstate.append(make_cond_eq(dnode.ctrl, value, dnode.statesig)); } if (accept_p) { vector accept_cond = dnode.accept; optimize_cond(accept_cond); for (auto &value : accept_cond) accept_sig.append(make_cond_eq(dnode.ctrl, value, dnode.statesig)); } if (reject_p) { vector reject_cond = dnode.reject; optimize_cond(reject_cond); for (auto &value : reject_cond) reject_sig.append(make_cond_eq(dnode.ctrl, value, dnode.statesig)); } } for (auto &it : dnodes) { SvaDFsmNode &dnode = it.second; if (GetSize(dnode.nextstate) == 0) { module->connect(dnode.ffoutwire, State::S0); } else if (GetSize(dnode.nextstate) == 1) { clocking.addDff(NEW_ID, dnode.nextstate, dnode.ffoutwire, State::S0); } else { SigSpec nextstate = module->ReduceOr(NEW_ID, dnode.nextstate); clocking.addDff(NEW_ID, nextstate, dnode.ffoutwire, State::S0); } } if (accept_p) { if (GetSize(accept_sig) == 0) final_accept_sig = State::S0; else if (GetSize(accept_sig) == 1) final_accept_sig = accept_sig; else final_accept_sig = module->ReduceOr(NEW_ID, accept_sig); *accept_p = final_accept_sig; } if (reject_p) { if (GetSize(reject_sig) == 0) final_reject_sig = State::S0; else if (GetSize(reject_sig) == 1) final_reject_sig = reject_sig; else final_reject_sig = module->ReduceOr(NEW_ID, reject_sig); *reject_p = final_reject_sig; } } SigBit getFirstAccept() { SigBit accept; getFirstAcceptReject(&accept, nullptr); return accept; } SigBit getReject() { SigBit reject; getFirstAcceptReject(nullptr, &reject); return reject; } void getDFsm(SvaFsm &output_fsm, int output_start_node, int output_accept_node, int output_reject_node = -1, bool firstmatch = true, bool condaccept = false) { log_assert(!materialized); materialized = true; // Create unlinked NFSM unodes.resize(GetSize(nodes)); for (int node = 0; node < GetSize(nodes); node++) node_to_unode(node, node, SigSpec()); mark_reachable_unode(startNode); // Create DFSM create_dnode(vector{startNode}, firstmatch, condaccept); dnodes.sort(); // Create DFSM Graph for (auto &it : dnodes) { SvaDFsmNode &dnode = it.second; dnode.outnode = output_fsm.createNode(); if (it.first == vector{startNode}) output_fsm.createLink(output_start_node, dnode.outnode); if (output_accept_node >= 0) { vector accept_cond = dnode.accept; optimize_cond(accept_cond); for (auto &value : accept_cond) output_fsm.createLink(it.second.outnode, output_accept_node, make_cond_eq(dnode.ctrl, value)); } if (output_reject_node >= 0) { vector reject_cond = dnode.reject; optimize_cond(reject_cond); for (auto &value : reject_cond) output_fsm.createLink(it.second.outnode, output_reject_node, make_cond_eq(dnode.ctrl, value)); } } for (auto &it : dnodes) { SvaDFsmNode &dnode = it.second; dict, vector> edge_cond; for (auto &edge : dnode.edges) edge_cond[edge.first].push_back(edge.second); for (auto &it : edge_cond) { optimize_cond(it.second); for (auto &value : it.second) output_fsm.createEdge(dnode.outnode, dnodes.at(it.first).outnode, make_cond_eq(dnode.ctrl, value)); } } } // ---------------------------------------------------- // State dump for verbose log messages void dump_nodes() { if (nodes.empty()) return; log(" non-deterministic encoding:\n"); for (int i = 0; i < GetSize(nodes); i++) { log(" node %d:%s\n", i, i == startNode ? " [start]" : i == acceptNode ? " [accept]" : i == condNode ? " [cond]" : ""); for (auto &it : nodes[i].edges) { if (it.second != State::S1) log(" egde %s -> %d\n", log_signal(it.second), it.first); else log(" egde -> %d\n", it.first); } for (auto &it : nodes[i].links) { if (it.second != State::S1) log(" link %s -> %d\n", log_signal(it.second), it.first); else log(" link -> %d\n", it.first); } } } void dump_unodes() { if (unodes.empty()) return; log(" unlinked non-deterministic encoding:\n"); for (int i = 0; i < GetSize(unodes); i++) { if (!unodes[i].reachable) continue; log(" unode %d:%s\n", i, i == startNode ? " [start]" : ""); for (auto &it : unodes[i].edges) { if (!it.second.empty()) log(" egde %s -> %d\n", log_signal(it.second), it.first); else log(" egde -> %d\n", it.first); } for (auto &ctrl : unodes[i].accept) { if (!ctrl.empty()) log(" accept %s\n", log_signal(ctrl)); else log(" accept\n"); } for (auto &ctrl : unodes[i].cond) { if (!ctrl.empty()) log(" cond %s\n", log_signal(ctrl)); else log(" cond\n"); } } } void dump_dnodes() { if (dnodes.empty()) return; log(" deterministic encoding:\n"); for (auto &it : dnodes) { log(" dnode {"); for (int i = 0; i < GetSize(it.first); i++) log("%s%d", i ? "," : "", it.first[i]); log("}:%s\n", GetSize(it.first) == 1 && it.first[0] == startNode ? " [start]" : ""); log(" ctrl %s\n", log_signal(it.second.ctrl)); for (auto &edge : it.second.edges) { log(" edge %s -> {", log_signal(edge.second)); for (int i = 0; i < GetSize(edge.first); i++) log("%s%d", i ? "," : "", edge.first[i]); log("}\n"); } for (auto &value : it.second.accept) log(" accept %s\n", log_signal(value)); for (auto &value : it.second.reject) log(" reject %s\n", log_signal(value)); } } void dump() { if (!nodes.empty()) log(" number of NFSM states: %d\n", GetSize(nodes)); if (!unodes.empty()) { int count = 0; for (auto &unode : unodes) if (unode.reachable) count++; log(" number of reachable UFSM states: %d\n", count); } if (!dnodes.empty()) log(" number of DFSM states: %d\n", GetSize(dnodes)); if (verific_verbose >= 2) { dump_nodes(); dump_unodes(); dump_dnodes(); } if (trigger_sig != State::S1) log(" trigger signal: %s\n", log_signal(trigger_sig)); if (final_accept_sig != State::Sx) log(" accept signal: %s\n", log_signal(final_accept_sig)); if (final_reject_sig != State::Sx) log(" reject signal: %s\n", log_signal(final_reject_sig)); } }; PRIVATE_NAMESPACE_END YOSYS_NAMESPACE_BEGIN pool verific_sva_prims = { // Copy&paste from Verific 3.16_484_32_170630 Netlist.h PRIM_SVA_IMMEDIATE_ASSERT, PRIM_SVA_ASSERT, PRIM_SVA_COVER, PRIM_SVA_ASSUME, PRIM_SVA_EXPECT, PRIM_SVA_POSEDGE, PRIM_SVA_NOT, PRIM_SVA_FIRST_MATCH, PRIM_SVA_ENDED, PRIM_SVA_MATCHED, PRIM_SVA_CONSECUTIVE_REPEAT, PRIM_SVA_NON_CONSECUTIVE_REPEAT, PRIM_SVA_GOTO_REPEAT, PRIM_SVA_MATCH_ITEM_TRIGGER, PRIM_SVA_AND, PRIM_SVA_OR, PRIM_SVA_SEQ_AND, PRIM_SVA_SEQ_OR, PRIM_SVA_EVENT_OR, PRIM_SVA_OVERLAPPED_IMPLICATION, PRIM_SVA_NON_OVERLAPPED_IMPLICATION, PRIM_SVA_OVERLAPPED_FOLLOWED_BY, PRIM_SVA_NON_OVERLAPPED_FOLLOWED_BY, PRIM_SVA_INTERSECT, PRIM_SVA_THROUGHOUT, PRIM_SVA_WITHIN, PRIM_SVA_AT, PRIM_SVA_DISABLE_IFF, PRIM_SVA_SAMPLED, PRIM_SVA_ROSE, PRIM_SVA_FELL, PRIM_SVA_STABLE, PRIM_SVA_PAST, PRIM_SVA_MATCH_ITEM_ASSIGN, PRIM_SVA_SEQ_CONCAT, PRIM_SVA_IF, PRIM_SVA_RESTRICT, PRIM_SVA_TRIGGERED, PRIM_SVA_STRONG, PRIM_SVA_WEAK, PRIM_SVA_NEXTTIME, PRIM_SVA_S_NEXTTIME, PRIM_SVA_ALWAYS, PRIM_SVA_S_ALWAYS, PRIM_SVA_S_EVENTUALLY, PRIM_SVA_EVENTUALLY, PRIM_SVA_UNTIL, PRIM_SVA_S_UNTIL, PRIM_SVA_UNTIL_WITH, PRIM_SVA_S_UNTIL_WITH, PRIM_SVA_IMPLIES, PRIM_SVA_IFF, PRIM_SVA_ACCEPT_ON, PRIM_SVA_REJECT_ON, PRIM_SVA_SYNC_ACCEPT_ON, PRIM_SVA_SYNC_REJECT_ON, PRIM_SVA_GLOBAL_CLOCKING_DEF, PRIM_SVA_GLOBAL_CLOCKING_REF, PRIM_SVA_IMMEDIATE_ASSUME, PRIM_SVA_IMMEDIATE_COVER, OPER_SVA_SAMPLED, OPER_SVA_STABLE }; struct VerificSvaImporter { VerificImporter *importer = nullptr; Module *module = nullptr; Netlist *netlist = nullptr; Instance *root = nullptr; VerificClocking clocking; bool mode_assert = false; bool mode_assume = false; bool mode_cover = false; bool mode_trigger = false; bool eventually = false; Instance *net_to_ast_driver(Net *n) { if (n == nullptr) return nullptr; if (n->IsMultipleDriven()) return nullptr; Instance *inst = n->Driver(); if (inst == nullptr) return nullptr; if (!verific_sva_prims.count(inst->Type())) return nullptr; if (inst->Type() == PRIM_SVA_ROSE || inst->Type() == PRIM_SVA_FELL || inst->Type() == PRIM_SVA_STABLE || inst->Type() == OPER_SVA_STABLE || inst->Type() == PRIM_SVA_PAST || inst->Type() == PRIM_SVA_TRIGGERED) return nullptr; return inst; } Instance *get_ast_input(Instance *inst) { return net_to_ast_driver(inst->GetInput()); } Instance *get_ast_input1(Instance *inst) { return net_to_ast_driver(inst->GetInput1()); } Instance *get_ast_input2(Instance *inst) { return net_to_ast_driver(inst->GetInput2()); } Instance *get_ast_input3(Instance *inst) { return net_to_ast_driver(inst->GetInput3()); } Instance *get_ast_control(Instance *inst) { return net_to_ast_driver(inst->GetControl()); } // ---------------------------------------------------------- // SVA Importer struct ParserErrorException { }; [[noreturn]] void parser_error(std::string errmsg) { if (!importer->mode_keep) log_error("%s", errmsg.c_str()); log_warning("%s", errmsg.c_str()); throw ParserErrorException(); } [[noreturn]] void parser_error(std::string errmsg, linefile_type loc) { parser_error(stringf("%s at %s:%d.\n", errmsg.c_str(), LineFile::GetFileName(loc), LineFile::GetLineNo(loc))); } [[noreturn]] void parser_error(std::string errmsg, Instance *inst) { parser_error(stringf("%s at %s (%s)", errmsg.c_str(), inst->View()->Owner()->Name(), inst->Name()), inst->Linefile()); } [[noreturn]] void parser_error(Instance *inst) { parser_error(stringf("Verific SVA primitive %s (%s) is currently unsupported in this context", inst->View()->Owner()->Name(), inst->Name()), inst->Linefile()); } dict check_expression_cache; bool check_expression(Net *net, bool raise_error = false) { while (!check_expression_cache.count(net)) { Instance *inst = net_to_ast_driver(net); if (inst == nullptr) { check_expression_cache[net] = true; break; } if (inst->Type() == PRIM_SVA_AT) { VerificClocking new_clocking(importer, net); log_assert(new_clocking.cond_net == nullptr); if (!clocking.property_matches_sequence(new_clocking)) parser_error("Mixed clocking is currently not supported", inst); check_expression_cache[net] = check_expression(new_clocking.body_net, raise_error); break; } if (inst->Type() == PRIM_SVA_FIRST_MATCH || inst->Type() == PRIM_SVA_NOT) { check_expression_cache[net] = check_expression(inst->GetInput(), raise_error); break; } if (inst->Type() == PRIM_SVA_SEQ_OR || inst->Type() == PRIM_SVA_SEQ_AND || inst->Type() == PRIM_SVA_INTERSECT || inst->Type() == PRIM_SVA_WITHIN || inst->Type() == PRIM_SVA_THROUGHOUT || inst->Type() == PRIM_SVA_OR || inst->Type() == PRIM_SVA_AND) { check_expression_cache[net] = check_expression(inst->GetInput1(), raise_error) && check_expression(inst->GetInput2(), raise_error); break; } if (inst->Type() == PRIM_SVA_SEQ_CONCAT) { const char *sva_low_s = inst->GetAttValue("sva:low"); const char *sva_high_s = inst->GetAttValue("sva:high"); int sva_low = atoi(sva_low_s); int sva_high = atoi(sva_high_s); bool sva_inf = !strcmp(sva_high_s, "$"); if (sva_low == 0 && sva_high == 0 && !sva_inf) check_expression_cache[net] = check_expression(inst->GetInput1(), raise_error) && check_expression(inst->GetInput2(), raise_error); else check_expression_cache[net] = false; break; } check_expression_cache[net] = false; } if (raise_error && !check_expression_cache.at(net)) parser_error(net_to_ast_driver(net)); return check_expression_cache.at(net); } SigBit parse_expression(Net *net) { check_expression(net, true); Instance *inst = net_to_ast_driver(net); if (inst == nullptr) { return importer->net_map_at(net); } if (inst->Type() == PRIM_SVA_AT) { VerificClocking new_clocking(importer, net); log_assert(new_clocking.cond_net == nullptr); if (!clocking.property_matches_sequence(new_clocking)) parser_error("Mixed clocking is currently not supported", inst); return parse_expression(new_clocking.body_net); } if (inst->Type() == PRIM_SVA_FIRST_MATCH) return parse_expression(inst->GetInput()); if (inst->Type() == PRIM_SVA_NOT) return module->Not(NEW_ID, parse_expression(inst->GetInput())); if (inst->Type() == PRIM_SVA_SEQ_OR || inst->Type() == PRIM_SVA_OR) return module->Or(NEW_ID, parse_expression(inst->GetInput1()), parse_expression(inst->GetInput2())); if (inst->Type() == PRIM_SVA_SEQ_AND || inst->Type() == PRIM_SVA_AND || inst->Type() == PRIM_SVA_INTERSECT || inst->Type() == PRIM_SVA_WITHIN || inst->Type() == PRIM_SVA_THROUGHOUT || inst->Type() == PRIM_SVA_SEQ_CONCAT) return module->And(NEW_ID, parse_expression(inst->GetInput1()), parse_expression(inst->GetInput2())); log_abort(); } int parse_sequence(SvaFsm &fsm, int start_node, Net *net) { if (check_expression(net)) { int node = fsm.createNode(); fsm.createLink(start_node, node, parse_expression(net)); return node; } Instance *inst = net_to_ast_driver(net); if (inst->Type() == PRIM_SVA_AT) { VerificClocking new_clocking(importer, net); log_assert(new_clocking.cond_net == nullptr); if (!clocking.property_matches_sequence(new_clocking)) parser_error("Mixed clocking is currently not supported", inst); return parse_sequence(fsm, start_node, new_clocking.body_net); } if (inst->Type() == PRIM_SVA_FIRST_MATCH) { SvaFsm match_fsm(clocking); match_fsm.createLink(parse_sequence(match_fsm, match_fsm.createStartNode(), inst->GetInput()), match_fsm.acceptNode); int node = fsm.createNode(); match_fsm.getDFsm(fsm, start_node, node); if (verific_verbose) { log(" First Match FSM:\n"); match_fsm.dump(); } return node; } if (inst->Type() == PRIM_SVA_SEQ_CONCAT) { const char *sva_low_s = inst->GetAttValue("sva:low"); const char *sva_high_s = inst->GetAttValue("sva:high"); int sva_low = atoi(sva_low_s); int sva_high = atoi(sva_high_s); bool sva_inf = !strcmp(sva_high_s, "$"); int node = parse_sequence(fsm, start_node, inst->GetInput1()); for (int i = 0; i < sva_low; i++) { int next_node = fsm.createNode(); fsm.createEdge(node, next_node); node = next_node; } if (sva_inf) { fsm.createEdge(node, node); } else { for (int i = sva_low; i < sva_high; i++) { int next_node = fsm.createNode(); fsm.createEdge(node, next_node); fsm.createLink(node, next_node); node = next_node; } } node = parse_sequence(fsm, node, inst->GetInput2()); return node; } if (inst->Type() == PRIM_SVA_CONSECUTIVE_REPEAT) { const char *sva_low_s = inst->GetAttValue("sva:low"); const char *sva_high_s = inst->GetAttValue("sva:high"); int sva_low = atoi(sva_low_s); int sva_high = atoi(sva_high_s); bool sva_inf = !strcmp(sva_high_s, "$"); Net *body_net = inst->GetInput(); int node = fsm.createNode(start_node); for (int i = 0; i < sva_low; i++) { int next_node = fsm.createNode(); if (i == 0) fsm.createLink(node, next_node); else fsm.createEdge(node, next_node); node = parse_sequence(fsm, next_node, body_net); } if (sva_inf) { int next_node = fsm.createNode(); fsm.createEdge(node, next_node); next_node = parse_sequence(fsm, next_node, body_net); fsm.createLink(next_node, node); } else { for (int i = sva_low; i < sva_high; i++) { int next_node = fsm.createNode(); if (i == 0) fsm.createLink(node, next_node); else fsm.createEdge(node, next_node); next_node = parse_sequence(fsm, next_node, body_net); fsm.createLink(node, next_node); node = next_node; } } return node; } if (inst->Type() == PRIM_SVA_NON_CONSECUTIVE_REPEAT || inst->Type() == PRIM_SVA_GOTO_REPEAT) { const char *sva_low_s = inst->GetAttValue("sva:low"); const char *sva_high_s = inst->GetAttValue("sva:high"); int sva_low = atoi(sva_low_s); int sva_high = atoi(sva_high_s); bool sva_inf = !strcmp(sva_high_s, "$"); Net *body_net = inst->GetInput(); int node = fsm.createNode(start_node); SigBit cond = parse_expression(body_net); SigBit not_cond = module->Not(NEW_ID, cond); for (int i = 0; i < sva_low; i++) { int wait_node = fsm.createNode(); fsm.createEdge(wait_node, wait_node, not_cond); if (i == 0) fsm.createLink(node, wait_node); else fsm.createEdge(node, wait_node); int next_node = fsm.createNode(); fsm.createLink(wait_node, next_node, cond); node = next_node; } if (sva_inf) { int wait_node = fsm.createNode(); fsm.createEdge(wait_node, wait_node, not_cond); fsm.createEdge(node, wait_node); fsm.createLink(wait_node, node, cond); } else { for (int i = sva_low; i < sva_high; i++) { int wait_node = fsm.createNode(); fsm.createEdge(wait_node, wait_node, not_cond); if (i == 0) fsm.createLink(node, wait_node); else fsm.createEdge(node, wait_node); int next_node = fsm.createNode(); fsm.createLink(wait_node, next_node, cond); fsm.createLink(node, next_node); node = next_node; } } if (inst->Type() == PRIM_SVA_NON_CONSECUTIVE_REPEAT) fsm.createEdge(node, node); return node; } if (inst->Type() == PRIM_SVA_SEQ_OR || inst->Type() == PRIM_SVA_OR) { int node = parse_sequence(fsm, start_node, inst->GetInput1()); int node2 = parse_sequence(fsm, start_node, inst->GetInput2()); fsm.createLink(node2, node); return node; } if (inst->Type() == PRIM_SVA_SEQ_AND || inst->Type() == PRIM_SVA_AND) { SvaFsm fsm1(clocking); fsm1.createLink(parse_sequence(fsm1, fsm1.createStartNode(), inst->GetInput1()), fsm1.acceptNode); SvaFsm fsm2(clocking); fsm2.createLink(parse_sequence(fsm2, fsm2.createStartNode(), inst->GetInput2()), fsm2.acceptNode); SvaFsm combined_fsm(clocking); fsm1.getDFsm(combined_fsm, combined_fsm.createStartNode(), -1, combined_fsm.acceptNode); fsm2.getDFsm(combined_fsm, combined_fsm.createStartNode(), -1, combined_fsm.acceptNode); int node = fsm.createNode(); combined_fsm.getDFsm(fsm, start_node, -1, node); if (verific_verbose) { log(" Left And FSM:\n"); fsm1.dump(); log(" Right And FSM:\n"); fsm1.dump(); log(" Combined And FSM:\n"); combined_fsm.dump(); } return node; } if (inst->Type() == PRIM_SVA_INTERSECT || inst->Type() == PRIM_SVA_WITHIN) { SvaFsm intersect_fsm(clocking); if (inst->Type() == PRIM_SVA_INTERSECT) { intersect_fsm.createLink(parse_sequence(intersect_fsm, intersect_fsm.createStartNode(), inst->GetInput1()), intersect_fsm.acceptNode); } else { int n = intersect_fsm.createNode(); intersect_fsm.createLink(intersect_fsm.createStartNode(), n); intersect_fsm.createEdge(n, n); n = parse_sequence(intersect_fsm, n, inst->GetInput1()); intersect_fsm.createLink(n, intersect_fsm.acceptNode); intersect_fsm.createEdge(n, n); } intersect_fsm.in_cond_mode = true; intersect_fsm.createLink(parse_sequence(intersect_fsm, intersect_fsm.createStartNode(), inst->GetInput2()), intersect_fsm.condNode); intersect_fsm.in_cond_mode = false; int node = fsm.createNode(); intersect_fsm.getDFsm(fsm, start_node, node, -1, false, true); if (verific_verbose) { log(" Intersect FSM:\n"); intersect_fsm.dump(); } return node; } if (inst->Type() == PRIM_SVA_THROUGHOUT) { SigBit expr = parse_expression(inst->GetInput1()); fsm.pushThroughout(expr); int node = parse_sequence(fsm, start_node, inst->GetInput2()); fsm.popThroughout(); return node; } parser_error(inst); } void get_fsm_accept_reject(SvaFsm &fsm, SigBit *accept_p, SigBit *reject_p, bool swap_accept_reject = false) { log_assert(accept_p != nullptr || reject_p != nullptr); if (swap_accept_reject) get_fsm_accept_reject(fsm, reject_p, accept_p); else if (reject_p == nullptr) *accept_p = fsm.getAccept(); else if (accept_p == nullptr) *reject_p = fsm.getReject(); else fsm.getFirstAcceptReject(accept_p, reject_p); } void parse_property(Net *net, SigBit *accept_p, SigBit *reject_p) { Instance *inst = net_to_ast_driver(net); SigBit trig = State::S1; if (clocking.cond_net != nullptr) trig = importer->net_map_at(clocking.cond_net); if (inst == nullptr) { log_assert(trig == State::S1); if (accept_p != nullptr) *accept_p = importer->net_map_at(net); if (reject_p != nullptr) *reject_p = module->Not(NEW_ID, importer->net_map_at(net)); } else if (inst->Type() == PRIM_SVA_OVERLAPPED_IMPLICATION || inst->Type() == PRIM_SVA_NON_OVERLAPPED_IMPLICATION) { Net *antecedent_net = inst->GetInput1(); Net *consequent_net = inst->GetInput2(); int node; SvaFsm antecedent_fsm(clocking, trig); node = parse_sequence(antecedent_fsm, antecedent_fsm.createStartNode(), antecedent_net); if (inst->Type() == PRIM_SVA_NON_OVERLAPPED_IMPLICATION) { int next_node = antecedent_fsm.createNode(); antecedent_fsm.createEdge(node, next_node); node = next_node; } Instance *consequent_inst = net_to_ast_driver(consequent_net); if (consequent_inst && (consequent_inst->Type() == PRIM_SVA_UNTIL || consequent_inst->Type() == PRIM_SVA_S_UNTIL || consequent_inst->Type() == PRIM_SVA_UNTIL_WITH || consequent_inst->Type() == PRIM_SVA_S_UNTIL_WITH)) { bool until_with = consequent_inst->Type() == PRIM_SVA_UNTIL_WITH || consequent_inst->Type() == PRIM_SVA_S_UNTIL_WITH; Net *until_net = consequent_inst->GetInput2(); consequent_net = consequent_inst->GetInput1(); consequent_inst = net_to_ast_driver(consequent_net); SigBit until_sig = parse_expression(until_net); SigBit not_until_sig = module->Not(NEW_ID, until_sig); antecedent_fsm.createEdge(node, node, not_until_sig); antecedent_fsm.createLink(node, antecedent_fsm.acceptNode, until_with ? State::S1 : not_until_sig); } else { antecedent_fsm.createLink(node, antecedent_fsm.acceptNode); } SigBit antecedent_match = antecedent_fsm.getAccept(); if (verific_verbose) { log(" Antecedent FSM:\n"); antecedent_fsm.dump(); } bool consequent_not = false; if (consequent_inst && consequent_inst->Type() == PRIM_SVA_NOT) { consequent_not = true; consequent_net = consequent_inst->GetInput(); consequent_inst = net_to_ast_driver(consequent_net); } SvaFsm consequent_fsm(clocking, antecedent_match); node = parse_sequence(consequent_fsm, consequent_fsm.createStartNode(), consequent_net); consequent_fsm.createLink(node, consequent_fsm.acceptNode); get_fsm_accept_reject(consequent_fsm, accept_p, reject_p, consequent_not); if (verific_verbose) { log(" Consequent FSM:\n"); consequent_fsm.dump(); } } else { bool prop_not = inst->Type() == PRIM_SVA_NOT; if (prop_not) { net = inst->GetInput(); inst = net_to_ast_driver(net); } SvaFsm fsm(clocking, trig); int node = parse_sequence(fsm, fsm.createStartNode(), net); fsm.createLink(node, fsm.acceptNode); get_fsm_accept_reject(fsm, accept_p, reject_p, prop_not); if (verific_verbose) { log(" Sequence FSM:\n"); fsm.dump(); } } } void import() { try { module = importer->module; netlist = root->Owner(); if (verific_verbose) log(" importing SVA property at root cell %s (%s) at %s:%d.\n", root->Name(), root->View()->Owner()->Name(), LineFile::GetFileName(root->Linefile()), LineFile::GetLineNo(root->Linefile())); RTLIL::IdString root_name = module->uniquify(importer->mode_names || root->IsUserDeclared() ? RTLIL::escape_id(root->Name()) : NEW_ID); // parse SVA sequence into trigger signal clocking = VerificClocking(importer, root->GetInput(), true); SigBit accept_bit = State::S0, reject_bit = State::S0; if (clocking.body_net == nullptr) { if (clocking.clock_net != nullptr || clocking.enable_net != nullptr || clocking.disable_net != nullptr || clocking.cond_net != nullptr) parser_error(stringf("Failed to parse SVA clocking"), root); if (mode_assert || mode_assume) { reject_bit = module->Not(NEW_ID, parse_expression(root->GetInput())); } else { accept_bit = parse_expression(root->GetInput()); } } else { if (mode_assert || mode_assume) { parse_property(clocking.body_net, nullptr, &reject_bit); } else { parse_property(clocking.body_net, &accept_bit, nullptr); } } if (mode_trigger) { module->connect(importer->net_map_at(root->GetOutput()), accept_bit); } else { SigBit sig_a = module->Not(NEW_ID, reject_bit); SigBit sig_en = module->Or(NEW_ID, accept_bit, reject_bit); // add final FF stage SigBit sig_a_q, sig_en_q; if (clocking.body_net == nullptr) { sig_a_q = sig_a; sig_en_q = sig_en; } else { sig_a_q = module->addWire(NEW_ID); sig_en_q = module->addWire(NEW_ID); clocking.addDff(NEW_ID, sig_a, sig_a_q, State::S0); clocking.addDff(NEW_ID, sig_en, sig_en_q, State::S0); } // generate assert/assume/cover cell RTLIL::Cell *c = nullptr; if (mode_assert) c = module->addAssert(root_name, sig_a_q, sig_en_q); if (mode_assume) c = module->addAssume(root_name, sig_a_q, sig_en_q); if (mode_cover) c = module->addCover(root_name, sig_a_q, sig_en_q); importer->import_attributes(c->attributes, root); } } catch (ParserErrorException) { } } }; void verific_import_sva_assert(VerificImporter *importer, Instance *inst) { VerificSvaImporter worker; worker.importer = importer; worker.root = inst; worker.mode_assert = true; worker.import(); } void verific_import_sva_assume(VerificImporter *importer, Instance *inst) { VerificSvaImporter worker; worker.importer = importer; worker.root = inst; worker.mode_assume = true; worker.import(); } void verific_import_sva_cover(VerificImporter *importer, Instance *inst) { VerificSvaImporter worker; worker.importer = importer; worker.root = inst; worker.mode_cover = true; worker.import(); } void verific_import_sva_trigger(VerificImporter *importer, Instance *inst) { VerificSvaImporter worker; worker.importer = importer; worker.root = inst; worker.mode_trigger = true; worker.import(); } bool verific_is_sva_net(VerificImporter *importer, Verific::Net *net) { VerificSvaImporter worker; worker.importer = importer; return worker.net_to_ast_driver(net) != nullptr; } YOSYS_NAMESPACE_END