From 85c58d0a7a86363a7be28421c3d5262a29a8ddd4 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sun, 17 May 2026 12:55:30 -0700 Subject: [PATCH 1/3] Use helper types for drive strengths and delays Drive strengths and delays are often handled as a pair of drive values and a rise/fall/decay triple. Add small helper types to carry these groups and use them in the continuous assignment and gate/UDP elaboration paths. Use the same helper types when propagating drive and delay values through netlist links. Also add helpers for dumping the values in debug output. This keeps the behavior consistent and fixes one small bug where some of the debug dumps printed the pointer value for the delays, rather than the actual delay values. Signed-off-by: Lars-Peter Clausen --- PDelays.cc | 44 ++++++++++---------- PDelays.h | 8 ++-- PGate.cc | 41 +++++-------------- PGate.h | 14 +++---- cprop.cc | 4 +- design_dump.cc | 107 ++++++++++++++++++++++--------------------------- elab_sig.cc | 4 +- elaborate.cc | 43 ++++++++------------ net_func.cc | 8 ++-- net_link.cc | 28 ++++++------- netlist.cc | 9 ++++- netlist.h | 66 +++++++++++++++++++++++------- pform.cc | 6 +-- pform_dump.cc | 6 +-- 14 files changed, 187 insertions(+), 201 deletions(-) diff --git a/PDelays.cc b/PDelays.cc index 20645434a..97e1d577b 100644 --- a/PDelays.cc +++ b/PDelays.cc @@ -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(rise); - const NetEConst *c_fall = dynamic_cast(fall); + const NetEConst *c_rise = dynamic_cast(rise); + const NetEConst *c_fall = dynamic_cast(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; } } diff --git a/PDelays.h b/PDelays.h index 30709070d..87a727e57 100644 --- a/PDelays.h +++ b/PDelays.h @@ -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; diff --git a/PGate.cc b/PGate.cc index 6da02060e..944d4e2dd 100644 --- a/PGate.cc +++ b/PGate.cc @@ -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*pins) } PGate::PGate(perm_string name, list*pins, const list*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*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*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*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 diff --git a/PGate.h b/PGate.h index 88e0d8c57..0c832ddd1 100644 --- a/PGate.h +++ b/PGate.h @@ -31,6 +31,7 @@ class PExpr; class PUdp; class Module; +struct delay_exprs_t; /* * A PGate represents a Verilog gate. The gate has a name and other @@ -66,10 +67,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 +75,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 attributes; @@ -109,7 +105,7 @@ class PGate : public PNamedItem { std::list*ranges_; - ivl_drive_t str0_, str1_; + drive_strength_t strength_; void set_pins_(std::list*pins); diff --git a/cprop.cc b/cprop.cc index 9e7864b09..804fa4e91 100644 --- a/cprop.cc +++ b/cprop.cc @@ -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) diff --git a/design_dump.cc b/design_dump.cc index c1e74325d..fe5d9edf3 100644 --- a/design_dump.cc +++ b/design_dump.cc @@ -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_ <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; } diff --git a/elaborate.cc b/elaborate.cc index c78723000..ff448cc95 100644 --- a/elaborate.cc +++ b/elaborate.cc @@ -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; @@ -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(rval_expr)) || (dynamic_cast(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)); @@ -826,8 +823,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 +857,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 +2451,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 +2468,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 +2487,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; diff --git a/net_func.cc b/net_func.cc index 1bd714aa4..755be3ec8 100644 --- a/net_func.cc +++ b/net_func.cc @@ -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); } } diff --git a/net_link.cc b/net_link.cc index 039428ae9..4ef426e48 100644 --- a/net_link.cc +++ b/net_link.cc @@ -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); } } diff --git a/netlist.cc b/netlist.cc index b26c508ec..79b5f6cfb 100644 --- a/netlist.cc +++ b/netlist.cc @@ -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) { diff --git a/netlist.h b/netlist.h index a3880604b..812372ff5 100644 --- a/netlist.h +++ b/netlist.h @@ -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; diff --git a/pform.cc b/pform.cc index d7c476532..e66d5dce8 100644 --- a/pform.cc +++ b/pform.cc @@ -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); diff --git a/pform_dump.cc b/pform_dump.cc index 6dc130687..94913eca6 100644 --- a/pform_dump.cc +++ b/pform_dump.cc @@ -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); From ab6a0e0799e17f6e737655dccad55ec496927609 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sun, 17 May 2026 12:56:09 -0700 Subject: [PATCH 2/3] Preserve delay and strength in unpacked array continuous assignments Continuous assignments to unpacked arrays are expanded into per-element BUFZ drivers. Currently this path drops the delay and drive strength from the original continuous assignment, so `assign #5 a = b` updates the array immediately and `assign (weak1, weak0) a = b` drives with the default strength. Pass the evaluated delay and strength values through the unpacked array assignment helper and apply them to each generated element driver. Signed-off-by: Lars-Peter Clausen --- PGate.h | 5 ++++- elaborate.cc | 9 ++++++--- netmisc.cc | 21 +++++++++++++++++---- netmisc.h | 6 +++++- 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/PGate.h b/PGate.h index 0c832ddd1..84e172377 100644 --- a/PGate.h +++ b/PGate.h @@ -32,6 +32,7 @@ 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 @@ -129,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; }; diff --git a/elaborate.cc b/elaborate.cc index ff448cc95..67320db99 100644 --- a/elaborate.cc +++ b/elaborate.cc @@ -133,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; } @@ -311,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, diff --git a/netmisc.cc b/netmisc.cc index 66e852820..a5af12d76 100644 --- a/netmisc.cc +++ b/netmisc.cc @@ -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 &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 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); } /* diff --git a/netmisc.h b/netmisc.h index d87b5d027..ff9edc1ec 100644 --- a/netmisc.h +++ b/netmisc.h @@ -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); From 635bdd8eb8325de8bd9d7d3133bd4938c9b2aaa4 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sun, 17 May 2026 12:06:44 -0700 Subject: [PATCH 3/3] Add regression tests for unpacked array continuous assignment strength and delay Check that continuous assignments to unpacked net arrays preserve delay and drive strength on the generated element drivers. Signed-off-by: Lars-Peter Clausen --- ivtest/ivltests/sv_array_cassign10.v | 39 +++++++++++++++++++++ ivtest/ivltests/sv_array_cassign9.v | 44 ++++++++++++++++++++++++ ivtest/regress-vvp.list | 2 ++ ivtest/vvp_tests/sv_array_cassign10.json | 9 +++++ ivtest/vvp_tests/sv_array_cassign9.json | 9 +++++ 5 files changed, 103 insertions(+) create mode 100644 ivtest/ivltests/sv_array_cassign10.v create mode 100644 ivtest/ivltests/sv_array_cassign9.v create mode 100644 ivtest/vvp_tests/sv_array_cassign10.json create mode 100644 ivtest/vvp_tests/sv_array_cassign9.json diff --git a/ivtest/ivltests/sv_array_cassign10.v b/ivtest/ivltests/sv_array_cassign10.v new file mode 100644 index 000000000..5a5fa6856 --- /dev/null +++ b/ivtest/ivltests/sv_array_cassign10.v @@ -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 diff --git a/ivtest/ivltests/sv_array_cassign9.v b/ivtest/ivltests/sv_array_cassign9.v new file mode 100644 index 000000000..4db9f0d05 --- /dev/null +++ b/ivtest/ivltests/sv_array_cassign9.v @@ -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 diff --git a/ivtest/regress-vvp.list b/ivtest/regress-vvp.list index 36d22f512..09f0498cf 100644 --- a/ivtest/regress-vvp.list +++ b/ivtest/regress-vvp.list @@ -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 diff --git a/ivtest/vvp_tests/sv_array_cassign10.json b/ivtest/vvp_tests/sv_array_cassign10.json new file mode 100644 index 000000000..0c3331289 --- /dev/null +++ b/ivtest/vvp_tests/sv_array_cassign10.json @@ -0,0 +1,9 @@ +{ + "type" : "normal", + "source" : "sv_array_cassign10.v", + "iverilog-args" : [ "-g2005-sv" ], + "vlog95" : { + "__comment" : "Array nets are not supported", + "type" : "CE" + } +} diff --git a/ivtest/vvp_tests/sv_array_cassign9.json b/ivtest/vvp_tests/sv_array_cassign9.json new file mode 100644 index 000000000..1b8bd91eb --- /dev/null +++ b/ivtest/vvp_tests/sv_array_cassign9.json @@ -0,0 +1,9 @@ +{ + "type" : "normal", + "source" : "sv_array_cassign9.v", + "iverilog-args" : [ "-g2005-sv" ], + "vlog95" : { + "__comment" : "Array nets are not supported", + "type" : "CE" + } +}