mirror of
https://github.com/steveicarus/iverilog.git
synced 2026-09-02 02:27:40 +02:00
Functions that appear in continuous assignment expressions and that have hidden dependencies or side effects need to be re-evaluated whenever any input to the expression changes. This patch adds support in the compiler and vvp runtime to enable this. This is currently activated for any system function call that has no arguments. The user may also force it to be used for any user function by passing the option -gstrict-ca-eval to the compiler driver. This patch also removes the -dautomatic option which was used for gaining confidence in the code that supports automatic tasks and functions. It is believed that the testsuite provides reasonable fault coverage, and further tests can be added if bugs are found.
981 lines
29 KiB
C++
981 lines
29 KiB
C++
/*
|
|
* Copyright (c) 2002-2008 Stephen Williams ([email protected])
|
|
*
|
|
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
|
*/
|
|
|
|
# include "config.h"
|
|
|
|
# include "functor.h"
|
|
# include "netlist.h"
|
|
# include "netmisc.h"
|
|
# include "compiler.h"
|
|
# include <assert.h>
|
|
|
|
|
|
bool NetProc::synth_async(Design*des, NetScope*scope,
|
|
const NetBus&nex_map, NetBus&nex_out)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool NetProc::synth_sync(Design*des, NetScope*scope, NetFF*ff,
|
|
const NetBus&nex_map, NetBus&nex_out,
|
|
const svector<NetEvProbe*>&events)
|
|
{
|
|
if (events.count() > 0) {
|
|
cerr << get_fileline() << ": error: Events are unaccounted"
|
|
<< " for in process synthesis." << endl;
|
|
des->errors += 1;
|
|
}
|
|
|
|
/* Synthesize the input to the DFF. */
|
|
return synth_async(des, scope, nex_map, nex_out);
|
|
}
|
|
|
|
|
|
/*
|
|
* 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,
|
|
const NetBus&nex_map, NetBus&nex_out)
|
|
{
|
|
NetNet*rsig = rval_->synthesize(des, scope, rval_);
|
|
assert(rsig);
|
|
|
|
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;
|
|
}
|
|
assert(lval_->more == 0);
|
|
|
|
if (debug_synth2) {
|
|
cerr << get_fileline() << ": debug: l-value signal is "
|
|
<< lsig->vector_width() << " bits, r-value signal is "
|
|
<< rsig->vector_width() << " bits." << endl;
|
|
}
|
|
|
|
/* For now, assume there is exactly one output. */
|
|
assert(nex_out.pin_count() == 1);
|
|
|
|
connect(nex_out.pin(0), rsig->pin(0));
|
|
|
|
/* 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 WIRE. */
|
|
lval_->turn_sig_to_wire_on_release();
|
|
|
|
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,
|
|
const NetBus&nex_map, NetBus&nex_out)
|
|
{
|
|
if (last_ == 0) {
|
|
return true;
|
|
}
|
|
|
|
bool flag = true;
|
|
NetProc*cur = last_;
|
|
do {
|
|
cur = cur->next_;
|
|
|
|
/* Create a temporary map of the output only from this
|
|
statement. */
|
|
NexusSet tmp_set;
|
|
cur->nex_output(tmp_set);
|
|
NetBus tmp_map (scope, tmp_set.count());
|
|
for (unsigned idx = 0 ; idx < tmp_set.count() ; idx += 1)
|
|
connect(tmp_set[idx], tmp_map.pin(idx));
|
|
|
|
/* Create also a temporary NetBus to collect the
|
|
output from the synthesis. */
|
|
NetBus tmp_out (scope, tmp_set.count());
|
|
|
|
bool ok_flag = cur->synth_async(des, scope, tmp_map, tmp_out);
|
|
|
|
flag = flag && ok_flag;
|
|
if (ok_flag == false)
|
|
continue;
|
|
|
|
/* Now find the tmp_map pins in the nex_map global map,
|
|
and use that to direct the connection to the nex_out
|
|
output bus. Look for the nex_map pin that is linked
|
|
to the tmp_map.pin(idx) pin, and link that to the
|
|
tmp_out.pin(idx) output link. */
|
|
for (unsigned idx = 0 ; idx < tmp_out.pin_count() ; idx += 1) {
|
|
unsigned ptr = 0;
|
|
while (ptr < nex_map.pin_count()
|
|
&& ! nex_map.pin(ptr).is_linked(tmp_map.pin(idx)))
|
|
ptr += 1;
|
|
|
|
assert(ptr < nex_out.pin_count());
|
|
connect(nex_out.pin(ptr), tmp_out.pin(idx));
|
|
}
|
|
|
|
} while (cur != last_);
|
|
|
|
return flag;
|
|
}
|
|
|
|
bool NetCase::synth_async(Design*des, NetScope*scope,
|
|
const NetBus&nex_map, NetBus&nex_out)
|
|
{
|
|
/* Synthesize the select expression. */
|
|
NetNet*esig = expr_->synthesize(des, scope, expr_);
|
|
|
|
unsigned sel_width = esig->vector_width();
|
|
assert(sel_width > 0);
|
|
|
|
unsigned mux_width = 0;
|
|
for (unsigned idx = 0 ; idx < nex_out.pin_count() ; idx += 1)
|
|
mux_width += nex_out.pin(idx).nexus()->vector_width();
|
|
|
|
/* 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;
|
|
map<unsigned long,NetProc*>statement_map;
|
|
NetProc*statement_default = 0;
|
|
|
|
for (unsigned item = 0 ; item < nitems_ ; item += 1) {
|
|
if (items_[item].guard == 0) {
|
|
statement_default = items_[item].statement;
|
|
continue;
|
|
}
|
|
|
|
NetEConst*ge = dynamic_cast<NetEConst*>(items_[item].guard);
|
|
assert(ge);
|
|
verinum gval = ge->value();
|
|
|
|
unsigned sel_idx = gval.as_ulong();
|
|
|
|
assert(items_[item].statement);
|
|
statement_map[sel_idx] = items_[item].statement;
|
|
|
|
if (sel_idx > max_guard_value)
|
|
max_guard_value = sel_idx;
|
|
}
|
|
|
|
unsigned mux_size = max_guard_value + 1;
|
|
|
|
NetMux*mux = new NetMux(scope, scope->local_symbol(),
|
|
mux_width, mux_size, sel_width);
|
|
des->add_node(mux);
|
|
|
|
/* The select signal is already synthesized. Simply hook it up. */
|
|
connect(mux->pin_Sel(), esig->pin(0));
|
|
|
|
/* For now, assume that the output is only 1 signal. */
|
|
assert(nex_out.pin_count() == 1);
|
|
connect(mux->pin_Result(), nex_out.pin(0));
|
|
|
|
/* For now, only support logic types. */
|
|
ivl_variable_type_t mux_data_type = IVL_VT_LOGIC;
|
|
|
|
/* Forgot to support default statements? */
|
|
assert(statement_default == 0);
|
|
|
|
NetNet*isig;
|
|
for (unsigned idx = 0 ; idx < mux_size ; idx += 1) {
|
|
|
|
NetProc*stmt = statement_map[idx];
|
|
if (stmt == 0) {
|
|
cerr << get_fileline() << ": error: case " << idx
|
|
<< " is not accounted for in asynchronous mux." << endl;
|
|
des->errors += 1;
|
|
continue;
|
|
}
|
|
|
|
isig = new NetNet(scope, scope->local_symbol(),
|
|
NetNet::TRI, mux_width);
|
|
isig->local_flag(true);
|
|
isig->data_type(mux_data_type);
|
|
|
|
connect(mux->pin_Data(idx), isig->pin(0));
|
|
|
|
NetBus tmp (scope, 1);
|
|
connect(tmp.pin(0), isig->pin(0));
|
|
stmt->synth_async(des, scope, tmp, tmp);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool NetCondit::synth_async(Design*des, NetScope*scope,
|
|
const NetBus&nex_map, NetBus&nex_out)
|
|
{
|
|
#if 0
|
|
NetNet*ssig = expr_->synthesize(des);
|
|
assert(ssig);
|
|
|
|
if (if_ == 0) {
|
|
DEBUG_SYNTH2_EXIT("NetCondit",false)
|
|
return false;
|
|
}
|
|
if (else_ == 0) {
|
|
cerr << get_line() << ": error: Asynchronous if statement"
|
|
<< " is missing the else clause." << endl;
|
|
DEBUG_SYNTH2_EXIT("NetCondit",false)
|
|
return false;
|
|
}
|
|
|
|
assert(if_ != 0);
|
|
assert(else_ != 0);
|
|
|
|
NetNet*asig = new NetNet(scope, scope->local_symbol(),
|
|
NetNet::WIRE, nex_map->pin_count());
|
|
asig->local_flag(true);
|
|
|
|
bool flag;
|
|
flag = if_->synth_async(des, scope, nex_map, asig);
|
|
if (!flag) {
|
|
delete asig;
|
|
DEBUG_SYNTH2_EXIT("NetCondit",false)
|
|
return false;
|
|
}
|
|
|
|
NetNet*bsig = new NetNet(scope, scope->local_symbol(),
|
|
NetNet::WIRE, nex_map->pin_count());
|
|
bsig->local_flag(true);
|
|
|
|
flag = else_->synth_async(des, scope, nex_map, bsig);
|
|
if (!flag) {
|
|
delete asig;
|
|
delete bsig;
|
|
DEBUG_SYNTH2_EXIT("NetCondit",false)
|
|
return false;
|
|
}
|
|
|
|
NetMux*mux = new NetMux(scope, scope->local_symbol(),
|
|
nex_out->vector_width(), 2, 1);
|
|
|
|
connect(mux->pin_Sel(), ssig->pin(0));
|
|
connect(mux->pin_Data(1), asig->pin(0));
|
|
connect(mux->pin_Data(0), bsig->pin(0));
|
|
connect(nex_out->pin(0), mux->pin_Result());
|
|
|
|
des->add_node(mux);
|
|
|
|
DEBUG_SYNTH2_EXIT("NetCondit",true)
|
|
return true;
|
|
|
|
#else
|
|
cerr << get_fileline() << ": sorry: "
|
|
<< "Forgot to implement NetCondit::synth_async" << endl;
|
|
des->errors += 1;
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool NetEvWait::synth_async(Design*des, NetScope*scope,
|
|
const NetBus&nex_map, NetBus&nex_out)
|
|
{
|
|
bool flag = statement_->synth_async(des, scope, nex_map, nex_out);
|
|
return flag;
|
|
}
|
|
|
|
/*
|
|
* 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() << ": debug: Process has "
|
|
<< nex_set.count() << " outputs." << endl;
|
|
}
|
|
|
|
NetBus nex_q (scope(), nex_set.count());
|
|
for (unsigned idx = 0 ; idx < nex_set.count() ; idx += 1) {
|
|
connect(nex_set[idx], nex_q.pin(idx));
|
|
}
|
|
|
|
bool flag = statement_->synth_async(des, scope(), nex_q, nex_q);
|
|
return flag;
|
|
}
|
|
|
|
/*
|
|
* 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
|
|
* <statement1>
|
|
* <statement2>
|
|
* ...
|
|
* end
|
|
*
|
|
* This needs to be split into a DFF bank for each statement, because
|
|
* the statements may each infer different reset and enable signals.
|
|
*/
|
|
bool NetBlock::synth_sync(Design*des, NetScope*scope, NetFF*ff,
|
|
const NetBus&nex_map, NetBus&nex_out,
|
|
const svector<NetEvProbe*>&events_in)
|
|
{
|
|
if (last_ == 0) {
|
|
return true;
|
|
}
|
|
|
|
#if 0
|
|
bool flag = true;
|
|
|
|
const perm_string tmp1 = perm_string::literal("tmp1");
|
|
const perm_string tmp2 = perm_string::literal("tmp2");
|
|
|
|
/* Keep an accounting of which statement accounts for which
|
|
bit slice of the FF bank. This is used for error checking. */
|
|
NetProc**pin_accounting = new NetProc* [ff->pin_count()];
|
|
for (unsigned idx = 0 ; idx < ff->pin_count() ; idx += 1)
|
|
pin_accounting[idx] = 0;
|
|
|
|
NetProc*cur = last_;
|
|
do {
|
|
cur = cur->next_;
|
|
|
|
/* Create a temporary nex_map for the substatement. */
|
|
NexusSet tmp_set;
|
|
cur->nex_output(tmp_set);
|
|
NetNet*tmp_map = new NetNet(scope, tmp1, NetNet::WIRE,
|
|
tmp_set.count());
|
|
for (unsigned idx = 0 ; idx < tmp_map->pin_count() ; idx += 1)
|
|
connect(tmp_set[idx], tmp_map->pin(idx));
|
|
|
|
/* NOTE: After this point, tmp_set should not be used as
|
|
the various functions I call do a lot of connecting,
|
|
and the nexa in the tmp_set may get realloced. Use
|
|
the tmp_map instead. */
|
|
|
|
/* Create also a temporary net_out to collect the
|
|
output. The tmp1 and tmp2 map and out sets together
|
|
are used to collect the outputs from the substatement
|
|
for the inputs of the FF bank. */
|
|
NetNet*tmp_out = new NetNet(scope, tmp2, NetNet::WIRE,
|
|
tmp_map->pin_count());
|
|
|
|
verinum tmp_aset = ff->aset_value();
|
|
verinum tmp_sset = ff->sset_value();
|
|
|
|
/* Create a new DFF to handle this part of the begin-end
|
|
block. Connect this NetFF to the associated pins of
|
|
the existing wide NetFF device. While I'm at it, also
|
|
copy the aset_value bits for the new ff device. */
|
|
NetFF*ff2 = new NetFF(scope, scope->local_symbol(),
|
|
tmp_out->pin_count());
|
|
des->add_node(ff2);
|
|
|
|
verinum aset_value2 (verinum::V1, ff2->width());
|
|
verinum sset_value2 (verinum::V1, ff2->width());
|
|
for (unsigned idx = 0 ; idx < ff2->width() ; idx += 1) {
|
|
unsigned ptr = find_nexus_in_set(nex_map,
|
|
tmp_map->pin(idx).nexus());
|
|
|
|
/* Connect Data and Q bits to the new FF. */
|
|
connect(ff->pin_Data(ptr), ff2->pin_Data(idx));
|
|
connect(ff->pin_Q(ptr), ff2->pin_Q(idx));
|
|
|
|
/* Copy the asynch set bit to the new device. */
|
|
if (ptr < tmp_aset.len())
|
|
aset_value2.set(idx, tmp_aset[ptr]);
|
|
|
|
/* Copy the synch set bit to the new device. */
|
|
if (ptr < tmp_sset.len())
|
|
sset_value2.set(idx, tmp_sset[ptr]);
|
|
|
|
if (pin_accounting[ptr] != 0) {
|
|
cerr << cur->get_line() << ": error: "
|
|
<< "Synchronous output conflicts with "
|
|
<< pin_accounting[ptr]->get_line()
|
|
<< "." << endl;
|
|
flag = false;
|
|
|
|
} else {
|
|
pin_accounting[ptr] = cur;
|
|
}
|
|
}
|
|
|
|
if (ff->pin_Aclr().is_linked())
|
|
connect(ff->pin_Aclr(), ff2->pin_Aclr());
|
|
if (ff->pin_Aset().is_linked())
|
|
connect(ff->pin_Aset(), ff2->pin_Aset());
|
|
if (ff->pin_Sclr().is_linked())
|
|
connect(ff->pin_Sclr(), ff2->pin_Sclr());
|
|
if (ff->pin_Sset().is_linked())
|
|
connect(ff->pin_Sset(), ff2->pin_Sset());
|
|
if (ff->pin_Clock().is_linked())
|
|
connect(ff->pin_Clock(), ff2->pin_Clock());
|
|
if (ff->pin_Enable().is_linked())
|
|
connect(ff->pin_Enable(),ff2->pin_Enable());
|
|
|
|
/* Remember to store the aset value into the new FF. If
|
|
this leads to an Aset value of 0 (and Aclr is not
|
|
otherwise used) then move the Aset input to Aclr. */
|
|
if (tmp_aset.len() == ff->width()) {
|
|
|
|
if (aset_value2.is_zero()
|
|
&& ff2->pin_Aset().is_linked()
|
|
&& !ff2->pin_Aclr().is_linked()) {
|
|
|
|
connect(ff2->pin_Aclr(), ff2->pin_Aset());
|
|
ff2->pin_Aset().unlink();
|
|
|
|
} else {
|
|
ff2->aset_value(aset_value2);
|
|
}
|
|
}
|
|
|
|
/* 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, ff2, tmp_map,
|
|
tmp_out, events_in);
|
|
flag = flag && ok_flag;
|
|
|
|
if (ok_flag == false)
|
|
continue;
|
|
|
|
/* Use the nex_map to link up the output from the
|
|
substatement to the output of the block as a
|
|
whole. It is occasionally possible to have outputs
|
|
beyond the input set, for example when the l-value of
|
|
an assignment is smaller then the r-value. */
|
|
for (unsigned idx = 0 ; idx < tmp_out->pin_count() ; idx += 1) {
|
|
unsigned ptr = find_nexus_in_set(nex_map,
|
|
tmp_map->pin(idx).nexus());
|
|
|
|
if (ptr < nex_out->pin_count())
|
|
connect(nex_out->pin(ptr), tmp_out->pin(idx));
|
|
}
|
|
|
|
delete tmp_map;
|
|
delete tmp_out;
|
|
|
|
} while (cur != last_);
|
|
|
|
delete[]pin_accounting;
|
|
|
|
/* Done. The large NetFF is no longer needed, as it has been
|
|
taken up by the smaller NetFF devices. */
|
|
delete ff;
|
|
|
|
return flag;
|
|
|
|
#else
|
|
cerr << get_fileline() << ": sorry: "
|
|
<< "Forgot to implement NetBlock::synth_sync"
|
|
<< endl;
|
|
des->errors += 1;
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* 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, NetFF*ff,
|
|
const NetBus&nex_map, NetBus&nex_out,
|
|
const svector<NetEvProbe*>&events_in)
|
|
{
|
|
#if 0
|
|
/* 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.count() ; idx += 1) {
|
|
|
|
NetEvProbe*ev = events_in[idx];
|
|
NexusSet pin_set;
|
|
pin_set.add(ev->pin(0).nexus());
|
|
|
|
if (! expr_input->contains(pin_set))
|
|
continue;
|
|
|
|
/* Ah, this edge is in the sensitivity list for the
|
|
expression, so we have an asynchronous
|
|
input. Synthesize the set/reset input expression. */
|
|
|
|
NetNet*rst = expr_->synthesize(des);
|
|
assert(rst->pin_count() == 1);
|
|
|
|
/* XXXX I really should find a way to check that the
|
|
edge used on the reset input is correct. This would
|
|
involve interpreting the expression that is fed by the
|
|
reset expression. */
|
|
//assert(ev->edge() == NetEvProbe::POSEDGE);
|
|
|
|
/* 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);
|
|
|
|
assert(if_ != 0);
|
|
bool flag = if_->synth_async(des, scope, nex_map, asig);
|
|
|
|
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 Aclr 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_Aclr(), rst->pin(0));
|
|
|
|
} else {
|
|
connect(ff->pin_Aset(), rst->pin(0));
|
|
ff->aset_value(tmp);
|
|
}
|
|
|
|
delete asig;
|
|
delete expr_input;
|
|
|
|
assert(events_in.count() == 1);
|
|
assert(else_ != 0);
|
|
flag = else_->synth_sync(des, scope, ff, nex_map,
|
|
nex_out, svector<NetEvProbe*>(0))
|
|
&& flag;
|
|
DEBUG_SYNTH2_EXIT("NetCondit",flag)
|
|
return flag;
|
|
}
|
|
|
|
delete expr_input;
|
|
|
|
/* 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<NetEvProbe*>(0))
|
|
&& flag;
|
|
DEBUG_SYNTH2_EXIT("NetCondit",flag)
|
|
return flag;
|
|
}
|
|
}
|
|
|
|
delete a_set;
|
|
|
|
/* Failed to find an asynchronous set/reset, so any events
|
|
input are probably in error. */
|
|
if (events_in.count() > 0) {
|
|
cerr << get_line() << ": error: Events are unaccounted"
|
|
<< " for in process synthesis." << endl;
|
|
des->errors += 1;
|
|
}
|
|
|
|
|
|
/* If this is an if/then/else, then it is likely a
|
|
combinational if, and I should synthesize it that way. */
|
|
if (if_ && else_) {
|
|
bool flag = synth_async(des, scope, nex_map, nex_out);
|
|
DEBUG_SYNTH2_EXIT("NetCondit",flag)
|
|
return flag;
|
|
}
|
|
|
|
assert(if_);
|
|
assert(!else_);
|
|
|
|
/* Synthesize the enable expression. */
|
|
NetNet*ce = expr_->synthesize(des);
|
|
assert(ce->pin_count() == 1);
|
|
|
|
/* What's left, is a synchronous CE statement like this:
|
|
|
|
if (expr_) <true statement>;
|
|
|
|
The expr_ expression has already been synthesized to the ce
|
|
net, so we connect it here to the FF. What's left is to
|
|
synthesize the substatement as a combinational
|
|
statement.
|
|
|
|
Watch out for the special case that there is already a CE
|
|
connected to this FF. This can be caused by code like this:
|
|
|
|
if (a) if (b) <statement>;
|
|
|
|
In this case, we are working on the inner IF, so we AND the
|
|
a and b expressions to make a new CE. */
|
|
|
|
if (ff->pin_Enable().is_linked()) {
|
|
NetLogic*ce_and = new NetLogic(scope,
|
|
scope->local_symbol(), 3,
|
|
NetLogic::AND, 1);
|
|
des->add_node(ce_and);
|
|
connect(ff->pin_Enable(), ce_and->pin(1));
|
|
connect(ce->pin(0), ce_and->pin(2));
|
|
|
|
ff->pin_Enable().unlink();
|
|
connect(ff->pin_Enable(), ce_and->pin(0));
|
|
|
|
NetNet*tmp = new NetNet(scope, scope->local_symbol(),
|
|
NetNet::IMPLICIT, 1);
|
|
tmp->local_flag(true);
|
|
connect(ff->pin_Enable(), tmp->pin(0));
|
|
|
|
} else {
|
|
|
|
connect(ff->pin_Enable(), ce->pin(0));
|
|
}
|
|
|
|
bool flag = if_->synth_sync(des, scope, ff, nex_map, nex_out, events_in);
|
|
|
|
return flag;
|
|
|
|
#else
|
|
cerr << get_fileline() << ": sorry: "
|
|
<< "Forgot to implement NetCondit::synth_sync" << endl;
|
|
des->errors += 1;
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool NetEvWait::synth_sync(Design*des, NetScope*scope, NetFF*ff,
|
|
const NetBus&nex_map, NetBus&nex_out,
|
|
const svector<NetEvProbe*>&events_in)
|
|
{
|
|
if (events_in.count() > 0) {
|
|
cerr << get_fileline() << ": error: Events are unaccounted"
|
|
<< " for in process synthesis." << endl;
|
|
des->errors += 1;
|
|
}
|
|
|
|
assert(events_in.count() == 0);
|
|
|
|
/* This can't be other than one unless there are named events,
|
|
which I cannot synthesize. */
|
|
assert(nevents_ == 1);
|
|
NetEvent*ev = events_[0];
|
|
|
|
assert(ev->nprobe() >= 1);
|
|
svector<NetEvProbe*>events (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() );
|
|
|
|
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;
|
|
return false;
|
|
}
|
|
|
|
connect(ff->pin_Clock(), pclk->pin(0));
|
|
if (pclk->edge() == NetEvProbe::NEGEDGE) {
|
|
perm_string polarity = perm_string::literal("Clock:LPM_Polarity");
|
|
ff->attribute(polarity, verinum("INVERT"));
|
|
|
|
if (debug_synth2) {
|
|
cerr << get_fileline() << ": debug: "
|
|
<< "Detected a NEGEDGE clock for the synthesized ff."
|
|
<< endl;
|
|
}
|
|
}
|
|
|
|
/* Synthesize the input to the DFF. */
|
|
bool flag = statement_->synth_sync(des, scope, ff,
|
|
nex_map, nex_out, events);
|
|
|
|
return flag;
|
|
}
|
|
|
|
/*
|
|
* 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() << ": debug: "
|
|
<< "Process is apparently synchronous. Making NetFFs."
|
|
<< endl;
|
|
}
|
|
|
|
NexusSet nex_set;
|
|
statement_->nex_output(nex_set);
|
|
|
|
/* 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. */
|
|
|
|
if (debug_synth2) {
|
|
cerr << get_fileline() << ": debug: "
|
|
<< "Top level making a "
|
|
<< nex_set[0]->vector_width() << "-wide "
|
|
<< "NetFF device." << endl;
|
|
}
|
|
|
|
NetFF*ff = new NetFF(scope(), scope()->local_symbol(),
|
|
nex_set[0]->vector_width());
|
|
des->add_node(ff);
|
|
ff->attribute(perm_string::literal("LPM_FFType"), verinum("DFF"));
|
|
|
|
NetBus nex_d (scope(), nex_set.count());
|
|
NetBus nex_q (scope(), nex_set.count());
|
|
|
|
/* The Q of the NetFF devices is connected to the output that
|
|
we are. The nex_q is a bundle of the outputs. We will also
|
|
pass the nex_q as a map to the statement's synth_sync
|
|
method to map it to the correct nex_d pin. */
|
|
for (unsigned idx = 0 ; idx < nex_set.count() ; idx += 1) {
|
|
connect(nex_set[idx], nex_q.pin(idx));
|
|
}
|
|
|
|
// Connect the input later.
|
|
|
|
/* Synthesize the input to the DFF. */
|
|
bool flag = statement_->synth_sync(des, scope(), ff,
|
|
nex_q, nex_d,
|
|
svector<NetEvProbe*>());
|
|
if (! flag) {
|
|
delete ff;
|
|
return false;
|
|
}
|
|
|
|
|
|
NetNet*tmp = nex_d.pin(0).nexus()->pick_any_net();
|
|
assert(tmp);
|
|
|
|
tmp = crop_to_width(des, tmp, ff->width());
|
|
connect(tmp->pin(0), ff->pin_Data());
|
|
connect(nex_q.pin(0), ff->pin_Q());
|
|
|
|
for (unsigned idx = 1 ; idx < nex_set.count() ; idx += 1) {
|
|
NetFF*ff2 = new NetFF(scope(), scope()->local_symbol(),
|
|
nex_set[idx]->vector_width());
|
|
des->add_node(ff2);
|
|
|
|
tmp = nex_d.pin(idx).nexus()->pick_any_net();
|
|
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(ff->pin_Clock(), ff2->pin_Clock());
|
|
if (ff->pin_Enable().is_linked())
|
|
connect(ff->pin_Enable(), ff2->pin_Enable());
|
|
if (ff->pin_Aset().is_linked())
|
|
connect(ff->pin_Aset(), ff2->pin_Aset());
|
|
if (ff->pin_Aclr().is_linked())
|
|
connect(ff->pin_Aclr(), ff2->pin_Aclr());
|
|
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());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
class synth2_f : public functor_t {
|
|
|
|
public:
|
|
void process(class Design*, class 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(class Design*des, class 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;
|
|
|
|
if (top->is_synchronous()) do {
|
|
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;
|
|
} while (0);
|
|
|
|
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() << ": internal error: "
|
|
<< "is_asynchronous does not match "
|
|
<< "sync_async results." << endl;
|
|
des->errors += 1;
|
|
return;
|
|
}
|
|
|
|
des->delete_process(top);
|
|
}
|
|
|
|
void synth2(Design*des)
|
|
{
|
|
synth2_f synth_obj;
|
|
des->functor(&synth_obj);
|
|
}
|