/* * Copyright (c) 2002-2016 Stephen Williams (steve@icarus.com) * * This source code is free software; you can redistribute it * and/or modify it in source code form under the terms of the GNU * General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ # include "config.h" # include "functor.h" # include "netlist.h" # include "netvector.h" # include "netmisc.h" # include "compiler.h" # include "ivl_assert.h" using namespace std; static void qualify_enable(Design*des, NetScope*scope, NetNet*qualifier, bool active_state, NetLogic::TYPE gate_type, Link&enable_i, Link&enable_o) { if (enable_i.is_linked(scope->tie_lo())) { connect(enable_o, scope->tie_lo()); return; } if (active_state == false) { NetLogic*gate = new NetLogic(scope, scope->local_symbol(), 2, NetLogic::NOT, 1); des->add_node(gate); connect(gate->pin(1), qualifier->pin(0)); NetNet*sig = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, &netvector_t::scalar_logic); sig->local_flag(true); connect(sig->pin(0), gate->pin(0)); qualifier = sig; } if (enable_i.is_linked(scope->tie_hi())) { connect(enable_o, qualifier->pin(0)); return; } NetLogic*gate = new NetLogic(scope, scope->local_symbol(), 3, gate_type, 1); des->add_node(gate); connect(gate->pin(1), qualifier->pin(0)); connect(gate->pin(2), enable_i); connect(enable_o, gate->pin(0)); NetNet*sig = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, &netvector_t::scalar_logic); sig->local_flag(true); connect(sig->pin(0), gate->pin(0)); } static void multiplex_enables(Design*des, NetScope*scope, NetNet*select, Link&enable_1, Link&enable_0, Link&enable_o) { if (!enable_1.is_linked() && !enable_0.is_linked() ) return; if ( enable_1.is_linked(scope->tie_hi()) && enable_0.is_linked(scope->tie_hi()) ) { connect(enable_o, scope->tie_hi()); return; } if (enable_1.is_linked(scope->tie_lo()) || !enable_1.is_linked()) { qualify_enable(des, scope, select, false, NetLogic::AND, enable_0, enable_o); return; } if (enable_0.is_linked(scope->tie_lo()) || !enable_0.is_linked()) { qualify_enable(des, scope, select, true, NetLogic::AND, enable_1, enable_o); return; } if (enable_1.is_linked(scope->tie_hi())) { qualify_enable(des, scope, select, true, NetLogic::OR, enable_0, enable_o); return; } if (enable_0.is_linked(scope->tie_hi())) { qualify_enable(des, scope, select, false, NetLogic::OR, enable_1, enable_o); return; } NetMux*mux = new NetMux(scope, scope->local_symbol(), 1, 2, 1); des->add_node(mux); connect(mux->pin_Sel(), select->pin(0)); connect(mux->pin_Data(1), enable_1); connect(mux->pin_Data(0), enable_0); connect(enable_o, mux->pin_Result()); NetNet*sig = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, &netvector_t::scalar_logic); sig->local_flag(true); connect(sig->pin(0), mux->pin_Result()); } static void merge_sequential_enables(Design*des, NetScope*scope, Link&top_enable, Link&sub_enable) { if (!sub_enable.is_linked()) return; if (top_enable.is_linked(scope->tie_hi())) return; if (sub_enable.is_linked(scope->tie_hi())) top_enable.unlink(); if (top_enable.is_linked()) { NetLogic*gate = new NetLogic(scope, scope->local_symbol(), 3, NetLogic::OR, 1); des->add_node(gate); connect(gate->pin(1), sub_enable); connect(gate->pin(2), top_enable); top_enable.unlink(); connect(top_enable, gate->pin(0)); NetNet*sig = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, &netvector_t::scalar_logic); sig->local_flag(true); connect(sig->pin(0), gate->pin(0)); } else { connect(top_enable, sub_enable); } } static void merge_sequential_masks(NetProc::mask_t&top_mask, NetProc::mask_t&sub_mask) { if (sub_mask.size() == 0) return; if (top_mask.size() == 0) { top_mask = sub_mask; return; } assert(top_mask.size() == sub_mask.size()); for (unsigned idx = 0 ; idx < top_mask.size() ; idx += 1) { if (sub_mask[idx] == true) top_mask[idx] = true; } } static void merge_parallel_masks(NetProc::mask_t&top_mask, NetProc::mask_t&sub_mask) { if (top_mask.size() == 0) return; if (sub_mask.size() == 0) { top_mask = sub_mask; return; } assert(top_mask.size() == sub_mask.size()); for (unsigned idx = 0 ; idx < top_mask.size() ; idx += 1) { if (sub_mask[idx] == false) top_mask[idx] = false; } } static bool all_bits_driven(NetProc::mask_t&mask) { if (mask.size() == 0) return false; for (unsigned idx = 0 ; idx < mask.size() ; idx += 1) { if (mask[idx] == false) return false; } return true; } bool NetProcTop::tie_off_floating_inputs_(Design*des, NexusSet&nex_map, NetBus&nex_in, vector&bitmasks, bool is_ff_input) { bool flag = true; for (unsigned idx = 0 ; idx < nex_in.pin_count() ; idx += 1) { if (nex_in.pin(idx).nexus()->has_floating_input()) { if (all_bits_driven(bitmasks[idx])) { // If all bits are unconditionally driven, we can // use the enable signal to prevent the flip-flop/ // latch from updating when an undriven mux input // is selected, so we can just tie off the input. unsigned width = nex_map[idx].wid; NetLogic*gate = new NetLogic(scope(), scope()->local_symbol(), 1, NetLogic::PULLDOWN, width); des->add_node(gate); connect(nex_in.pin(idx), gate->pin(0)); if (nex_in.pin(idx).nexus()->pick_any_net()) continue; ivl_variable_type_t data_type = IVL_VT_LOGIC; netvector_t*tmp_vec = new netvector_t(data_type, width-1,0); NetNet*sig = new NetNet(scope(), scope()->local_symbol(), NetNet::WIRE, tmp_vec); sig->local_flag(true); connect(sig->pin(0), gate->pin(0)); } else if (is_ff_input) { // For a flip-flop, we can feed back the output // to ensure undriven bits hold their last value. connect(nex_in.pin(idx), nex_map[idx].lnk); } else { // This infers a latch, but without generating // gate enable signals at the bit-level, we // can't safely latch the undriven bits (we // shouldn't generate combinatorial loops). cerr << get_fileline() << ": warning: A latch " << "has been inferred for some bits of '" << nex_map[idx].lnk.nexus()->pick_any_net()->name() << "'." << endl; cerr << get_fileline() << ": sorry: Bit-level " "latch gate enables are not currently " "supported in synthesis." << endl; des->errors += 1; flag = false; } } } return flag; } bool NetProc::synth_async(Design*, NetScope*, NexusSet&, NetBus&, NetBus&, vector&) { return false; } /* * Async synthesis of assignments is done by synthesizing the rvalue * expression, then connecting the l-value directly to the output of * the r-value. * * The nex_map is the O-set for the statement, and lists the positions * of the outputs as the caller wants results linked up. The nex_out, * however, is the set of nexa that are to actually get linked to the * r-value. */ bool NetAssignBase::synth_async(Design*des, NetScope*scope, NexusSet&nex_map, NetBus&nex_out, NetBus&enables, vector&bitmasks) { /* If the lval is a concatenation, synthesise each part separately. */ if (lval_->more ) { /* Temporarily set the lval_ and rval_ fields for each part in turn and recurse. Restore them when done. */ NetAssign_*full_lval = lval_; NetExpr*full_rval = rval_; unsigned offset = 0; bool flag = true; while (lval_) { unsigned width = lval_->lwidth(); NetEConst*base = new NetEConst(verinum(offset)); base->set_line(*this); rval_ = new NetESelect(full_rval->dup_expr(), base, width); rval_->set_line(*this); eval_expr(rval_, width); NetAssign_*more = lval_->more; lval_->more = 0; if (!synth_async(des, scope, nex_map, nex_out, enables, bitmasks)) flag = false; lval_ = lval_->more = more; offset += width; } lval_ = full_lval; rval_ = full_rval; return flag; } NetNet*rsig = rval_->synthesize(des, scope, rval_); assert(rsig); if (lval_->word() && ! dynamic_cast(lval_->word())) { cerr << get_fileline() << ": sorry: Assignment to variable " "location in memory is not currently supported in " "synthesis." << endl; des->errors += 1; return false; } NetNet*lsig = lval_->sig(); if (!lsig) { cerr << get_fileline() << ": error: " "NetAssignBase::synth_async on unsupported lval "; dump_lval(cerr); cerr << endl; des->errors += 1; return false; } if (debug_synth2) { cerr << get_fileline() << ": NetAssignBase::synth_async: " << "l-value signal is " << lsig->vector_width() << " bits, " << "r-value signal is " << rsig->vector_width() << " bits." << endl; cerr << get_fileline() << ": NetAssignBase::synth_async: " << "lval_->lwidth()=" << lval_->lwidth() << endl; cerr << get_fileline() << ": NetAssignBase::synth_async: " << "lsig = " << scope_path(scope) << "." << lsig->name() << endl; if (const NetExpr*base = lval_->get_base()) { cerr << get_fileline() << ": NetAssignBase::synth_async: " << "base_=" << *base << endl; } cerr << get_fileline() << ": NetAssignBase::synth_async: " << "nex_map.size()==" << nex_map.size() << ", nex_out.pin_count()==" << nex_out.pin_count() << endl; } unsigned ptr = 0; if (nex_out.pin_count() > 1) { NexusSet tmp_set; nex_output(tmp_set); ivl_assert(*this, tmp_set.size() == 1); ptr = nex_map.find_nexus(tmp_set[0]); ivl_assert(*this, nex_out.pin_count() > ptr); ivl_assert(*this, enables.pin_count() > ptr); ivl_assert(*this, bitmasks.size() > ptr); } else { ivl_assert(*this, nex_out.pin_count() == 1); ivl_assert(*this, enables.pin_count() == 1); ivl_assert(*this, bitmasks.size() == 1); } unsigned lval_width = lval_->lwidth(); unsigned lsig_width = lsig->vector_width(); ivl_assert(*this, nex_map[ptr].wid == lsig_width); // Here we note if the l-value is actually a bit/part // select. If so, generate a NetPartSelect to perform the select. bool is_part_select = lval_width != lsig_width; long base_off = 0; if (is_part_select && !scope->loop_index_tmp.empty()) { // If we are within a NetForLoop, there may be an index // value. That is collected from the scope member // loop_index_tmp, and the evaluate_function method // knows how to apply it. ivl_assert(*this, !scope->loop_index_tmp.empty()); ivl_assert(*this, lval_width < lsig_width); // Evaluate the index expression to a constant. const NetExpr*base_expr_raw = lval_->get_base(); ivl_assert(*this, base_expr_raw); NetExpr*base_expr = base_expr_raw->evaluate_function(*this, scope->loop_index_tmp); if (! eval_as_long(base_off, base_expr)) { ivl_assert(*this, 0); } ivl_assert(*this, base_off >= 0); ivl_variable_type_t tmp_data_type = rsig->data_type(); netvector_t*tmp_type = new netvector_t(tmp_data_type, lsig_width-1,0); NetNet*tmp = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, NetNet::not_an_array, tmp_type); tmp->local_flag(true); tmp->set_line(*this); NetPartSelect*ps = new NetPartSelect(tmp, base_off, lval_width, NetPartSelect::PV); ps->set_line(*this); des->add_node(ps); connect(ps->pin(0), rsig->pin(0)); rsig = tmp; } else if (is_part_select) { // In this case, there is no loop_index_tmp, so we are // not within a NetForLoop. Generate a NetSubstitute // object to handle the bit/part-select in the l-value. ivl_assert(*this, scope->loop_index_tmp.empty()); ivl_assert(*this, lval_width < lsig_width); const NetExpr*base_expr_raw = lval_->get_base(); ivl_assert(*this, base_expr_raw); NetExpr*base_expr = base_expr_raw->evaluate_function(*this, scope->loop_index_tmp); if (! eval_as_long(base_off, base_expr)) { cerr << get_fileline() << ": sorry: assignment to variable " "bit location is not currently supported in " "synthesis." << endl; des->errors += 1; return false; } ivl_assert(*this, base_off >= 0); ivl_variable_type_t tmp_data_type = rsig->data_type(); netvector_t*tmp_type = new netvector_t(tmp_data_type, lsig_width-1,0); NetNet*tmp = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, NetNet::not_an_array, tmp_type); tmp->local_flag(true); tmp->set_line(*this); NetNet*isig = nex_out.pin(ptr).nexus()->pick_any_net(); if (isig) { if (debug_synth2) { cerr << get_fileline() << ": NetAssignBase::synth_async: " << " Found an isig:" << endl; nex_out.pin(ptr).dump_link(cerr, 8); } } else { if (debug_synth2) { cerr << get_fileline() << ": NetAssignBase::synth_async: " << " Found no isig, resorting to lsig." << endl; } isig = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, NetNet::not_an_array, tmp_type); isig->local_flag(true); isig->set_line(*this); connect(isig->pin(0), nex_out.pin(ptr)); } ivl_assert(*this, isig); NetSubstitute*ps = new NetSubstitute(isig, rsig, lsig_width, base_off); ps->set_line(*this); des->add_node(ps); connect(ps->pin(0), tmp->pin(0)); rsig = tmp; } rsig = crop_to_width(des, rsig, lsig_width); ivl_assert(*this, rsig->pin_count()==1); nex_out.pin(ptr).unlink(); enables.pin(ptr).unlink(); connect(nex_out.pin(ptr), rsig->pin(0)); connect(enables.pin(ptr), scope->tie_hi()); mask_t&bitmask = bitmasks[ptr]; if (is_part_select) { if (bitmask.size() == 0) { bitmask = mask_t (lsig_width, false); } ivl_assert(*this, bitmask.size() == lsig_width); for (unsigned idx = 0; idx < lval_width; idx += 1) { bitmask[base_off + idx] = true; } } else if (bitmask.size() > 0) { for (unsigned idx = 0; idx < bitmask.size(); idx += 1) { bitmask[idx] = true; } } else { bitmask = mask_t (lsig_width, true); } /* This lval_ represents a reg that is a WIRE in the synthesized results. This function signals the destructor to change the REG that this l-value refers to into a WIRE. It is done then, at the last minute, so that pending synthesis can continue to work with it as a REG. */ lval_->turn_sig_to_wire_on_release(); return true; } bool NetProc::synth_async_block_substatement_(Design*des, NetScope*scope, NexusSet&nex_map, NetBus&nex_out, NetBus&enables, vector&bitmasks, NetProc*substmt) { ivl_assert(*this, nex_map.size() == nex_out.pin_count()); ivl_assert(*this, nex_map.size() == enables.pin_count()); ivl_assert(*this, nex_map.size() == bitmasks.size()); // Create a temporary map of the output only from this statement. NexusSet tmp_map; substmt->nex_output(tmp_map); if (debug_synth2) { cerr << get_fileline() << ": NetProc::synth_async_block_substatement_: " << "tmp_map.size()==" << tmp_map.size() << " for statement at " << substmt->get_fileline() << endl; for (unsigned idx = 0 ; idx < nex_out.pin_count() ; idx += 1) { cerr << get_fileline() << ": NetProc::synth_async_block_substatement_: " << "incoming nex_out[" << idx << "] dump link" << endl; nex_out.pin(idx).dump_link(cerr, 8); } } // Create temporary variables to collect the output from the synthesis. NetBus tmp_out (scope, tmp_map.size()); NetBus tmp_ena (scope, tmp_map.size()); vector tmp_masks (tmp_map.size()); // Map (and move) the accumulated nex_out for this block // to the version that we can pass to the next statement. // We will move the result back later. for (unsigned idx = 0 ; idx < tmp_out.pin_count() ; idx += 1) { unsigned ptr = nex_map.find_nexus(tmp_map[idx]); ivl_assert(*this, ptr < nex_out.pin_count()); connect(tmp_out.pin(idx), nex_out.pin(ptr)); nex_out.pin(ptr).unlink(); } if (debug_synth2) { for (unsigned idx = 0 ; idx < nex_map.size() ; idx += 1) { cerr << get_fileline() << ": NetProc::synth_async_block_substatement_: nex_map[" << idx << "] dump link, base=" << nex_map[idx].base << ", wid=" << nex_map[idx].wid << endl; nex_map[idx].lnk.dump_link(cerr, 8); } for (unsigned idx = 0 ; idx < tmp_map.size() ; idx += 1) { cerr << get_fileline() << ": NetProc::synth_async_block_substatement_: tmp_map[" << idx << "] dump link, base=" << tmp_map[idx].base << ", wid=" << tmp_map[idx].wid << endl; tmp_map[idx].lnk.dump_link(cerr, 8); } for (unsigned idx = 0 ; idx < tmp_out.pin_count() ; idx += 1) { cerr << get_fileline() << ": NetProc::synth_async_block_substatement_: tmp_out[" << idx << "] dump link" << endl; tmp_out.pin(idx).dump_link(cerr, 8); } } bool flag = substmt->synth_async(des, scope, tmp_map, tmp_out, tmp_ena, tmp_masks); if (debug_synth2) { cerr << get_fileline() << ": NetProc::synth_async_block_substatement_: " "substmt->synch_async(...) --> " << (flag? "true" : "false") << " for statement at " << substmt->get_fileline() << "." << endl; } if (!flag) return false; // Now map the output from the substatement back to the // outputs for this block. for (unsigned idx = 0 ; idx < tmp_out.pin_count() ; idx += 1) { unsigned ptr = nex_map.find_nexus(tmp_map[idx]); ivl_assert(*this, ptr < nex_out.pin_count()); if (debug_synth2) { cerr << get_fileline() << ": NetProc::synth_async_block_substatement_: " << "tmp_out.pin(" << idx << "):" << endl; tmp_out.pin(idx).dump_link(cerr, 8); } connect(nex_out.pin(ptr), tmp_out.pin(idx)); merge_sequential_enables(des, scope, enables.pin(ptr), tmp_ena.pin(idx)); merge_sequential_masks(bitmasks[ptr], tmp_masks[idx]); } return true; } /* * Sequential blocks are translated to asynchronous logic by * translating each statement of the block, in order, into gates. * The nex_out for the block is the union of the nex_out for all * the substatements. */ bool NetBlock::synth_async(Design*des, NetScope*scope, NexusSet&nex_map, NetBus&nex_out, NetBus&enables, vector&bitmasks) { if (last_ == 0) { return true; } bool flag = true; NetProc*cur = last_; do { cur = cur->next_; bool sub_flag = synth_async_block_substatement_(des, scope, nex_map, nex_out, enables, bitmasks, cur); flag = flag && sub_flag; } while (cur != last_); return flag; } /* * This function is used to fix up a MUX selector to be no longer than * it needs to be. The general idea is that if the selector needs to * be only N bits, but is actually M bits, we translate it to this: * * osig = { |esig[M-1:N-1], esig[N-2:0] } * * This obviously implies that (N >= 2) and (M >= N). In the code * below, N is sel_need, and M is sel_got (= esig->vector_width()). */ static NetNet* mux_selector_reduce_width(Design*des, NetScope*scope, const LineInfo&loc, NetNet*esig, unsigned sel_need) { const unsigned sel_got = esig->vector_width(); ivl_assert(*esig, sel_got >= sel_need); // If the actual width matches the desired width (M==N) then // osig is esig itself. We're done. if (sel_got == sel_need) return esig; if (debug_synth2) { cerr << loc.get_fileline() << ": mux_selector_reduce_width: " << "Reduce selector width=" << sel_got << " to " << sel_need << " bits." << endl; } ivl_assert(*esig, sel_need >= 2); // This is the output signal, osig. ivl_variable_type_t osig_data_type = IVL_VT_LOGIC; netvector_t*osig_vec = new netvector_t(osig_data_type, sel_need-1, 0); NetNet*osig = new NetNet(scope, scope->local_symbol(), NetNet::TRI, osig_vec); osig->local_flag(true); osig->set_line(loc); // Create the concat: osig = {...,...} NetConcat*osig_cat = new NetConcat(scope, scope->local_symbol(), sel_need, 2, true); osig_cat->set_line(loc); des->add_node(osig_cat); connect(osig_cat->pin(0), osig->pin(0)); // Create the part select esig[N-2:0]... NetPartSelect*ps0 = new NetPartSelect(esig, 0, sel_need-1, NetPartSelect::VP); ps0->set_line(loc); des->add_node(ps0); connect(ps0->pin(1), esig->pin(0)); netvector_t*ps0_vec = new netvector_t(osig_data_type, sel_need-2, 0); NetNet*ps0_sig = new NetNet(scope, scope->local_symbol(), NetNet::TRI, ps0_vec); ps0_sig->local_flag(true); ps0_sig->set_line(loc); connect(ps0_sig->pin(0), ps0->pin(0)); // osig = {..., esig[N-2:0]} connect(osig_cat->pin(1), ps0_sig->pin(0)); // Create the part select esig[M-1:N-1] NetPartSelect*ps1 = new NetPartSelect(esig, sel_need-1, sel_got-sel_need+1, NetPartSelect::VP); ps1->set_line(loc); des->add_node(ps1); connect(ps1->pin(1), esig->pin(0)); netvector_t*ps1_vec = new netvector_t(osig_data_type, sel_got-sel_need, 0); NetNet*ps1_sig = new NetNet(scope, scope->local_symbol(), NetNet::TRI, ps1_vec); ps1_sig->local_flag(true); ps1_sig->set_line(loc); connect(ps1_sig->pin(0), ps1->pin(0)); // Create the reduction OR: | esig[M-1:N-1] NetUReduce*ered = new NetUReduce(scope, scope->local_symbol(), NetUReduce::OR, sel_got-sel_need+1); ered->set_line(loc); des->add_node(ered); connect(ered->pin(1), ps1_sig->pin(0)); NetNet*ered_sig = new NetNet(scope, scope->local_symbol(), NetNet::TRI, &netvector_t::scalar_logic); ered_sig->local_flag(true); ered_sig->set_line(loc); connect(ered->pin(0), ered_sig->pin(0)); // osig = { |esig[M-1:N-1], esig[N-2:0] } connect(osig_cat->pin(2), ered_sig->pin(0)); return osig; } bool NetCase::synth_async(Design*des, NetScope*scope, NexusSet&nex_map, NetBus&nex_out, NetBus&enables, vector&bitmasks) { if (type()==NetCase::EQZ || type()==NetCase::EQX) return synth_async_casez_(des, scope, nex_map, nex_out, enables, bitmasks); // Special case: If the case expression is constant, then this // is a pattern where the guards are non-constant and tested // against a constant case. Handle this as chained conditions // instead. if (dynamic_cast (expr_)) return synth_async_casez_(des, scope, nex_map, nex_out, enables, bitmasks); ivl_assert(*this, nex_map.size() == nex_out.pin_count()); ivl_assert(*this, nex_map.size() == enables.pin_count()); ivl_assert(*this, nex_map.size() == bitmasks.size()); if (debug_synth2) { cerr << get_fileline() << ": NetCase::synth_async: " << "Selector expression: " << *expr_ << endl; } /* Synthesize the select expression. */ NetNet*esig = expr_->synthesize(des, scope, expr_); unsigned sel_width = esig->vector_width(); ivl_assert(*this, sel_width > 0); if (debug_synth2) { cerr << get_fileline() << ": NetCase::synth_async: " << "selector width (sel_width) = " << sel_width << endl; } vector mux_width (nex_out.pin_count()); for (unsigned idx = 0 ; idx < nex_out.pin_count() ; idx += 1) { mux_width[idx] = nex_map[idx].wid; if (debug_synth2) { cerr << get_fileline() << ": NetCase::synth_async: " << "idx=" << idx << ", mux_width[idx]=" << mux_width[idx] << endl; } } // The incoming nex_out is taken as the input for this // statement. Since there are collection of statements // that start at this same point, we save all these // inputs and reuse them for each statement. Unlink the // nex_out now, so we can hook up the mux outputs. NetBus statement_input (scope, nex_out.pin_count()); for (unsigned idx = 0 ; idx < nex_out.pin_count() ; idx += 1) { connect(statement_input.pin(idx), nex_out.pin(idx)); nex_out.pin(idx).unlink(); if (debug_synth2) { cerr << get_fileline() << ": NetCase::synth_async: " << "statement_input.pin(" << idx << "):" << endl; statement_input.pin(idx).dump_link(cerr, 8); } } /* Collect all the statements into a map of index to statement. The guard expression it evaluated to be the index of the mux value, and the statement is bound to that index. */ unsigned long max_guard_value = 0; mapstatement_map; NetProc*default_statement = 0; for (size_t item = 0 ; item < items_.size() ; item += 1) { if (items_[item].guard == 0) { default_statement = items_[item].statement; continue; } NetEConst*ge = dynamic_cast(items_[item].guard); if (ge == 0) { cerr << items_[item].guard->get_fileline() << ": sorry: " << "variable case item expressions with a variable " << "case select expression are not supported in " << "synthesis. " << endl; des->errors += 1; return false; } ivl_assert(*this, ge); verinum gval = ge->value(); unsigned long sel_idx = gval.as_ulong(); if (statement_map[sel_idx]) { cerr << ge->get_fileline() << ": warning: duplicate case " << "value '" << sel_idx << "' detected. This case is " << "unreachable." << endl; delete items_[item].statement; items_[item].statement = 0; continue; } if (sel_idx > max_guard_value) max_guard_value = sel_idx; if (items_[item].statement) { statement_map[sel_idx] = items_[item].statement; continue; } // Handle the special case of an empty statement. statement_map[sel_idx] = this; } // The minimum selector width is the number of inputs that // are selected, rounded up to the nearest power of 2. unsigned sel_need = ceil(log2(max_guard_value + 1)); // If the sel_width can select more than just the explicit // guard values, and there is a default statement, then adjust // the sel_need to allow for the implicit selections. if (default_statement && (sel_width > sel_need)) sel_need += 1; // The mux size is always an exact power of 2. if (sel_need >= 8*sizeof(unsigned)) { cerr << get_fileline() << ": sorry: mux select width of " << sel_need << " bits is too large for synthesis." << endl; des->errors += 1; return false; } unsigned mux_size = 1U << sel_need; if (debug_synth2) { cerr << get_fileline() << ": NetCase::synth_async: " << "Adjusted mux_size is " << mux_size << " (max_guard_value=" << max_guard_value << ", sel_need=" << sel_need << ", sel_width=" << sel_width << ")." << endl; } if (sel_width > sel_need) { if (debug_synth2) { cerr << get_fileline() << ": NetCase::synth_async: " << "Selector is " << sel_width << " bits, " << "need only " << sel_need << " bits." << endl; } esig = mux_selector_reduce_width(des, scope, *this, esig, sel_need); } /* If there is a default clause, synthesize it once and we'll link it in wherever it is needed. If there isn't, create a dummy default to pass on the accumulated nex_out from preceding statements. */ NetBus default_out (scope, nex_out.pin_count()); NetBus default_ena (scope, nex_out.pin_count()); vector default_masks (nex_out.pin_count()); for (unsigned idx = 0 ; idx < nex_out.pin_count() ; idx += 1) { connect(default_out.pin(idx), statement_input.pin(idx)); connect(default_ena.pin(idx), scope->tie_lo()); } if (default_statement) { bool flag = synth_async_block_substatement_(des, scope, nex_map, default_out, default_ena, default_masks, default_statement); if (!flag) return false; if (debug_synth2) { cerr << get_fileline() << ": NetCase::synth_async: " << "synthesize default clause at " << default_statement->get_fileline() << " is done." << endl; } } vector out_mux (nex_out.pin_count()); vector ena_mux (nex_out.pin_count()); vector full_case (nex_out.pin_count()); for (size_t mdx = 0 ; mdx < nex_out.pin_count() ; mdx += 1) { out_mux[mdx] = new NetMux(scope, scope->local_symbol(), mux_width[mdx], mux_size, sel_need); des->add_node(out_mux[mdx]); // The select signal is already synthesized, and is // common for every mux of this case statement. Simply // hook it up. connect(out_mux[mdx]->pin_Sel(), esig->pin(0)); // The outputs are in the nex_out, and connected to the // mux Result pins. connect(out_mux[mdx]->pin_Result(), nex_out.pin(mdx)); // Make sure the output is now connected to a net. If // not, then create a fake one to carry the net-ness of // the pin. if (out_mux[mdx]->pin_Result().nexus()->pick_any_net() == 0) { ivl_variable_type_t mux_data_type = IVL_VT_LOGIC; netvector_t*tmp_vec = new netvector_t(mux_data_type, mux_width[mdx]-1,0); NetNet*tmp = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, tmp_vec); tmp->local_flag(true); ivl_assert(*this, tmp->vector_width() != 0); connect(out_mux[mdx]->pin_Result(), tmp->pin(0)); } // Create a mux for the enables, but don't hook it up // until we know we need it. ena_mux[mdx] = new NetMux(scope, scope->local_symbol(), 1, mux_size, sel_need); // Initialise the bitmasks appropriately for calculating // the intersection of the individual clause bitmasks. bitmasks[mdx] = mask_t (mux_width[mdx], true); // Assume a full case to start with. We'll check this as // we synthesise each clause. full_case[mdx] = true; } for (unsigned idx = 0 ; idx < mux_size ; idx += 1) { NetProc*stmt = statement_map[idx]; if (stmt==0) { ivl_assert(*this, default_out.pin_count() == out_mux.size()); for (unsigned mdx = 0 ; mdx < nex_out.pin_count() ; mdx += 1) { connect(out_mux[mdx]->pin_Data(idx), default_out.pin(mdx)); connect(ena_mux[mdx]->pin_Data(idx), default_ena.pin(mdx)); merge_parallel_masks(bitmasks[mdx], default_masks[mdx]); if (!default_ena.pin(mdx).is_linked(scope->tie_hi())) full_case[mdx] = false; } continue; } ivl_assert(*this, stmt); if (stmt == this) { // Handle the special case of an empty statement. ivl_assert(*this, statement_input.pin_count() == out_mux.size()); for (unsigned mdx = 0 ; mdx < nex_out.pin_count() ; mdx += 1) { connect(out_mux[mdx]->pin_Data(idx), statement_input.pin(mdx)); connect(ena_mux[mdx]->pin_Data(idx), scope->tie_lo()); bitmasks[mdx] = mask_t (mux_width[mdx], false); full_case[mdx] = false; } continue; } NetBus tmp_out (scope, nex_out.pin_count()); NetBus tmp_ena (scope, nex_out.pin_count()); for (unsigned mdx = 0 ; mdx < nex_out.pin_count() ; mdx += 1) { connect(tmp_out.pin(mdx), statement_input.pin(mdx)); connect(tmp_ena.pin(mdx), scope->tie_lo()); } vector tmp_masks (nex_out.pin_count()); bool flag = synth_async_block_substatement_(des, scope, nex_map, tmp_out, tmp_ena, tmp_masks, stmt); if (!flag) return false; for (size_t mdx = 0 ; mdx < nex_out.pin_count() ; mdx += 1) { connect(out_mux[mdx]->pin_Data(idx), tmp_out.pin(mdx)); connect(ena_mux[mdx]->pin_Data(idx), tmp_ena.pin(mdx)); merge_parallel_masks(bitmasks[mdx], tmp_masks[mdx]); if (!tmp_ena.pin(mdx).is_linked(scope->tie_hi())) full_case[mdx] = false; } } for (unsigned mdx = 0 ; mdx < nex_out.pin_count() ; mdx += 1) { // Optimize away the enable mux if we have a full case, // otherwise hook it up. if (full_case[mdx]) { connect(enables.pin(mdx), scope->tie_hi()); delete ena_mux[mdx]; continue; } des->add_node(ena_mux[mdx]); connect(ena_mux[mdx]->pin_Sel(), esig->pin(0)); connect(enables.pin(mdx), ena_mux[mdx]->pin_Result()); NetNet*tmp = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, &netvector_t::scalar_logic); tmp->local_flag(true); connect(ena_mux[mdx]->pin_Result(), tmp->pin(0)); } return true; } /* * casez statements are hard to implement as a single wide mux because * the test doesn't really map to a select input. Instead, implement * it as a chain of binary muxes. This gives the synthesizer more * flexibility, and is more typically what is desired from a casez anyhow. */ bool NetCase::synth_async_casez_(Design*des, NetScope*scope, NexusSet&nex_map, NetBus&nex_out, NetBus&enables, vector&bitmasks) { ivl_assert(*this, nex_map.size() == nex_out.pin_count()); ivl_assert(*this, nex_map.size() == enables.pin_count()); ivl_assert(*this, nex_map.size() == bitmasks.size()); /* Synthesize the select expression. */ NetNet*esig = expr_->synthesize(des, scope, expr_); unsigned sel_width = esig->vector_width(); ivl_assert(*this, sel_width > 0); vectormux_width (nex_out.pin_count()); for (unsigned idx = 0 ; idx < nex_out.pin_count() ; idx += 1) { mux_width[idx] = nex_map[idx].wid; if (debug_synth2) { cerr << get_fileline() << ": NetCase::synth_async_casez_: " << "idx=" << idx << ", mux_width[idx]=" << mux_width[idx] << endl; } } // The incoming nex_out is taken as the input for this // statement. Since there are collection of statements // that start at this same point, we save all these // inputs and reuse them for each statement. Unlink the // nex_out now, so we can hook up the mux outputs. NetBus statement_input (scope, nex_out.pin_count()); for (unsigned idx = 0 ; idx < nex_out.pin_count() ; idx += 1) { connect(statement_input.pin(idx), nex_out.pin(idx)); nex_out.pin(idx).unlink(); if (debug_synth2) { cerr << get_fileline() << ": NetCase::synth_async_casez_: " << "statement_input.pin(" << idx << "):" << endl; statement_input.pin(idx).dump_link(cerr, 8); } } // Look for a default statement. NetProc*default_statement = 0; for (size_t item = 0 ; item < items_.size() ; item += 1) { if (items_[item].guard != 0) continue; ivl_assert(*this, default_statement==0); default_statement = items_[item].statement; } /* If there is a default clause, synthesize it once and we'll link it in wherever it is needed. If there isn't, create a dummy default to pass on the accumulated nex_out from preceding statements. */ NetBus default_out (scope, nex_out.pin_count()); for (unsigned idx = 0 ; idx < default_out.pin_count() ; idx += 1) connect(default_out.pin(idx), statement_input.pin(idx)); if (default_statement) { bool flag = synth_async_block_substatement_(des, scope, nex_map, default_out, enables, bitmasks, default_statement); if (!flag) return false; if (debug_synth2) { cerr << get_fileline() << ": NetCase::synth_async_casez_: " << "synthesize default clause at " << default_statement->get_fileline() << " is done." << endl; } } netvector_t*condit_type = new netvector_t(IVL_VT_LOGIC, 0, 0); NetCaseCmp::kind_t case_kind = NetCaseCmp::EEQ; switch (type()) { case NetCase::EQ: case_kind = NetCaseCmp::EEQ; break; case NetCase::EQX: case_kind = NetCaseCmp::XEQ; break; case NetCase::EQZ: case_kind = NetCaseCmp::ZEQ; break; default: assert(0); } // Process the items from last to first. We generate a // true/false mux, with the select being the comparison of // the case select with the guard expression. The true input // (data1) is the current statement, and the false input is // the result of a later statement. vectorprev_mux (nex_out.pin_count()); for (size_t idx = 0 ; idx < items_.size() ; idx += 1) { size_t item = items_.size()-idx-1; if (items_[item].guard == 0) continue; NetProc*stmt = items_[item].statement; ivl_assert(*this, stmt); NetExpr*guard_expr = items_[item].guard; NetNet*guard = guard_expr->synthesize(des, scope, guard_expr); NetCaseCmp*condit_dev = new NetCaseCmp(scope, scope->local_symbol(), sel_width, case_kind); des->add_node(condit_dev); condit_dev->set_line(*this); // Note that the expression that may have wildcards must // go in the pin(2) input. This is the definition of the // NetCaseCmp statement. connect(condit_dev->pin(1), esig->pin(0)); connect(condit_dev->pin(2), guard->pin(0)); NetNet*condit = new NetNet(scope, scope->local_symbol(), NetNet::TRI, condit_type); condit->set_line(*this); condit->local_flag(true); connect(condit_dev->pin(0), condit->pin(0)); // Synthesize the guarded statement. NetBus tmp_out (scope, nex_out.pin_count()); NetBus tmp_ena (scope, nex_out.pin_count()); vector tmp_masks (nex_out.pin_count()); for (unsigned pdx = 0 ; pdx < nex_out.pin_count() ; pdx += 1) connect(tmp_out.pin(pdx), statement_input.pin(pdx)); synth_async_block_substatement_(des, scope, nex_map, tmp_out, tmp_ena, tmp_masks, stmt); NetBus prev_ena (scope, nex_out.pin_count()); for (unsigned mdx = 0 ; mdx < nex_out.pin_count() ; mdx += 1) { NetMux*mux = new NetMux(scope, scope->local_symbol(), mux_width[mdx], 2, 1); des->add_node(mux); mux->set_line(*this); connect(mux->pin_Sel(), condit->pin(0)); connect(mux->pin_Data(1), tmp_out.pin(mdx)); // If there is a previous mux, then use that as the // false clause input. Otherwise, use the default. if (prev_mux[mdx]) connect(mux->pin_Data(0), prev_mux[mdx]->pin_Result()); else connect(mux->pin_Data(0), default_out.pin(mdx)); // Make a NetNet for the result. ivl_variable_type_t mux_data_type = IVL_VT_LOGIC; netvector_t*tmp_vec = new netvector_t(mux_data_type, mux_width[mdx]-1,0); NetNet*tmp = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, tmp_vec); tmp->local_flag(true); tmp->set_line(*this); ivl_assert(*this, tmp->vector_width() != 0); connect(mux->pin_Result(), tmp->pin(0)); // This mux becomes the "false" input to the next mux. prev_mux[mdx] = mux; connect(prev_ena.pin(mdx), enables.pin(mdx)); enables.pin(mdx).unlink(); multiplex_enables(des, scope, condit, tmp_ena.pin(mdx), prev_ena.pin(mdx), enables.pin(mdx)); merge_parallel_masks(bitmasks[mdx], tmp_masks[mdx]); } } // Connect the last mux to the output. for (size_t mdx = 0 ; mdx < prev_mux.size() ; mdx += 1) connect(prev_mux[mdx]->pin_Result(), nex_out.pin(mdx)); return true; } /* * A condit statement (if (cond) ... else ... ;) infers an A-B mux, * with the cond expression acting as a select input. If the cond * expression is true, the if_ clause is selected, and if false, the * else_ clause is selected. */ bool NetCondit::synth_async(Design*des, NetScope*scope, NexusSet&nex_map, NetBus&nex_out, NetBus&enables, vector&bitmasks) { // Handle the unlikely case that both clauses are empty. if ((if_ == 0) && (else_ == 0)) return true; ivl_assert(*this, nex_map.size() == nex_out.pin_count()); ivl_assert(*this, nex_map.size() == enables.pin_count()); ivl_assert(*this, nex_map.size() == bitmasks.size()); // Synthesize the condition. This will act as a select signal // for a binary mux. NetNet*ssig = expr_->synthesize(des, scope, expr_); ivl_assert(*this, ssig); // The incoming nex_out is taken as the input for this // statement. Since there are two statements that start // at this same point, we save all these inputs and reuse // them for both statements. Unlink the nex_out now, so // we can hook up the mux outputs. NetBus statement_input (scope, nex_out.pin_count()); for (unsigned idx = 0 ; idx < nex_out.pin_count() ; idx += 1) { connect(statement_input.pin(idx), nex_out.pin(idx)); nex_out.pin(idx).unlink(); if (debug_synth2) { cerr << get_fileline() << ": NetCondit::synth_async: " << "statement_input.pin(" << idx << "):" << endl; statement_input.pin(idx).dump_link(cerr, 8); } } NetBus a_out (scope, nex_out.pin_count()); NetBus a_ena (scope, nex_out.pin_count()); vector a_masks (nex_out.pin_count()); if (if_) { if (debug_synth2) { cerr << get_fileline() << ": NetCondit::synth_async: " << "Synthesize if clause at " << if_->get_fileline() << endl; } for (unsigned idx = 0 ; idx < a_out.pin_count() ; idx += 1) { connect(a_out.pin(idx), statement_input.pin(idx)); } bool flag = synth_async_block_substatement_(des, scope, nex_map, a_out, a_ena, a_masks, if_); if (!flag) return false; } else { for (unsigned idx = 0 ; idx < a_out.pin_count() ; idx += 1) { connect(a_out.pin(idx), statement_input.pin(idx)); connect(a_ena.pin(idx), scope->tie_lo()); } } NetBus b_out(scope, nex_out.pin_count()); NetBus b_ena(scope, nex_out.pin_count()); vector b_masks (nex_out.pin_count()); if (else_) { if (debug_synth2) { cerr << get_fileline() << ": NetCondit::synth_async: " << "Synthesize else clause at " << else_->get_fileline() << endl; } for (unsigned idx = 0 ; idx < b_out.pin_count() ; idx += 1) { connect(b_out.pin(idx), statement_input.pin(idx)); } bool flag = synth_async_block_substatement_(des, scope, nex_map, b_out, b_ena, b_masks, else_); if (!flag) return false; } else { for (unsigned idx = 0 ; idx < b_out.pin_count() ; idx += 1) { connect(b_out.pin(idx), statement_input.pin(idx)); connect(b_ena.pin(idx), scope->tie_lo()); } } /* The nex_out output, a_out input, and b_out input all have the same pin count (usually, but not always 1) because they are net arrays of the same dimension. The for loop below creates a NetMux for each pin of the output. (Note that pins may be, in fact usually are, vectors.) */ for (unsigned idx = 0 ; idx < nex_out.pin_count() ; idx += 1) { bool a_driven = a_out.pin(idx).nexus()->pick_any_net(); bool b_driven = b_out.pin(idx).nexus()->pick_any_net(); if (!a_driven && !b_driven) { connect(nex_out.pin(idx), statement_input.pin(idx)); continue; } bitmasks[idx] = mask_t (nex_map[idx].wid, true); merge_parallel_masks(bitmasks[idx], a_masks[idx]); merge_parallel_masks(bitmasks[idx], b_masks[idx]); // If one clause is empty and the other clause unconditionally // drives all bits of the vector, we can rely on the enable // to prevent the flip-flop or latch updating when the empty // clause is selected, and hence don't need a mux. if (!a_driven && all_bits_driven(b_masks[idx])) { connect(nex_out.pin(idx), b_out.pin(idx)); continue; } if (!b_driven && all_bits_driven(a_masks[idx])) { connect(nex_out.pin(idx), a_out.pin(idx)); continue; } // Guess the mux type from the type of the output. ivl_variable_type_t mux_data_type = IVL_VT_LOGIC; if (NetNet*tmp = nex_out.pin(idx).nexus()->pick_any_net()) { mux_data_type = tmp->data_type(); } unsigned mux_off = 0; unsigned mux_width = nex_map[idx].wid; if (debug_synth2) { cerr << get_fileline() << ": NetCondit::synth_async: " << "Calculated mux_width=" << mux_width << endl; } NetPartSelect*apv = detect_partselect_lval(a_out.pin(idx)); if (debug_synth2 && apv) { cerr << get_fileline() << ": NetCondit::synth_async: " << "Assign-to-part apv base=" << apv->base() << ", width=" << apv->width() << endl; } NetPartSelect*bpv = detect_partselect_lval(b_out.pin(idx)); if (debug_synth2 && bpv) { cerr << get_fileline() << ": NetCondit::synth_async: " << "Assign-to-part bpv base=" << bpv->base() << ", width=" << bpv->width() << endl; } unsigned mux_lwidth = mux_width; ivl_assert(*this, mux_width != 0); if (apv && bpv && apv->width()==bpv->width() && apv->base()==bpv->base()) { // The a and b sides are both assigning to the // same bits of the output, so we can use that to // create a much narrower mux that only // manipulates the width of the part. mux_width = apv->width(); mux_off = apv->base(); a_out.pin(idx).unlink(); b_out.pin(idx).unlink(); connect(a_out.pin(idx), apv->pin(0)); connect(b_out.pin(idx), bpv->pin(0)); delete apv; delete bpv; } else { // The part selects are of no use. Forget them. apv = 0; bpv = 0; } NetMux*mux = new NetMux(scope, scope->local_symbol(), mux_width, 2, 1); mux->set_line(*this); des->add_node(mux); netvector_t*tmp_type = 0; if (mux_width==1) tmp_type = new netvector_t(mux_data_type); else tmp_type = new netvector_t(mux_data_type, mux_width-1,0); // Bind some temporary signals to carry pin type. NetNet*otmp = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, NetNet::not_an_array, tmp_type); otmp->local_flag(true); otmp->set_line(*this); connect(mux->pin_Result(),otmp->pin(0)); connect(mux->pin_Sel(), ssig->pin(0)); connect(mux->pin_Data(1), a_out.pin(idx)); connect(mux->pin_Data(0), b_out.pin(idx)); // If we are only muxing a part of the output vector, make a // NetSubstitute to blend the mux output with the accumulated // output from previous statements. if (mux_width < mux_lwidth) { tmp_type = new netvector_t(mux_data_type, mux_lwidth-1,0); NetNet*itmp = statement_input.pin(idx).nexus()->pick_any_net(); if (itmp == 0) { itmp = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, NetNet::not_an_array, tmp_type); itmp->local_flag(true); itmp->set_line(*this); connect(itmp->pin(0), statement_input.pin(idx)); } NetNet*tmp = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, NetNet::not_an_array, tmp_type); tmp->local_flag(true); tmp->set_line(*this); NetSubstitute*ps = new NetSubstitute(itmp, otmp, mux_lwidth, mux_off); des->add_node(ps); connect(ps->pin(0), tmp->pin(0)); otmp = tmp; } connect(nex_out.pin(idx), otmp->pin(0)); } for (unsigned idx = 0 ; idx < nex_out.pin_count() ; idx += 1) { multiplex_enables(des, scope, ssig, a_ena.pin(idx), b_ena.pin(idx), enables.pin(idx)); } return true; } bool NetEvWait::synth_async(Design*des, NetScope*scope, NexusSet&nex_map, NetBus&nex_out, NetBus&enables, vector&bitmasks) { bool flag = statement_->synth_async(des, scope, nex_map, nex_out, enables, bitmasks); return flag; } bool NetForLoop::synth_async(Design*des, NetScope*scope, NexusSet&nex_map, NetBus&nex_out, NetBus&enables, vector&bitmasks) { if (debug_synth2) { cerr << get_fileline() << ": NetForLoop::synth_async: " << "Index variable is " << index_->name() << endl; cerr << get_fileline() << ": NetForLoop::synth_async: " << "Initialization expression: " << *init_expr_ << endl; } // Get the step assignment statement and break it into the // l-value (should be the index) and the r-value, which is the // step expressions. NetAssign*step_assign = dynamic_cast (step_statement_); char assign_operator = step_assign->assign_operator(); ivl_assert(*this, step_assign); NetExpr*step_expr = step_assign->rval(); // Tell the scope that this index value is like a genvar. LocalVar index_var; index_var.nwords = 0; map index_args; // Calculate the initial value for the index. index_var.value = init_expr_->evaluate_function(*this, index_args); ivl_assert(*this, index_var.value); index_args[index_->name()] = index_var; for (;;) { // Evaluate the condition expression. If it is false, // then we are going to break out of this synthesis loop. NetExpr*tmp = condition_->evaluate_function(*this, index_args); ivl_assert(*this, tmp); long cond_value; bool rc = eval_as_long(cond_value, tmp); ivl_assert(*this, rc); delete tmp; if (!cond_value) break; scope->genvar_tmp = index_->name(); rc = eval_as_long(scope->genvar_tmp_val, index_var.value); ivl_assert(*this, rc); if (debug_synth2) { cerr << get_fileline() << ": NetForLoop::synth_async: " << "Synthesis iteration with " << index_->name() << "=" << *index_var.value << endl; } // Synthesize the iterated expression. Stash the loop // index value so that the substatements can see this // value and use it during its own synthesis. ivl_assert(*this, scope->loop_index_tmp.empty()); scope->loop_index_tmp = index_args; NetBus tmp_ena (scope, nex_out.pin_count()); vector tmp_masks (nex_out.pin_count()); rc = synth_async_block_substatement_(des, scope, nex_map, nex_out, tmp_ena, tmp_masks, statement_); for (unsigned idx = 0 ; idx < nex_out.pin_count() ; idx += 1) { merge_sequential_enables(des, scope, enables.pin(idx), tmp_ena.pin(idx)); merge_sequential_masks(bitmasks[idx], tmp_masks[idx]); } scope->loop_index_tmp.clear(); // Evaluate the step_expr to generate the next index value. tmp = step_expr->evaluate_function(*this, index_args); ivl_assert(*this, tmp); // If there is an assign_operator, then replace the // index_var.value with (value tmp) and evaluate // that to get the next value. "value" is the existing // value, and "tmp" is the step value. We are replacing // (value += tmp) with (value = value + tmp) and // evaluating it. switch (assign_operator) { case 0: break; case '+': case '-': index_var.value = new NetEBAdd(assign_operator, tmp, index_var.value, 32, true); tmp = index_var.value->evaluate_function(*this, index_args); break; default: cerr << get_fileline() << ": internal error: " << "NetForLoop::synth_async: What to do with assign_operator=" << assign_operator << endl; ivl_assert(*this, 0); } delete index_var.value; index_var.value = tmp; index_args[index_->name()] = index_var; } delete index_var.value; return true; } /* * This method is called when the process is shown to be * asynchronous. Figure out the nexus set of outputs from this * process, and pass that to the synth_async method for the statement * of the process. The statement will connect its output to the * nex_out set, using the nex_map as a guide. Starting from the top, * the nex_map is the same as the nex_map. */ bool NetProcTop::synth_async(Design*des) { NexusSet nex_set; statement_->nex_output(nex_set); if (debug_synth2) { cerr << get_fileline() << ": NetProcTop::synth_async: " << "Process has " << nex_set.size() << " outputs." << endl; } NetBus nex_out (scope(), nex_set.size()); NetBus enables (scope(), nex_set.size()); vector bitmasks (nex_set.size()); // Save links to the initial nex_out. These will be used later // to detect floating mux inputs that need to be tied off. NetBus nex_in (scope(), nex_out.pin_count()); for (unsigned idx = 0 ; idx < nex_out.pin_count() ; idx += 1) connect(nex_in.pin(idx), nex_out.pin(idx)); bool flag = statement_->synth_async(des, scope(), nex_set, nex_out, enables, bitmasks); if (!flag) return false; bool latch_flag = false; for (unsigned idx = 0 ; idx < enables.pin_count() ; idx += 1) { if (!enables.pin(idx).is_linked(scope()->tie_hi())) { cerr << get_fileline() << ": warning: " << "A latch has been inferred for '" << nex_set[idx].lnk.nexus()->pick_any_net()->name() << "'." << endl; latch_flag = true; } } if (latch_flag) { cerr << get_fileline() << ": sorry: Latches are not " << "currently supported in synthesis." << endl; des->errors += 1; return false; } flag = tie_off_floating_inputs_(des, nex_set, nex_in, bitmasks, false); if (!flag) return false; for (unsigned idx = 0 ; idx < nex_set.size() ; idx += 1) connect(nex_set[idx].lnk, nex_out.pin(idx)); synthesized_design_ = des; return true; } bool NetProc::synth_sync(Design*des, NetScope*scope, bool& /* ff_negedge */, NetNet* /* ff_clk */, NetBus&ff_ce, NetBus& /* ff_aclr*/, NetBus& /* ff_aset*/, vector& /*ff_aset_value*/, NexusSet&nex_map, NetBus&nex_out, vector&bitmasks, const vector&events) { if (events.size() > 0) { cerr << get_fileline() << ": error: Events are unaccounted" << " for in process synthesis." << endl; des->errors += 1; } if (debug_synth2) { cerr << get_fileline() << ": NetProc::synth_sync: " << "This statement is an async input to a sync process." << endl; } /* Synthesize the input to the DFF. */ return synth_async(des, scope, nex_map, nex_out, ff_ce, bitmasks); } /* * This method is called when a block is encountered near the surface * of a synchronous always statement. For example, this code will be * invoked for input like this: * * always @(posedge clk...) begin * * * ... * end * * This needs to be split into a DFF bank for each statement, because * the statements may each infer different reset and enables signals. */ bool NetBlock::synth_sync(Design*des, NetScope*scope, bool&ff_negedge, NetNet*ff_clk, NetBus&ff_ce, NetBus&ff_aclr,NetBus&ff_aset, vector&ff_aset_value, NexusSet&nex_map, NetBus&nex_out, vector&bitmasks, const vector&events_in) { if (debug_synth2) { cerr << get_fileline() << ": NetBlock::synth_sync: " << "Examine this block for synchronous logic." << endl; } if (last_ == 0) { return true; } bool flag = true; NetProc*cur = last_; do { cur = cur->next_; // Create a temporary nex_map for the substatement. NexusSet tmp_map; cur->nex_output(tmp_map); // Create temporary variables to collect the output from the synthesis. NetBus tmp_out (scope, tmp_map.size()); NetBus tmp_ce (scope, tmp_map.size()); vector tmp_masks (tmp_map.size()); // Map (and move) the accumulated nex_out for this block // to the version that we can pass to the next statement. // We will move the result back later. for (unsigned idx = 0 ; idx < tmp_out.pin_count() ; idx += 1) { unsigned ptr = nex_map.find_nexus(tmp_map[idx]); ivl_assert(*this, ptr < nex_out.pin_count()); connect(tmp_out.pin(idx), nex_out.pin(ptr)); nex_out.pin(ptr).unlink(); } /* Now go on with the synchronous synthesis for this subset of the statement. The tmp_map is the output nexa that we expect, and the tmp_out is where we want those outputs connected. */ bool ok_flag = cur->synth_sync(des, scope, ff_negedge, ff_clk, tmp_ce, ff_aclr, ff_aset, ff_aset_value, tmp_map, tmp_out, tmp_masks, events_in); flag = flag && ok_flag; if (ok_flag == false) continue; // Now map the output from the substatement back to the // outputs for this block. for (unsigned idx = 0 ; idx < tmp_out.pin_count() ; idx += 1) { unsigned ptr = nex_map.find_nexus(tmp_map[idx]); ivl_assert(*this, ptr < nex_out.pin_count()); connect(nex_out.pin(ptr), tmp_out.pin(idx)); merge_sequential_enables(des, scope, ff_ce.pin(ptr), tmp_ce.pin(idx)); merge_sequential_masks(bitmasks[ptr], tmp_masks[idx]); } } while (cur != last_); if (debug_synth2) { cerr << get_fileline() << ": NetBlock::synth_sync: " << "Done Examining this block for synchronous logic." << endl; } return flag; } /* * This method handles the case where I find a conditional near the * surface of a synchronous thread. This conditional can be a CE or an * asynchronous set/reset, depending on whether the pin of the * expression is connected to an event, or not. */ bool NetCondit::synth_sync(Design*des, NetScope*scope, bool&ff_negedge, NetNet*ff_clk, NetBus&ff_ce, NetBus&ff_aclr,NetBus&ff_aset, vector&ff_aset_value, NexusSet&nex_map, NetBus&nex_out, vector&bitmasks, const vector&events_in) { /* First try to turn the condition expression into an asynchronous set/reset. If the condition expression has inputs that are included in the sensitivity list, then it is likely intended as an asynchronous input. */ NexusSet*expr_input = expr_->nex_input(); assert(expr_input); for (unsigned idx = 0 ; idx < events_in.size() ; idx += 1) { NetEvProbe*ev = events_in[idx]; NexusSet pin_set; pin_set.add(ev->pin(0).nexus(), 0, 0); if (! expr_input->contains(pin_set)) continue; // Synthesize the set/reset input expression. NetNet*rst = expr_->synthesize(des, scope, expr_); ivl_assert(*this, rst->pin_count() == 1); // Check that the edge used on the set/reset input is correct. switch (ev->edge()) { case NetEvProbe::POSEDGE: if (ev->pin(0).nexus() != rst->pin(0).nexus()) { cerr << get_fileline() << ": error: " << "Condition for posedge asynchronous set/reset " << "must exactly match the event expression." << endl; des->errors += 1; return false; } break; case NetEvProbe::NEGEDGE: { bool is_inverter = false; NetNode*node = rst->pin(0).nexus()->pick_any_node(); if (NetLogic*gate = dynamic_cast(node)) { if (gate->type() == NetLogic::NOT) is_inverter = true; } if (NetUReduce*gate = dynamic_cast(node)) { if (gate->type() == NetUReduce::NOR) is_inverter = true; } if (!is_inverter || ev->pin(0).nexus() != node->pin(1).nexus()) { cerr << get_fileline() << ": error: " << "Condition for negedge asynchronous set/reset must be " << "a simple inversion of the event expression." << endl; des->errors += 1; return false; } break; } default: cerr << get_fileline() << ": error: " << "Asynchronous set/reset event must be " << "edge triggered." << endl; des->errors += 1; return false; } // Synthesize the true clause to figure out what kind of // set/reset we have. This should synthesize down to a // constant. If not, we have an asynchronous LOAD, a // very different beast. ivl_assert(*this, if_); NetBus tmp_out(scope, nex_out.pin_count()); NetBus tmp_ena(scope, nex_out.pin_count()); vector tmp_masks (nex_out.pin_count()); bool flag = if_->synth_async(des, scope, nex_map, tmp_out, tmp_ena, tmp_masks); if (!flag) return false; ivl_assert(*this, tmp_out.pin_count() == ff_aclr.pin_count()); ivl_assert(*this, tmp_out.pin_count() == ff_aset.pin_count()); for (unsigned pin = 0 ; pin < tmp_out.pin_count() ; pin += 1) { Nexus*rst_nex = tmp_out.pin(pin).nexus(); if (!all_bits_driven(tmp_masks[pin])) { cerr << get_fileline() << ": sorry: Not all bits of '" << nex_map[idx].lnk.nexus()->pick_any_net()->name() << "' are asynchronously set or reset. This is " << "not currently supported in synthesis." << endl; des->errors += 1; return false; } if (! rst_nex->drivers_constant() || ! tmp_ena.pin(pin).is_linked(scope->tie_hi()) ) { cerr << get_fileline() << ": sorry: Asynchronous load " << "is not currently supported in synthesis." << endl; des->errors += 1; return false; } if (ff_aclr.pin(pin).is_linked() || ff_aset.pin(pin).is_linked()) { cerr << get_fileline() << ": sorry: More than " "one asynchronous set/reset clause is " "not currently supported in synthesis." << endl; des->errors += 1; return false; } verinum rst_drv = rst_nex->driven_vector(); verinum zero (verinum::V0, rst_drv.len()); verinum ones (verinum::V1, rst_drv.len()); if (rst_drv==zero) { // Don't yet support multiple asynchronous reset inputs. ivl_assert(*this, ! ff_aclr.pin(pin).is_linked()); ivl_assert(*this, rst->pin_count()==1); connect(ff_aclr.pin(pin), rst->pin(0)); } else { // Don't yet support multiple asynchronous set inputs. ivl_assert(*this, ! ff_aset.pin(pin).is_linked()); ivl_assert(*this, rst->pin_count()==1); connect(ff_aset.pin(pin), rst->pin(0)); if (rst_drv!=ones) ff_aset_value[pin] = rst_drv; } } if (else_ == 0) return true; vector events; for (unsigned jdx = 0 ; jdx < events_in.size() ; jdx += 1) { if (jdx != idx) events.push_back(events_in[jdx]); } return else_->synth_sync(des, scope, ff_negedge, ff_clk, ff_ce, ff_aclr, ff_aset, ff_aset_value, nex_map, nex_out, bitmasks, events); } delete expr_input; #if 0 /* Detect the case that this is a *synchronous* set/reset. It is not asynchronous because we know the condition is not included in the sensitivity list, but if the if_ case is constant (has no inputs) then we can model this as a synchronous set/reset. This is only synchronous set/reset if there is a true and a false clause, and no inputs. The "no inputs" requirement is met if the assignments are of all constant values. */ assert(if_ != 0); NexusSet*a_set = if_->nex_input(); if ((a_set->count() == 0) && if_ && else_) { NetNet*rst = expr_->synthesize(des); assert(rst->pin_count() == 1); /* Synthesize the true clause to figure out what kind of set/reset we have. */ NetNet*asig = new NetNet(scope, scope->local_symbol(), NetNet::WIRE, nex_map->pin_count()); asig->local_flag(true); bool flag = if_->synth_async(des, scope, nex_map, asig); if (!flag) { /* This path leads nowhere */ delete asig; } else { assert(asig->pin_count() == ff->width()); /* Collect the set/reset value into a verinum. If this turns out to be entirely 0 values, then use the Sclr input. Otherwise, use the Aset input and save the set value. */ verinum tmp (verinum::V0, ff->width()); for (unsigned bit = 0 ; bit < ff->width() ; bit += 1) { assert(asig->pin(bit).nexus()->drivers_constant()); tmp.set(bit, asig->pin(bit).nexus()->driven_value()); } assert(tmp.is_defined()); if (tmp.is_zero()) { connect(ff->pin_Sclr(), rst->pin(0)); } else { connect(ff->pin_Sset(), rst->pin(0)); ff->sset_value(tmp); } delete a_set; assert(else_ != 0); flag = else_->synth_sync(des, scope, ff, nex_map, nex_out, svector(0)) && flag; DEBUG_SYNTH2_EXIT("NetCondit",flag) return flag; } } delete a_set; #endif #if 0 /* This gives a false positive for strange coding styles, such as ivltests/conditsynth3.v. */ /* Failed to find an asynchronous set/reset, so any events input are probably in error. */ if (events_in.size() > 0) { cerr << get_fileline() << ": error: Events are unaccounted" << " for in process synthesis." << endl; des->errors += 1; } #endif return synth_async(des, scope, nex_map, nex_out, ff_ce, bitmasks); } bool NetEvWait::synth_sync(Design*des, NetScope*scope, bool&ff_negedge, NetNet*ff_clk, NetBus&ff_ce, NetBus&ff_aclr,NetBus&ff_aset, vector&ff_aset_value, NexusSet&nex_map, NetBus&nex_out, vector&bitmasks, const vector&events_in) { if (debug_synth2) { cerr << get_fileline() << ": NetEvWait::synth_sync: " << "Synchronous process an event statement." << endl; } if (events_in.size() > 0) { cerr << get_fileline() << ": error: Events are unaccounted" << " for in process synthesis." << endl; des->errors += 1; } assert(events_in.size() == 0); /* This can't be other than one unless there are named events, which I cannot synthesize. */ ivl_assert(*this, events_.size() == 1); NetEvent*ev = events_[0]; assert(ev->nprobe() >= 1); vectorevents (ev->nprobe() - 1); /* Get the input set from the substatement. This will be used to figure out which of the probes is the clock. */ NexusSet*statement_input = statement_ -> nex_input(); /* Search for a clock input. The clock input is the edge event that is not also an input to the substatement. */ NetEvProbe*pclk = 0; unsigned event_idx = 0; for (unsigned idx = 0 ; idx < ev->nprobe() ; idx += 1) { NetEvProbe*tmp = ev->probe(idx); assert(tmp->pin_count() == 1); NexusSet tmp_nex; tmp_nex .add( tmp->pin(0).nexus(), 0, 0 ); if (! statement_input ->contains(tmp_nex)) { if (pclk != 0) { cerr << get_fileline() << ": error: Too many " << "clocks for synchronous logic." << endl; cerr << get_fileline() << ": : Perhaps an" << " asynchronous set/reset is misused?" << endl; des->errors += 1; } pclk = tmp; } else { events[event_idx++] = tmp; } } if (pclk == 0) { cerr << get_fileline() << ": error: None of the edges" << " are valid clock inputs." << endl; cerr << get_fileline() << ": : Perhaps the clock" << " is read by a statement or expression?" << endl; des->errors += 1; return false; } if (debug_synth2) { cerr << get_fileline() << ": NetEvWait::synth_sync: " << "Found and synthesized the FF clock." << endl; } connect(ff_clk->pin(0), pclk->pin(0)); if (pclk->edge() == NetEvProbe::NEGEDGE) { ff_negedge = true; if (debug_synth2) { cerr << get_fileline() << ": debug: " << "Detected a NEGEDGE clock for the synthesized ff." << endl; } } /* Synthesize the input to the DFF. */ return statement_->synth_sync(des, scope, ff_negedge, ff_clk, ff_ce, ff_aclr, ff_aset, ff_aset_value, nex_map, nex_out, bitmasks, events); } /* * This method is called for a process that is determined to be * synchronous. Create a NetFF device to hold the output from the * statement, and synthesize that statement in place. */ bool NetProcTop::synth_sync(Design*des) { if (debug_synth2) { cerr << get_fileline() << ": NetProcTop::synth_sync: " << "Process is apparently synchronous. Making NetFFs." << endl; } NexusSet nex_set; statement_->nex_output(nex_set); vector aset_value(nex_set.size()); /* Make a model FF that will connect to the first item in the set, and will also take the initial connection of clocks and resets. */ // Create a net to carry the clock for the synthesized FFs. NetNet*clock = new NetNet(scope(), scope()->local_symbol(), NetNet::TRI, &netvector_t::scalar_logic); clock->local_flag(true); clock->set_line(*this); NetBus ce (scope(), nex_set.size()); NetBus nex_d (scope(), nex_set.size()); NetBus nex_q (scope(), nex_set.size()); NetBus aclr (scope(), nex_set.size()); NetBus aset (scope(), nex_set.size()); vector bitmasks (nex_set.size()); // Save links to the initial nex_d. These will be used later // to detect floating mux inputs that need to be tied off. NetBus nex_in (scope(), nex_d.pin_count()); for (unsigned idx = 0 ; idx < nex_in.pin_count() ; idx += 1) connect(nex_in.pin(idx), nex_d.pin(idx)); // The Q of the NetFF devices is connected to the output that // we are. The nex_q is a bundle of the outputs. for (unsigned idx = 0 ; idx < nex_q.pin_count() ; idx += 1) connect(nex_q.pin(idx), nex_set[idx].lnk); // Connect the input later. /* Synthesize the input to the DFF. */ bool negedge = false; bool flag = statement_->synth_sync(des, scope(), negedge, clock, ce, aclr, aset, aset_value, nex_set, nex_d, bitmasks, vector()); if (! flag) { delete clock; return false; } flag = tie_off_floating_inputs_(des, nex_set, nex_in, bitmasks, true); if (!flag) return false; for (unsigned idx = 0 ; idx < nex_set.size() ; idx += 1) { //ivl_assert(*this, nex_set[idx].nex); if (debug_synth2) { cerr << get_fileline() << ": debug: " << "Top level making a " << nex_set[idx].wid << "-wide " << "NetFF device." << endl; } NetFF*ff2 = new NetFF(scope(), scope()->local_symbol(), negedge, nex_set[idx].wid); des->add_node(ff2); ff2->set_line(*this); ff2->aset_value(aset_value[idx]); NetNet*tmp = nex_d.pin(idx).nexus()->pick_any_net(); tmp->set_line(*this); assert(tmp); tmp = crop_to_width(des, tmp, ff2->width()); connect(nex_q.pin(idx), ff2->pin_Q()); connect(tmp->pin(0), ff2->pin_Data()); connect(clock->pin(0), ff2->pin_Clock()); if (ce.pin(idx).is_linked()) connect(ce.pin(idx), ff2->pin_Enable()); if (aclr.pin(idx).is_linked()) connect(aclr.pin(idx), ff2->pin_Aclr()); if (aset.pin(idx).is_linked()) connect(aset.pin(idx), ff2->pin_Aset()); #if 0 if (ff->pin_Sset().is_linked()) connect(ff->pin_Sset(), ff2->pin_Sset()); if (ff->pin_Sclr().is_linked()) connect(ff->pin_Sclr(), ff2->pin_Sclr()); #endif } // The "clock" net was just to carry the connection back // to the flip-flop. Delete it now. The connection will // persist. delete clock; synthesized_design_ = des; return true; } class synth2_f : public functor_t { public: void process(Design*, NetProcTop*); private: }; /* * Look at a process. If it is asynchronous, then synthesize it as an * asynchronous process and delete the process itself for its gates. */ void synth2_f::process(Design*des, NetProcTop*top) { if (top->attribute(perm_string::literal("ivl_synthesis_off")).as_ulong() != 0) return; /* If the scope that contains this process as a cell attribute attached to it, then skip synthesis. */ if (top->scope()->attribute(perm_string::literal("ivl_synthesis_cell")).len() > 0) return; /* Create shared pullup and pulldown nodes (if they don't already exist) for use when creating clock/gate enables. */ top->scope()->add_tie_hi(des); top->scope()->add_tie_lo(des); if (top->is_synchronous()) { bool flag = top->synth_sync(des); if (! flag) { cerr << top->get_fileline() << ": error: " << "Unable to synthesize synchronous process." << endl; des->errors += 1; return; } des->delete_process(top); return; } if (! top->is_asynchronous()) { bool synth_error_flag = false; if (top->attribute(perm_string::literal("ivl_combinational")).as_ulong() != 0) { cerr << top->get_fileline() << ": error: " << "Process is marked combinational," << " but isn't really." << endl; des->errors += 1; synth_error_flag = true; } if (top->attribute(perm_string::literal("ivl_synthesis_on")).as_ulong() != 0) { cerr << top->get_fileline() << ": error: " << "Process is marked for synthesis," << " but I can't do it." << endl; des->errors += 1; synth_error_flag = true; } if (! synth_error_flag) cerr << top->get_fileline() << ": warning: " << "Process not synthesized." << endl; return; } if (! top->synth_async(des)) { cerr << top->get_fileline() << ": error: " << "Unable to synthesize asynchronous process." << endl; des->errors += 1; return; } des->delete_process(top); } void synth2(Design*des) { synth2_f synth_obj; des->functor(&synth_obj); }