Merge pull request #1369 from larsclausen/unpacked-array-assign-strength-delay

Preserve delay and strength in unpacked array continuous assignments
This commit is contained in:
Cary R. 2026-05-20 18:49:41 -07:00 committed by GitHub
commit 129a5c980f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 322 additions and 210 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999-2025 Stephen Williams (steve@icarus.com)
* Copyright (c) 1999-2026 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
@ -130,10 +130,11 @@ static NetExpr* make_delay_nets(Design*des, NetScope*scope, NetExpr*expr)
return expr;
}
static NetExpr* calc_decay_time(NetExpr *rise, NetExpr *fall)
static const NetExpr *calc_decay_time(const NetExpr *rise,
const NetExpr *fall)
{
const NetEConst *c_rise = dynamic_cast<NetEConst*>(rise);
const NetEConst *c_fall = dynamic_cast<NetEConst*>(fall);
const NetEConst *c_rise = dynamic_cast<const NetEConst*>(rise);
const NetEConst *c_fall = dynamic_cast<const NetEConst*>(fall);
if (c_rise && c_fall) {
if (c_rise->value() < c_fall->value()) return rise;
else return fall;
@ -142,44 +143,43 @@ static NetExpr* calc_decay_time(NetExpr *rise, NetExpr *fall)
return 0;
}
void PDelays::eval_delays(Design*des, NetScope*scope,
NetExpr*&rise_time,
NetExpr*&fall_time,
NetExpr*&decay_time,
void PDelays::eval_delays(Design*des, NetScope*scope, delay_exprs_t &delays,
bool as_nets_flag) const
{
assert(scope);
if (delay_[0]) {
rise_time = calculate_val(des, scope, delay_[0]);
NetExpr *rise = calculate_val(des, scope, delay_[0]);
if (as_nets_flag)
rise_time = make_delay_nets(des, scope, rise_time);
rise = make_delay_nets(des, scope, rise);
delays.rise = rise;
if (delay_[1]) {
fall_time = calculate_val(des, scope, delay_[1]);
NetExpr *fall = calculate_val(des, scope, delay_[1]);
if (as_nets_flag)
fall_time = make_delay_nets(des, scope, fall_time);
fall = make_delay_nets(des, scope, fall);
delays.fall = fall;
if (delay_[2]) {
decay_time = calculate_val(des, scope, delay_[2]);
NetExpr *decay = calculate_val(des, scope, delay_[2]);
if (as_nets_flag)
decay_time = make_delay_nets(des, scope,
decay_time);
decay = make_delay_nets(des, scope, decay);
delays.decay = decay;
} else {
// If this is zero then we need to do the min()
// at run time.
decay_time = calc_decay_time(rise_time, fall_time);
delays.decay = calc_decay_time(delays.rise,
delays.fall);
}
} else {
assert(delay_[2] == 0);
fall_time = rise_time;
decay_time = rise_time;
delays.fall = delays.rise;
delays.decay = delays.rise;
}
} else {
rise_time = 0;
fall_time = 0;
decay_time = 0;
delays.rise = nullptr;
delays.fall = nullptr;
delays.decay = nullptr;
}
}

View File

@ -1,7 +1,7 @@
#ifndef IVL_PDelays_H
#define IVL_PDelays_H
/*
* Copyright (c) 1999-2021 Stephen Williams (steve@icarus.com)
* Copyright (c) 1999-2026 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
@ -27,6 +27,7 @@ class Design;
class NetScope;
class NetExpr;
class PExpr;
struct delay_exprs_t;
/*
* Various PForm objects can carry delays. These delays include rise,
@ -46,10 +47,7 @@ class PDelays {
unsigned delay_count() const;
void eval_delays(Design*des, NetScope*scope,
NetExpr*&rise_time,
NetExpr*&fall_time,
NetExpr*&decay_time,
void eval_delays(Design*des, NetScope*scope, delay_exprs_t &delays,
bool as_nets_flag =false) const;
void dump_delays(std::ostream&out) const;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999-2025 Stephen Williams (steve@icarus.com)
* Copyright (c) 1999-2026 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
@ -41,29 +41,23 @@ void PGate::set_pins_(list<PExpr*>*pins)
}
PGate::PGate(perm_string name, list<PExpr*>*pins, const list<PExpr*>*del)
: name_(name), pins_(pins? pins->size() : 0), ranges_(0)
: name_(name), pins_(pins? pins->size() : 0), ranges_(nullptr)
{
if (pins) set_pins_(pins);
if (del) delay_.set_delays(del);
str0_ = IVL_DR_STRONG;
str1_ = IVL_DR_STRONG;
}
PGate::PGate(perm_string name, list<PExpr*>*pins, PExpr*del)
: name_(name), pins_(pins? pins->size() : 0), ranges_(0)
: name_(name), pins_(pins? pins->size() : 0), ranges_(nullptr)
{
if (pins) set_pins_(pins);
if (del) delay_.set_delay(del);
str0_ = IVL_DR_STRONG;
str1_ = IVL_DR_STRONG;
}
PGate::PGate(perm_string name, list<PExpr*>*pins)
: name_(name), pins_(pins? pins->size() : 0), ranges_(0)
: name_(name), pins_(pins? pins->size() : 0), ranges_(nullptr)
{
if (pins) set_pins_(pins);
str0_ = IVL_DR_STRONG;
str1_ = IVL_DR_STRONG;
}
PGate::~PGate()
@ -76,24 +70,14 @@ void PGate::set_ranges(list<pform_range_t>*ranges)
ranges_ = ranges;
}
ivl_drive_t PGate::strength0() const
drive_strength_t PGate::strength() const
{
return str0_;
return strength_;
}
void PGate::strength0(ivl_drive_t s)
void PGate::strength(const drive_strength_t &str)
{
str0_ = s;
}
ivl_drive_t PGate::strength1() const
{
return str1_;
}
void PGate::strength1(ivl_drive_t s)
{
str1_ = s;
strength_ = str;
}
void PGate::elaborate_scope(Design*, NetScope*) const
@ -109,15 +93,10 @@ void PGate::elaborate_scope(Design*, NetScope*) const
* numbers of expressions.
*/
void PGate::eval_delays(Design*des, NetScope*scope,
NetExpr*&rise_expr,
NetExpr*&fall_expr,
NetExpr*&decay_expr,
void PGate::eval_delays(Design*des, NetScope*scope, delay_exprs_t &delays,
bool as_net_flag) const
{
delay_.eval_delays(des, scope,
rise_expr, fall_expr, decay_expr,
as_net_flag);
delay_.eval_delays(des, scope, delays, as_net_flag);
}
unsigned PGate::delay_count() const

19
PGate.h
View File

@ -31,6 +31,8 @@
class PExpr;
class PUdp;
class Module;
struct delay_exprs_t;
struct drive_strength_t;
/*
* A PGate represents a Verilog gate. The gate has a name and other
@ -66,10 +68,7 @@ class PGate : public PNamedItem {
// This evaluates the delays as far as possible, but returns
// an expression, and do not signal errors.
void eval_delays(Design*des, NetScope*scope,
NetExpr*&rise_time,
NetExpr*&fall_time,
NetExpr*&decay_time,
void eval_delays(Design*des, NetScope*scope, delay_exprs_t &delays,
bool as_net_flag =false) const;
unsigned delay_count() const;
@ -77,11 +76,9 @@ class PGate : public PNamedItem {
unsigned pin_count() const { return pins_.size(); }
PExpr*pin(unsigned idx) const { return pins_[idx]; }
ivl_drive_t strength0() const;
ivl_drive_t strength1() const;
drive_strength_t strength() const;
void strength0(ivl_drive_t);
void strength1(ivl_drive_t);
void strength(const drive_strength_t &str);
std::map<perm_string,PExpr*> attributes;
@ -109,7 +106,7 @@ class PGate : public PNamedItem {
std::list<pform_range_t>*ranges_;
ivl_drive_t str0_, str1_;
drive_strength_t strength_;
void set_pins_(std::list<PExpr*>*pins);
@ -133,7 +130,9 @@ class PGAssign : public PGate {
virtual void elaborate(Design*des, NetScope*scope) const override;
private:
void elaborate_unpacked_array_(Design*des, NetScope*scope, NetNet*lval) const;
void elaborate_unpacked_array_(Design*des, NetScope*scope, NetNet*lval,
const drive_strength_t &drive,
const delay_exprs_t &delays) const;
};

View File

@ -180,9 +180,7 @@ void cprop_functor::lpm_mux(Design*des, NetMux*obj)
<< "Replace binary MUX with constant select=" << sel_val
<< " with a BUFZ to the selected input." << endl;
tmp->rise_time(obj->rise_time());
tmp->fall_time(obj->fall_time());
tmp->decay_time(obj->decay_time());
tmp->delay_times(obj->delay_times());
connect(tmp->pin(0), obj->pin_Result());
if (sel_val == verinum::V1)

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998-2021 Stephen Williams (steve@icarus.com)
* Copyright (c) 1998-2026 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
@ -90,6 +90,30 @@ ostream& operator << (ostream&o, ivl_drive_t str)
return o;
}
ostream &operator << (ostream &o, const drive_strength_t &strength)
{
o << strength.drive0 << "0 " << strength.drive1 << "1";
return o;
}
static void dump_delay_expr(ostream &o, const NetExpr *expr)
{
if (expr)
o << *expr;
else
o << "0";
}
ostream &operator << (ostream &o, const delay_exprs_t &delays)
{
dump_delay_expr(o, delays.rise);
o << ",";
dump_delay_expr(o, delays.fall);
o << ",";
dump_delay_expr(o, delays.decay);
return o;
}
ostream& operator << (ostream&o, ivl_variable_type_t val)
{
switch (val) {
@ -454,8 +478,7 @@ void NetNet::dump_net(ostream&o, unsigned ind) const
o << " (eref=" << peek_eref() << ", lref=" << peek_lref() << ")";
if (scope())
o << " scope=" << scope_path(scope());
o << " #(" << rise_time() << "," << fall_time() << ","
<< decay_time() << ") vector_width=" << vector_width()
o << " #(" << delay_times() << ") vector_width=" << vector_width()
<< " pin_count=" << pin_count();
if (pins_are_virtual()) {
o << " pins_are_virtual" << endl;
@ -486,8 +509,7 @@ void NetNet::dump_net(ostream&o, unsigned ind) const
void NetNode::dump_node(ostream&o, unsigned ind) const
{
o << setw(ind) << "" << "node: ";
o << typeid(*this).name() << " #(" << rise_time()
<< "," << fall_time() << "," << decay_time() << ") " << name()
o << typeid(*this).name() << " #(" << delay_times() << ") " << name()
<< endl;
dump_node_pins(o, ind+4);
@ -518,8 +540,7 @@ void NetPins::dump_node_pins(ostream&o, unsigned ind, const char**pin_names) con
break;
}
o << " (" << pin(idx).drive0() << "0 "
<< pin(idx).drive1() << "1): ";
o << " (" << pin(idx).drive() << "): ";
if (pin(idx).is_linked()) {
const Nexus*nex = pin(idx).nexus();
@ -622,11 +643,7 @@ void NetConcat::dump_node(ostream&o, unsigned ind) const
o << setw(ind) << "" << "NetConcat: ";
o << name();
if (rise_time())
o << " #(" << *rise_time()
<< "," << *fall_time() << "," << *decay_time() << ")";
else
o << " #(0,0,0)";
o << " #(" << delay_times() << ")";
o << " scope=" << scope_path(scope())
<< " width=" << width_ << endl;
dump_node_pins(o, ind+4);
@ -651,14 +668,7 @@ void NetPow::dump_node(ostream&o, unsigned ind) const
{
o << setw(ind) << "" << "LPM_POW (NetPow): " << name()
<< " scope=" << scope_path(scope())
<< " delay=(";
if (rise_time())
o << *rise_time() << "," << *fall_time() << ","
<< *decay_time();
else
o << "0,0,0";
o << ")" << endl;
<< " delay=(" << delay_times() << ")" << endl;
dump_node_pins(o, ind+4);
dump_obj_attr(o, ind+4);
}
@ -676,8 +686,7 @@ void NetBUFZ::dump_node(ostream&o, unsigned ind) const
{
o << setw(ind) << "" << "NetBUFZ: " << name()
<< " scope=" << scope_path(scope())
<< " delay=(" << rise_time() << "," << fall_time() << "," <<
decay_time() << ") width=" << width()
<< " delay=(" << delay_times() << ") width=" << width()
<< (transparent()? " " : " non-") << "transparent" << endl;
dump_node_pins(o, ind+4);
}
@ -693,10 +702,8 @@ void NetConst::dump_node(ostream&o, unsigned ind) const
{
o << setw(ind) << "" << "constant " << value_;
o << ": " << name();
if (rise_time())
o << " #(" << *rise_time()
<< "," << *fall_time()
<< "," << *decay_time() << ")";
if (delay_times().has_delay())
o << " #(" << delay_times() << ")";
else
o << " #(.,.,.)";
o << endl;
@ -729,10 +736,8 @@ void NetLiteral::dump_node(ostream&o, unsigned ind) const
{
o << setw(ind) << "" << "constant real " << real_
<< ": " << name();
if (rise_time())
o << " #(" << *rise_time()
<< "," << *fall_time()
<< "," << *decay_time() << ")";
if (delay_times().has_delay())
o << " #(" << delay_times() << ")";
else
o << " #(.,.,.)";
o << endl;
@ -810,8 +815,7 @@ void NetLogic::dump_node(ostream&o, unsigned ind) const
o << "xor";
break;
}
o << " #(" << rise_time()
<< "," << fall_time() << "," << decay_time() << ") " << name()
o << " #(" << delay_times() << ") " << name()
<< " scope=" << scope_path(scope())
<< endl;
@ -839,10 +843,8 @@ void NetPartSelect::dump_node(ostream&o, unsigned ind) const
}
o << setw(ind) << "" << "NetPartSelect(" << pt << "): "
<< name();
if (rise_time())
o << " #(" << *rise_time()
<< "," << *fall_time()
<< "," << *decay_time() << ")";
if (delay_times().has_delay())
o << " #(" << delay_times() << ")";
else
o << " #(.,.,.)";
o << " off=" << off_ << " wid=" << wid_ <<endl;
@ -854,10 +856,8 @@ void NetSubstitute::dump_node(ostream&fd, unsigned ind) const
{
fd << setw(ind) << "" << "NetSubstitute: "
<< name();
if (rise_time())
fd << " #(" << *rise_time()
<< "," << *fall_time()
<< "," << *decay_time() << ")";
if (delay_times().has_delay())
fd << " #(" << delay_times() << ")";
else
fd << " #(.,.,.)";
fd << " width=" << wid_ << " base=" << off_ <<endl;
@ -877,10 +877,8 @@ void NetReplicate::dump_node(ostream&o, unsigned ind) const
void NetSignExtend::dump_node(ostream&o, unsigned ind) const
{
o << setw(ind) << "" << "NetSignExtend: " << name();
if (rise_time())
o << " #(" << *rise_time()
<< "," << *fall_time()
<< "," << *decay_time() << ")";
if (delay_times().has_delay())
o << " #(" << delay_times() << ")";
else
o << " #(.,.,.)";
o << " output width=" << width_ << endl;
@ -914,8 +912,7 @@ void NetUReduce::dump_node(ostream&o, unsigned ind) const
o << "xnor";
break;
}
o << " #(" << rise_time()
<< "," << fall_time() << "," << decay_time() << ") " << name()
o << " #(" << delay_times() << ") " << name()
<< " scope=" << scope_path(scope())
<< endl;
@ -935,10 +932,8 @@ void NetUserFunc::dump_node(ostream&o, unsigned ind) const
{
o << setw(ind) << "" << "USER FUNC: "
<< scope_path(def_);
if (rise_time())
o << " #(" <<*rise_time()
<<","<<*fall_time()
<< "," <<*decay_time() << ")";
if (delay_times().has_delay())
o << " #(" << delay_times() << ")";
o << endl;
dump_node_pins(o, ind+4);
dump_obj_attr(o, ind+4);
@ -986,14 +981,7 @@ void NetTran::dump_node(ostream&o, unsigned ind) const
<< " part=" << part_width()
<< " offset=" << part_offset();
}
o << " delay=(";
if (rise_time())
o << *rise_time() << "," << *fall_time() << ","
<< *decay_time();
else
o << "0,0,0";
o << ")" << endl;
o << " delay=(" << delay_times() << ")" << endl;
dump_node_pins(o, ind+4);
dump_obj_attr(o, ind+4);
}
@ -1001,8 +989,7 @@ void NetTran::dump_node(ostream&o, unsigned ind) const
void NetUDP::dump_node(ostream&o, unsigned ind) const
{
o << setw(ind) << "" << "UDP (" << udp_name() << "): ";
o << " #(" << rise_time() << "," << fall_time() << "," << decay_time() <<
") " << name() << endl;
o << " #(" << delay_times() << ") " << name() << endl;
dump_node_pins(o, ind+4);
dump_obj_attr(o, ind+4);

View File

@ -1197,8 +1197,8 @@ NetNet* PWire::elaborate_sig(Design*des, NetScope*scope)
pull = new NetLogic(scope, scope->local_symbol(),
1, pull_type, wid);
pull->set_line(*this);
pull->pin(0).drive0(IVL_DR_SUPPLY);
pull->pin(0).drive1(IVL_DR_SUPPLY);
pull->pin(0).drive(drive_strength_t(IVL_DR_SUPPLY,
IVL_DR_SUPPLY));
des->add_node(pull);
wtype = NetNet::WIRE;
}

View File

@ -115,19 +115,16 @@ void PGAssign::elaborate(Design*des, NetScope*scope) const
{
ivl_assert(*this, scope);
NetExpr* rise_time, *fall_time, *decay_time;
eval_delays(des, scope, rise_time, fall_time, decay_time, true);
ivl_drive_t drive0 = strength0();
ivl_drive_t drive1 = strength1();
drive_strength_t drive = strength();
delay_exprs_t delays;
eval_delays(des, scope, delays, true);
ivl_assert(*this, pin(0));
ivl_assert(*this, pin(1));
/* Elaborate the l-value. */
// A continuous assignment can drive a variable if the default strength is used.
bool var_allowed_in_sv = (drive0 == IVL_DR_STRONG &&
drive1 == IVL_DR_STRONG) ? true : false;
bool var_allowed_in_sv = !drive.has_drive();
NetNet*lval = pin(0)->elaborate_lnet(des, scope, var_allowed_in_sv);
if (lval == 0) {
return;
@ -136,7 +133,7 @@ void PGAssign::elaborate(Design*des, NetScope*scope) const
// If this turns out to be an assignment to an unpacked array,
// then handle that special case elsewhere.
if (lval->unpacked_dimensions() > 0) {
elaborate_unpacked_array_(des, scope, lval);
elaborate_unpacked_array_(des, scope, lval, drive, delays);
return;
}
@ -214,7 +211,7 @@ void PGAssign::elaborate(Design*des, NetScope*scope) const
/* When we are given a non-default strength value and if the drive
* source is a bit, part, indexed select or a concatenation we need
* to add a driver (BUFZ) to convey the strength information. */
if ((drive0 != IVL_DR_STRONG || drive1 != IVL_DR_STRONG) &&
if (drive.has_drive() &&
((dynamic_cast<NetESelect*>(rval_expr)) ||
(dynamic_cast<NetEConcat*>(rval_expr)))) {
need_driver_flag = true;
@ -242,11 +239,11 @@ void PGAssign::elaborate(Design*des, NetScope*scope) const
/* Set the drive and delays for the r-val. */
if (drive0 != IVL_DR_STRONG || drive1 != IVL_DR_STRONG)
rval->pin(0).drivers_drive(drive0, drive1);
if (drive.has_drive())
rval->pin(0).drivers_drive(drive);
if (rise_time || fall_time || decay_time)
rval->pin(0).drivers_delays(rise_time, fall_time, decay_time);
if (delays.has_delay())
rval->pin(0).drivers_delays(delays);
connect(lval->pin(0), rval->pin(0));
@ -314,11 +311,14 @@ NetNet *elaborate_unpacked_array(Design *des, NetScope *scope, const LineInfo &l
return expr_net;
}
void PGAssign::elaborate_unpacked_array_(Design*des, NetScope*scope, NetNet*lval) const
void PGAssign::elaborate_unpacked_array_(Design*des, NetScope*scope, NetNet*lval,
const drive_strength_t &drive,
const delay_exprs_t &delays) const
{
NetNet *rval_net = elaborate_unpacked_array(des, scope, *this, lval, pin(1));
if (rval_net)
assign_unpacked_with_bufz(des, scope, lval, lval, rval_net);
assign_unpacked_with_bufz(des, scope, lval, lval, rval_net, drive,
delays);
}
void PGBuiltin::calculate_gate_and_lval_count_(unsigned&gate_count,
@ -826,8 +826,9 @@ void PGBuiltin::elaborate(Design*des, NetScope*scope) const
values are given, they are taken as specified. */
if (check_delay_count(des)) return;
NetExpr* rise_time, *fall_time, *decay_time;
eval_delays(des, scope, rise_time, fall_time, decay_time, true);
delay_exprs_t delays;
eval_delays(des, scope, delays, true);
drive_strength_t drive = strength();
struct attrib_list_t*attrib_list;
unsigned attrib_list_n = 0;
@ -859,12 +860,8 @@ void PGBuiltin::elaborate(Design*des, NetScope*scope) const
attrib_list[adx].val);
/* Set the delays and drive strength for all built in gates. */
cur[idx]->rise_time(rise_time);
cur[idx]->fall_time(fall_time);
cur[idx]->decay_time(decay_time);
cur[idx]->pin(0).drive0(strength0());
cur[idx]->pin(0).drive1(strength1());
cur[idx]->delay_times(delays);
cur[idx]->pin(0).drive(drive);
cur[idx]->set_line(*this);
des->add_node(cur[idx]);
@ -2457,7 +2454,7 @@ void PGModule::elaborate_mod_(Design*des, Module*rmod, NetScope*scope) const
void PGModule::elaborate_udp_(Design*des, PUdp*udp, NetScope*scope) const
{
NetExpr*rise_expr =0, *fall_expr =0, *decay_expr =0;
delay_exprs_t delays;
perm_string my_name = get_name();
if (my_name == 0)
@ -2474,8 +2471,7 @@ void PGModule::elaborate_udp_(Design*des, PUdp*udp, NetScope*scope) const
} else {
PDelays tmp_del;
tmp_del.set_delays(overrides_, false);
tmp_del.eval_delays(des, scope, rise_expr, fall_expr,
decay_expr, true);
tmp_del.eval_delays(des, scope, delays, true);
}
}
@ -2494,9 +2490,7 @@ void PGModule::elaborate_udp_(Design*des, PUdp*udp, NetScope*scope) const
ivl_assert(*this, udp);
NetUDP*net = new NetUDP(scope, my_name, udp->ports.size(), udp);
net->set_line(*this);
net->rise_time(rise_expr);
net->fall_time(fall_expr);
net->decay_time(decay_expr);
net->delay_times(delays);
struct attrib_list_t*attrib_list;
unsigned attrib_list_n = 0;

View File

@ -0,0 +1,39 @@
// Check that continuous assignments to unpacked arrays preserve drive strength.
module test;
reg failed;
reg [8*3-1:0] s;
wire driven[0:1];
wire resolved[0:1];
assign (weak1, weak0) driven = '{1'b1, 1'b0};
assign resolved[0] = 1'b0;
assign resolved[1] = 1'b1;
assign (weak1, weak0) resolved = '{1'b1, 1'b0};
`define check_str(val, exp) begin \
$swrite(s, "%v", val); \
if (s != exp) begin \
$display("FAILED(%0d). '%s' expected %s, got %s", `__LINE__, \
`"val`", exp, s); \
failed = 1'b1; \
end \
end
initial begin
failed = 1'b0;
#0;
`check_str(driven[0], "We1");
`check_str(driven[1], "We0");
`check_str(resolved[0], "St0");
`check_str(resolved[1], "St1");
if (!failed) begin
$display("PASSED");
end
end
endmodule

View File

@ -0,0 +1,44 @@
// Check that continuous assignments to unpacked arrays preserve delay.
module test;
reg failed;
wire delayed[0:1];
reg value[0:1];
assign #5 delayed = value;
`define check(val, exp) \
if (val !== exp) begin \
$display("FAILED(%0d). '%s' expected %b, got %b", `__LINE__, \
`"val`", exp, val); \
failed = 1'b1; \
end
initial begin
failed = 1'b0;
value[0] = 1'b1;
value[1] = 1'b0;
#5;
`check(delayed[0], 1'b1)
`check(delayed[1], 1'b0)
value[0] = 1'b0;
value[1] = 1'b1;
#4;
`check(delayed[0], 1'b1)
`check(delayed[1], 1'b0)
#1;
`check(delayed[0], 1'b0)
`check(delayed[1], 1'b1)
if (!failed) begin
$display("PASSED");
end
end
endmodule

View File

@ -242,6 +242,8 @@ sv_array_assign_single_fail1 vvp_tests/sv_array_assign_single_fail1.json
sv_array_cassign6 vvp_tests/sv_array_cassign6.json
sv_array_cassign7 vvp_tests/sv_array_cassign7.json
sv_array_cassign8 vvp_tests/sv_array_cassign8.json
sv_array_cassign9 vvp_tests/sv_array_cassign9.json
sv_array_cassign10 vvp_tests/sv_array_cassign10.json
sv_array_cassign_single vvp_tests/sv_array_cassign_single.json
sv_array_cassign_single_fail1 vvp_tests/sv_array_cassign_single_fail1.json
sv_automatic_2state vvp_tests/sv_automatic_2state.json

View File

@ -0,0 +1,9 @@
{
"type" : "normal",
"source" : "sv_array_cassign10.v",
"iverilog-args" : [ "-g2005-sv" ],
"vlog95" : {
"__comment" : "Array nets are not supported",
"type" : "CE"
}
}

View File

@ -0,0 +1,9 @@
{
"type" : "normal",
"source" : "sv_array_cassign9.v",
"iverilog-args" : [ "-g2005-sv" ],
"vlog95" : {
"__comment" : "Array nets are not supported",
"type" : "CE"
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002-2025 Stephen Williams (steve@icarus.com)
* Copyright (c) 2002-2026 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
@ -41,8 +41,7 @@ NetUserFunc::NetUserFunc(NetScope*s, perm_string n, NetScope*d,
for (unsigned idx = 1 ; idx < pin_count() ; idx += 1) {
pin(idx).set_dir(Link::INPUT);
pin(idx).drive0(IVL_DR_HiZ);
pin(idx).drive1(IVL_DR_HiZ);
pin(idx).drive(drive_strength_t::hiz);
}
}
@ -103,8 +102,7 @@ NetSysFunc::NetSysFunc(NetScope*s, perm_string n,
for (unsigned idx = 1 ; idx < pin_count() ; idx += 1) {
pin(idx).set_dir(Link::INPUT);
pin(idx).drive0(IVL_DR_HiZ);
pin(idx).drive1(IVL_DR_HiZ);
pin(idx).drive(drive_strength_t::hiz);
}
}

View File

@ -164,25 +164,26 @@ Link::DIR Link::get_dir() const
return dir_;
}
void Link::drivers_delays(const NetExpr*rise, const NetExpr*fall, const NetExpr*decay)
void Link::drivers_delays(const delay_exprs_t &delays)
{
find_nexus_()->drivers_delays(rise, fall, decay);
find_nexus_()->drivers_delays(delays);
}
void Link::drivers_drive(ivl_drive_t drive0__, ivl_drive_t drive1__)
void Link::drivers_drive(const drive_strength_t &drive)
{
find_nexus_()->drivers_drive(drive0__, drive1__);
find_nexus_()->drivers_drive(drive);
}
void Link::drive0(ivl_drive_t str)
void Link::drive(const drive_strength_t &drive)
{
drive0_ = str;
drive0_ = drive.drive0;
drive1_ = drive.drive1;
}
void Link::drive1(ivl_drive_t str)
drive_strength_t Link::drive() const
{
drive1_ = str;
return drive_strength_t(drive0_, drive1_);
}
ivl_drive_t Link::drive0() const
@ -358,7 +359,7 @@ bool Nexus::drivers_present() const
return false;
}
void Nexus::drivers_delays(const NetExpr*rise, const NetExpr*fall, const NetExpr*decay)
void Nexus::drivers_delays(const delay_exprs_t &delays)
{
for (Link*cur = first_nlink() ; cur ; cur = cur->next_nlink()) {
if (cur->get_dir() != Link::OUTPUT)
@ -368,20 +369,17 @@ void Nexus::drivers_delays(const NetExpr*rise, const NetExpr*fall, const NetExpr
if (obj == 0)
continue;
obj->rise_time(rise);
obj->fall_time(fall);
obj->decay_time(decay);
obj->delay_times(delays);
}
}
void Nexus::drivers_drive(ivl_drive_t drive0, ivl_drive_t drive1)
void Nexus::drivers_drive(const drive_strength_t &drive)
{
for (Link*cur = first_nlink() ; cur ; cur = cur->next_nlink()) {
if (cur->get_dir() != Link::OUTPUT)
continue;
cur->drive0(drive0);
cur->drive1(drive1);
cur->drive(drive);
}
}

View File

@ -40,6 +40,8 @@
using namespace std;
const drive_strength_t drive_strength_t::hiz(IVL_DR_HiZ, IVL_DR_HiZ);
ostream& operator<< (ostream&o, NetNet::Type t)
{
switch (t) {
@ -237,7 +239,7 @@ bool NetPins::is_linked(void) const
}
NetObj::NetObj(NetScope*s, perm_string n, unsigned np)
: NetPins(np), scope_(s), name_(n), delay1_(0), delay2_(0), delay3_(0)
: NetPins(np), scope_(s), name_(n), delays_()
{
/* Don't
ivl_assert(*this, np > 0);
@ -260,6 +262,11 @@ const NetScope* NetObj::scope() const
return scope_;
}
void NetObj::delay_times(const delay_exprs_t &delays)
{
delays_ = delays;
}
NetNode::NetNode(NetScope*s, perm_string n, unsigned npins)
: NetObj(s, n, npins), node_next_(0), node_prev_(0), design_(0)
{

View File

@ -103,6 +103,42 @@ struct functor_t;
# define ENUM_UNSIGNED_INT
#endif
struct drive_strength_t {
static const drive_strength_t hiz;
explicit drive_strength_t(ivl_drive_t d0 = IVL_DR_STRONG,
ivl_drive_t d1 = IVL_DR_STRONG)
: drive0(d0), drive1(d1)
{ }
bool has_drive() const
{
return drive0 != IVL_DR_STRONG || drive1 != IVL_DR_STRONG;
}
ivl_drive_t drive0;
ivl_drive_t drive1;
};
struct delay_exprs_t {
explicit delay_exprs_t(const NetExpr *r = nullptr,
const NetExpr *f = nullptr,
const NetExpr *d = nullptr)
: rise(r), fall(f), decay(d)
{ }
bool has_delay() const
{
return rise || fall || decay;
}
const NetExpr *rise;
const NetExpr *fall;
const NetExpr *decay;
};
std::ostream &operator << (std::ostream &o, const drive_strength_t &strength);
std::ostream &operator << (std::ostream &o, const delay_exprs_t &delays);
std::ostream& operator << (std::ostream&o, ivl_variable_type_t val);
extern void join_island(NetPins*obj);
@ -126,17 +162,17 @@ class Link {
DIR get_dir() const;
// Set the delay for all the drivers to this nexus.
void drivers_delays(const NetExpr*rise, const NetExpr*fall, const NetExpr*decay);
void drivers_delays(const delay_exprs_t &delays);
// A link has a drive strength for 0 and 1 values. The drive0
// strength is for when the link has the value 0, and drive1
// strength is for when the link has a value 1.
void drive0(ivl_drive_t);
void drive1(ivl_drive_t);
void drive(const drive_strength_t &drive);
drive_strength_t drive() const;
// This sets the drives for all drivers of this link, and not
// just the current link.
void drivers_drive(ivl_drive_t d0, ivl_drive_t d1);
void drivers_drive(const drive_strength_t &drive);
ivl_drive_t drive0() const;
ivl_drive_t drive1() const;
@ -269,13 +305,15 @@ class NetObj : public NetPins, public Attrib {
perm_string name() const { return name_; }
void rename(perm_string n) { name_ = n; }
const NetExpr* rise_time() const { return delay1_; }
const NetExpr* fall_time() const { return delay2_; }
const NetExpr* decay_time() const { return delay3_; }
const NetExpr *rise_time() const { return delays_.rise; }
const NetExpr *fall_time() const { return delays_.fall; }
const NetExpr *decay_time() const { return delays_.decay; }
delay_exprs_t delay_times() const
{
return delays_;
}
void rise_time(const NetExpr* d) { delay1_ = d; }
void fall_time(const NetExpr* d) { delay2_ = d; }
void decay_time(const NetExpr* d) { delay3_ = d; }
void delay_times(const delay_exprs_t &delays);
void dump_obj_attr(std::ostream&, unsigned) const;
@ -284,9 +322,7 @@ class NetObj : public NetPins, public Attrib {
private:
NetScope*scope_;
perm_string name_;
const NetExpr* delay1_;
const NetExpr* delay2_;
const NetExpr* delay3_;
delay_exprs_t delays_;
};
/*
@ -374,8 +410,8 @@ class Nexus {
const char* name() const;
void drivers_delays(const NetExpr*rise, const NetExpr*fall, const NetExpr*decay);
void drivers_drive(ivl_drive_t d0, ivl_drive_t d1);
void drivers_delays(const delay_exprs_t &delays);
void drivers_drive(const drive_strength_t &drive);
Link*first_nlink();
const Link* first_nlink()const;

View File

@ -1619,6 +1619,8 @@ NetExpr*collapse_array_indices(Design*des, NetScope*scope, const NetNet*net,
static void assign_unpacked_with_bufz_dim(Design *des, NetScope *scope,
const LineInfo *loc,
NetNet *lval, NetNet *rval,
const drive_strength_t &drive,
const delay_exprs_t &delays,
const std::vector<long> &stride,
unsigned int dim = 0,
unsigned int idx_l = 0,
@ -1661,11 +1663,19 @@ static void assign_unpacked_with_bufz_dim(Design *des, NetScope *scope,
driver->set_line(*loc);
des->add_node(driver);
connect(lval->pin(idx_l), driver->pin(0));
connect(driver->pin(1), rval->pin(idx_r));
if (drive.has_drive())
driver->pin(0).drive(drive);
if (delays.has_delay())
driver->delay_times(delays);
connect(lval->pin(idx_l), driver->pin(0));
} else {
assign_unpacked_with_bufz_dim(des, scope, loc, lval, rval,
stride, dim + 1, idx_l, idx_r);
drive, delays, stride,
dim + 1, idx_l, idx_r);
}
idx_l += inc_l;
@ -1675,7 +1685,9 @@ static void assign_unpacked_with_bufz_dim(Design *des, NetScope *scope,
void assign_unpacked_with_bufz(Design*des, NetScope*scope,
const LineInfo*loc,
NetNet*lval, NetNet*rval)
NetNet*lval, NetNet*rval,
const drive_strength_t &drive,
const delay_exprs_t &delays)
{
ivl_assert(*loc, lval->pin_count()==rval->pin_count());
@ -1683,7 +1695,8 @@ void assign_unpacked_with_bufz(Design*des, NetScope*scope,
vector<long> stride(dims.size());
make_strides(dims, stride);
assign_unpacked_with_bufz_dim(des, scope, loc, lval, rval, stride);
assign_unpacked_with_bufz_dim(des, scope, loc, lval, rval, drive,
delays, stride);
}
/*

View File

@ -492,7 +492,11 @@ extern NetExpr*collapse_array_exprs(Design*des, NetScope*scope,
extern void assign_unpacked_with_bufz(Design*des, NetScope*scope,
const LineInfo*loc,
NetNet*lval, NetNet*rval);
NetNet*lval, NetNet*rval,
const drive_strength_t &drive =
drive_strength_t(),
const delay_exprs_t &delays =
delay_exprs_t());
extern NetPartSelect* detect_partselect_lval(Link&pin);

View File

@ -2260,8 +2260,7 @@ static void pform_makegate(PGBuiltin::Type type,
// pform_bind_attributes function to keep the attr object.
pform_bind_attributes(cur->attributes, attr, true);
cur->strength0(str.str0);
cur->strength1(str.str1);
cur->strength(drive_strength_t(str.str0, str.str1));
cur->set_line(info);
if (pform_cur_generate) {
@ -2483,8 +2482,7 @@ static PGAssign* pform_make_pgassign(PExpr*lval, PExpr*rval,
else
cur = new PGAssign(wires, del);
cur->strength0(str.str0);
cur->strength1(str.str1);
cur->strength(drive_strength_t(str.str0, str.str1));
if (pform_cur_generate)
pform_cur_generate->add_gate(cur);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998-2025 Stephen Williams (steve@icarus.com)
* Copyright (c) 1998-2026 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
@ -741,7 +741,7 @@ void PGate::dump(ostream&out, unsigned ind) const
void PGAssign::dump(ostream&out, unsigned ind) const
{
out << setw(ind) << "";
out << "assign (" << strength0() << "0 " << strength1() << "1) ";
out << "assign (" << strength() << ") ";
dump_delays(out);
out << " " << *pin(0) << " = " << *pin(1) << ";" << endl;
}
@ -787,7 +787,7 @@ void PGBuiltin::dump(ostream&out, unsigned ind) const
out << "builtin gate ";
}
out << "(" << strength0() << "0 " << strength1() << "1) ";
out << "(" << strength() << ") ";
dump_delays(out);
out << " " << get_name();
dump_ranges(out);