Merge pull request #9 from muhammadjawadkhan/feat/clocking-blocks

SV: interface clocking-block vertical slice (Tier A #4)
This commit is contained in:
muhammadjawadkhan 2026-07-21 18:16:50 +05:00 committed by GitHub
commit 8504b349f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 472 additions and 43 deletions

View File

@ -127,7 +127,7 @@ O = main.o async.o design_dump.o discipline.o dup_expr.o elaborate.o \
pform_types.o \
symbol_search.o sync.o sys_funcs.o verinum.o verireal.o vpi_modules.o target.o \
Attrib.o HName.o Module.o PClass.o PDelays.o PEvent.o PExpr.o PFunction.o \
PGate.o PGenerate.o PModport.o PNamedItem.o PPackage.o PScope.o PSpec.o PTimingCheck.o \
PGate.o PGenerate.o PClocking.o PModport.o PNamedItem.o PPackage.o PScope.o PSpec.o PTimingCheck.o \
PTask.o PUdp.o PWire.o Statement.o AStatement.o $M $(FF) $(TT)
all: dep config.h _pli_types.h version_tag.h version_base.h ivl@EXEEXT@

View File

@ -36,6 +36,7 @@ class PEIdent;
class PGate;
class PGenerate;
class PModport;
class PClocking;
class PSpecPath;
class PTimingCheck;
class PTask;
@ -154,6 +155,9 @@ class Module : public PScopeExtra, public PNamedItem {
program blocks. */
std::map<perm_string,PModport*> modports;
/* Clocking blocks (modules and interfaces). */
std::map<perm_string,PClocking*> clockings;
/* List for specify paths and timing checks */
std::list<PSpecPath*> specify_paths;
std::list<PTimingCheck*> timing_checks;

26
PClocking.cc Normal file
View File

@ -0,0 +1,26 @@
/*
* Minimal SystemVerilog clocking-block parse representation.
*/
# include "config.h"
# include "PClocking.h"
PClocking::PClocking(perm_string n)
: name_(n)
{
}
PClocking::~PClocking()
{
}
void PClocking::set_events(const std::vector<PEEvent*>&evs)
{
events_ = evs;
}
PNamedItem::SymbolType PClocking::symbol_type() const
{
return CLOCKING;
}

51
PClocking.h Normal file
View File

@ -0,0 +1,51 @@
#ifndef IVL_PClocking_H
#define IVL_PClocking_H
/*
* Minimal SystemVerilog clocking-block parse representation (UVM Tier A #4).
*/
# include "PNamedItem.h"
# include "PScope.h"
# include "StringHeap.h"
# include "netlist.h"
# include <map>
# include <vector>
class PEEvent;
class PExpr;
/*
* PClocking holds a parsed clocking block: the clocking event and the
* list of clocking signals (direction + optional skew expression).
* Skews beyond #0 are accepted in the grammar but ignored at runtime.
*/
class PClocking : public PNamedItem {
public:
struct signal_t {
NetNet::PortType direction;
PExpr*skew; /* optional; ignored for v1 */
};
explicit PClocking(perm_string name);
~PClocking() override;
perm_string name() const { return name_; }
void set_events(const std::vector<PEEvent*>&evs);
const std::vector<PEEvent*>& events() const { return events_; }
std::map<perm_string,signal_t> signals;
SymbolType symbol_type() const override;
private:
perm_string name_;
std::vector<PEEvent*> events_;
private: // not implemented
PClocking(const PClocking&);
PClocking& operator= (const PClocking&);
};
#endif /* IVL_PClocking_H */

View File

@ -78,6 +78,9 @@ std::ostream& operator << (std::ostream&o, PNamedItem::SymbolType st)
case PNamedItem::MODPORT:
o << "a modport";
break;
case PNamedItem::CLOCKING:
o << "a clocking block";
break;
case PNamedItem::PACKAGE:
o << "a package";
break;

View File

@ -30,8 +30,8 @@ class PNamedItem : virtual public LineInfo {
public:
enum SymbolType { ANY, PARAM, NET, VAR, GENVAR, EVENT, TYPE, ENUM,
CLASS, FUNCTION, TASK, BLOCK, GENBLOCK, MODPORT,
PACKAGE, MODULE, PROGRAM, INTERFACE, PRIMITIVE,
INSTANCE };
CLOCKING, PACKAGE, MODULE, PROGRAM, INTERFACE,
PRIMITIVE, INSTANCE };
explicit PNamedItem();
virtual ~PNamedItem() override;

View File

@ -508,6 +508,8 @@ class PEventStatement : public Statement {
NetProc* elaborate_wait(Design*des, NetScope*scope, NetProc*st) const;
NetProc* elaborate_wait_fork(Design*des, const NetScope*scope) const;
const std::vector<PEEvent*>& get_events() const { return expr_; }
private:
std::vector<PEEvent*>expr_;
Statement*statement_;

View File

@ -7,7 +7,7 @@ Each item should be a dedicated `feat/<name>` branch, with tests/examples and a
1. **Parameterized classes**`class C #(type T = int);` / `C#(byte)` — needed for `uvm_*#(T)`, `config_db`**partial** (defaults; see STATUS)
2. **Associative arrays**`int aa[string];`**in progress / partial** (string keys only; see [assoc-array.md](assoc-array.md))
3. **Virtual interfaces** + eventing on `vif.clk`**partial** (see [virtual-interface.md](virtual-interface.md))
4. **Clocking blocks** — enough for `@(vif.cb)`
4. **Clocking blocks** — enough for `@(vif.cb)`**partial** (interface-local `@(bif.cb)`; see [clocking.md](clocking.md))
5. **`mailbox` / `semaphore` builtins** (or solid class equivalents with blocking put/get)
6. **Constraints + `randomize()` / `randomize() with`** — start unconstrained `rand`, then solver
7. **`$cast` / `$typename` hardening** for factory patterns

View File

@ -16,6 +16,7 @@ Last updated: 2026-07-21
| Parameterized classes | **Partial** on `feat/param-classes` (merged): ANSI `class C #(type T = int, parameter int W = 8);` parses into the class scope and elaborates with **defaults**. Explicit specializations `C#(byte)` / overrides not done yet. See [`examples/param_classes`](../examples/param_classes). |
| Associative arrays | **Partial** on `feat/assoc-array`: string-keyed `int aa[string]` / `int aa[*]` with size/num/exists/delete/foreach/copy. See [`docs/assoc-array.md`](assoc-array.md) and [`examples/assoc_array`](../examples/assoc_array). |
| Virtual interfaces | **Partial** on `feat/virtual-interface`: `virtual interface T` as class property / TF arg; assign interface instance; member R/W; `@(posedge vif.clk)`. See [`docs/virtual-interface.md`](virtual-interface.md) and [`examples/virtual_interface`](../examples/virtual_interface). |
| Clocking blocks | **Partial** on `feat/clocking-blocks`: interface-local `clocking`; `@(bif.cb)`; `cb.sig` R/W with `#0` skew. See [`docs/clocking.md`](clocking.md) and [`examples/clocking`](../examples/clocking). |
| Constraints / randomize | Missing |
| Covergroups / DPI | Missing |

59
docs/clocking.md Normal file
View File

@ -0,0 +1,59 @@
# Clocking blocks (Tier A #4)
Status: **partial** — interface-local clocking enough for basic driver sync.
## Supported in this slice
```systemverilog
interface bus_if(input logic clk);
logic [7:0] data;
logic req;
clocking cb @(posedge clk);
input #0 data; // #1step may be written; treated as #0
output #0 req;
endclocking
endinterface
module top;
logic clk;
bus_if bif(clk);
logic [7:0] x;
initial begin
bif.data = 8'h3C;
@(bif.cb); // wait on clocking event (posedge clk)
bif.cb.req <= 1'b1; // NBA to underlying req
x = bif.cb.data; // read current value of data
end
endmodule
```
### Pragmatic semantics (v1)
| Construct | Behavior |
|-----------|----------|
| `@(cb)` / `@(bif.cb)` | Wait on the clocking event (`posedge clk`) |
| `cb.req <= val` | Non-blocking assign to underlying `req` (visible after NBA / next clocking event) |
| `cb.data` (read) | Current value of `data` (sampling skew = `#0`) |
| `#0` / `#1step` skew | Accepted; `#1step` aliases to `#0` if present |
Clocking signal names are **transparent** hierarchical hops: `bif.cb.req` resolves to `bif.req`.
## Example
[`examples/clocking/cb_basic.sv`](../examples/clocking/cb_basic.sv) — prints `PASSED`.
```bash
./install/bin/iverilog -g2012 -o /tmp/cb_basic.vvp examples/clocking/cb_basic.sv
./install/bin/vvp /tmp/cb_basic.vvp
```
## Deferred (do not claim)
- `default clocking`
- `##` cycle delays
- Modport clocking (`modport m (clocking cb);`)
- Complex / non-zero input/output skews
- `inout` clocking drive/sample timing
- `vif.cb` through virtual interfaces (follow-on once VI member lookup covers clocking)
See also [STATUS.md](STATUS.md) and [ROADMAP.md](ROADMAP.md).

View File

@ -55,6 +55,7 @@
# include "netclass.h"
# include "netmisc.h"
# include "PModport.h"
# include "PClocking.h"
# include "util.h"
# include "parse_api.h"
# include "compiler.h"
@ -5408,46 +5409,61 @@ NetProc* PEventStatement::elaborate_st(Design*des, NetScope*scope,
}
/* Vertical-slice support: @(posedge vif.clk) where vif is a
virtual interface. Emit $ivl_vif_wait then the continuation. */
virtual interface. Emit $ivl_vif_wait then the continuation.
Skip speculative elaborate when the path is a clocking wait. */
if (expr_.size() == 1 && dynamic_cast<PEIdent*>(expr_[0]->expr())) {
NetExpr*tmp = elab_and_eval(des, scope, expr_[0]->expr(), -1);
if (NetESFunc*sf = dynamic_cast<NetESFunc*>(tmp)) {
if (strcmp(sf->name(), "$ivl_vif_get") == 0 && sf->nparms() == 2) {
int edge_code = 2; /* anyedge */
switch (expr_[0]->type()) {
case PEEvent::POSEDGE: edge_code = 0; break;
case PEEvent::NEGEDGE: edge_code = 1; break;
case PEEvent::ANYEDGE: edge_code = 2; break;
case PEEvent::EDGE: edge_code = 2; break;
default: break;
}
NetExpr*idx_ex = sf->parm(1);
long midx = 0;
if (!eval_as_long(midx, idx_ex)) {
cerr << get_fileline() << ": error: "
<< "Virtual interface wait member index "
<< "must be constant." << endl;
des->errors += 1;
delete tmp;
return 0;
}
/* Dup the VI base — NetESFunc::parm(idx,0) deletes
the old expression, so we cannot steal in place. */
NetExpr*vif_base = sf->parm(0)->dup_expr();
delete tmp;
NetSTask*wait_task = elab_vif_wait_task(*this, vif_base,
edge_code,
static_cast<int>(midx));
if (!enet)
return wait_task;
NetBlock*blk = new NetBlock(NetBlock::SEQU, 0);
blk->set_line(*this);
blk->append(wait_task);
blk->append(enet);
return blk;
const PEIdent*vid = dynamic_cast<PEIdent*>(expr_[0]->expr());
bool maybe_clocking = false;
if (vid && !vid->path().name.empty()) {
symbol_search_results csr;
if (symbol_search(this, des, scope, vid->path(),
vid->lexical_pos(), &csr) &&
csr.is_scope() && csr.scope) {
perm_string tail = vid->path().name.back().name;
if (find_scope_clocking(csr.scope, tail))
maybe_clocking = true;
}
}
delete tmp;
if (!maybe_clocking) {
NetExpr*tmp = elab_and_eval(des, scope, expr_[0]->expr(), -1);
if (NetESFunc*sf = dynamic_cast<NetESFunc*>(tmp)) {
if (strcmp(sf->name(), "$ivl_vif_get") == 0 && sf->nparms() == 2) {
int edge_code = 2; /* anyedge */
switch (expr_[0]->type()) {
case PEEvent::POSEDGE: edge_code = 0; break;
case PEEvent::NEGEDGE: edge_code = 1; break;
case PEEvent::ANYEDGE: edge_code = 2; break;
case PEEvent::EDGE: edge_code = 2; break;
default: break;
}
NetExpr*idx_ex = sf->parm(1);
long midx = 0;
if (!eval_as_long(midx, idx_ex)) {
cerr << get_fileline() << ": error: "
<< "Virtual interface wait member index "
<< "must be constant." << endl;
des->errors += 1;
delete tmp;
return 0;
}
/* Dup the VI base — NetESFunc::parm(idx,0) deletes
the old expression, so we cannot steal in place. */
NetExpr*vif_base = sf->parm(0)->dup_expr();
delete tmp;
NetSTask*wait_task = elab_vif_wait_task(*this, vif_base,
edge_code,
static_cast<int>(midx));
if (!enet)
return wait_task;
NetBlock*blk = new NetBlock(NetBlock::SEQU, 0);
blk->set_line(*this);
blk->append(wait_task);
blk->append(enet);
return blk;
}
}
delete tmp;
}
}
/* Create a single NetEvent and NetEvWait. Then, create a
@ -5554,7 +5570,8 @@ cerr << endl;
/* If the expression is an identifier that matches a
named event, then handle this case all at once and
skip the rest of the expression handling. */
skip the rest of the expression handling. Also handle
terminal clocking-block waits: @(cb) / @(bif.cb). */
if (const PEIdent*id = dynamic_cast<PEIdent*>(expr_[idx]->expr())) {
symbol_search_results sr;
@ -5586,6 +5603,72 @@ cerr << endl;
}
continue;
}
/* @(bif.cb): path ends at a clocking block name that
was treated as transparent during symbol_search. */
if (sr.is_scope() && sr.scope && !id->path().name.empty()) {
perm_string cb_name = id->path().name.back().name;
const PClocking*cb = find_scope_clocking(sr.scope, cb_name);
if (cb && !cb->events().empty()) {
NetScope*cb_scope = sr.scope;
for (size_t eidx = 0 ; eidx < cb->events().size()
; eidx += 1) {
PEEvent*cev = cb->events()[eidx];
if (!cev || !cev->expr()) {
cerr << get_fileline() << ": error: "
"Clocking block '" << cb_name
<< "' has an empty clocking event."
<< endl;
des->errors += 1;
continue;
}
NetExpr*tmp = elab_and_eval(des, cb_scope,
cev->expr(), -1);
if (tmp == 0) {
cerr << get_fileline() << ": error: "
"Failed to evaluate clocking event for '"
<< cb_name << "'." << endl;
des->errors += 1;
continue;
}
NetNet*expr = tmp->synthesize(des, cb_scope, tmp);
if (expr == 0) {
des->errors += 1;
delete tmp;
continue;
}
delete tmp;
unsigned pins = (cev->type() == PEEvent::ANYEDGE)
? expr->pin_count() : 1;
NetEvProbe*pr = 0;
switch (cev->type()) {
case PEEvent::POSEDGE:
pr = new NetEvProbe(scope, scope->local_symbol(),
ev, NetEvProbe::POSEDGE, pins);
break;
case PEEvent::NEGEDGE:
pr = new NetEvProbe(scope, scope->local_symbol(),
ev, NetEvProbe::NEGEDGE, pins);
break;
case PEEvent::EDGE:
pr = new NetEvProbe(scope, scope->local_symbol(),
ev, NetEvProbe::EDGE, pins);
break;
case PEEvent::ANYEDGE:
default:
pr = new NetEvProbe(scope, scope->local_symbol(),
ev, NetEvProbe::ANYEDGE, pins);
break;
}
for (unsigned p = 0 ; p < pr->pin_count() ; p += 1)
connect(pr->pin(p), expr->pin(p));
des->add_node(pr);
expr_count += 1;
}
continue;
}
}
}

View File

@ -0,0 +1,30 @@
// Clocking-block vertical slice (UVM Tier A #4).
// Interface-local clocking with @(bif.cb), NBA write, and sampled read.
interface bus_if(input logic clk);
logic [7:0] data;
logic req;
clocking cb @(posedge clk);
input #0 data;
output #0 req;
endclocking
endinterface
module top;
logic clk;
bus_if bif(clk);
logic [7:0] x;
initial begin clk = 0; forever #5 clk = ~clk; end
initial begin
bif.data = 8'h3C;
@(bif.cb);
bif.cb.req <= 1'b1; // NBA to underlying req (#0 skew)
x = bif.cb.data; // #0 sample of data
@(bif.cb); // next clocking event: NBA is visible on bif.req
if (x !== 8'h3C || bif.req !== 1'b1) begin
$display("FAILED x=%0h req=%0b", x, bif.req);
$finish;
end
$display("PASSED");
$finish;
end
endmodule

View File

@ -147,6 +147,10 @@ extern bool check_interface_modport_access(const LineInfo *li, Design *des,
const symbol_search_results &res,
bool is_write);
class PClocking;
extern const PClocking* find_scope_clocking(const NetScope*scope,
perm_string name);
/*
* This function transforms an expression by either zero or sign extending
* the high bits until the expression has the desired width. This may mean

41
parse.y
View File

@ -1099,7 +1099,8 @@ Module::port_t *module_declare_interface_port(const YYLTYPE&loc, char *type,
%type <nettype> net_type net_type_opt net_type_or_var net_type_or_var_opt
%type <gatetype> gatetype switchtype
%type <porttype> port_direction port_direction_opt
%type <porttype> port_direction port_direction_opt clocking_direction
%type <expr> clocking_skew_opt
%type <vartype> integer_vector_type integer_vector_type_no_reg
%type <parmvalue> parameter_value_opt
@ -2521,6 +2522,37 @@ modport_tf_port
| K_function data_type_or_implicit_or_void IDENTIFIER tf_port_list_parens_opt
;
/* Minimal clocking-block items: direction [#skew] signals ; */
clocking_item_list_opt
: clocking_item_list
|
;
clocking_item_list
: clocking_item
| clocking_item_list clocking_item
;
clocking_direction
: K_input { $$ = NetNet::PINPUT; }
| K_output { $$ = NetNet::POUTPUT; }
| K_inout { $$ = NetNet::PINOUT; }
;
clocking_skew_opt
: '#' delay_value_simple { $$ = $2; }
| { $$ = 0; }
;
clocking_item
: clocking_direction clocking_skew_opt list_of_identifiers ';'
{ pform_add_clocking_signals(@1, $1, $2, $3); }
| error ';'
{ yyerror(@1, "error: Invalid clocking block item.");
yyerrok;
}
;
non_integer_type /* IEEE1800-2005: A.2.2.1 */
: K_real { $$ = real_type_t::REAL; }
| K_realtime { $$ = real_type_t::REAL; }
@ -5935,6 +5967,13 @@ module_item
| modport_declaration
/* SystemVerilog clocking blocks (Tier A #4 vertical slice). */
| attribute_list_opt K_clocking identifier_name event_control ';'
{ pform_start_clocking(@2, $3, $4); }
clocking_item_list_opt
K_endclocking
{ pform_end_clocking(@8); }
/* 1364-2001 and later allow specparam declarations outside specify blocks. */
| attribute_list_opt K_specparam

View File

@ -31,6 +31,7 @@
# include "PUdp.h"
# include "PGenerate.h"
# include "PModport.h"
# include "PClocking.h"
# include "PSpec.h"
# include "PTimingCheck.h"
# include "discipline.h"
@ -367,6 +368,7 @@ static list<set<perm_string> > conditional_block_names;
/* This tracks the current modport list being processed. This is
always within an interface. */
static PModport*pform_cur_modport = 0;
static PClocking*pform_cur_clocking = 0;
static NetNet::Type pform_default_nettype = NetNet::WIRE;
@ -3554,6 +3556,78 @@ void pform_add_modport_port(const struct vlltype&loc,
pform_cur_modport->simple_ports[name] = make_pair(port_type, expr);
}
void pform_start_clocking(const struct vlltype&loc, const char*name,
PEventStatement*ev)
{
Module*scope = pform_cur_module.front();
ivl_assert(loc, scope);
ivl_assert(loc, pform_cur_clocking == 0);
pform_requires_sv(loc, "Clocking block");
perm_string use_name = lex_strings.make(name);
pform_cur_clocking = new PClocking(use_name);
FILE_NAME(pform_cur_clocking, loc);
if (ev) {
pform_cur_clocking->set_events(ev->get_events());
/* Keep PEEvent objects; drop the statement wrapper. */
delete ev;
} else {
cerr << loc << ": error: Clocking block '" << use_name
<< "' requires a clocking event." << endl;
error_count += 1;
}
if (scope->clockings.find(use_name) != scope->clockings.end()) {
cerr << loc << ": error: Duplicate clocking block '"
<< use_name << "'." << endl;
error_count += 1;
}
add_local_symbol(scope, use_name, pform_cur_clocking);
scope->clockings[use_name] = pform_cur_clocking;
delete[] name;
}
void pform_end_clocking(const struct vlltype&loc)
{
ivl_assert(loc, pform_cur_clocking);
pform_cur_clocking = 0;
}
void pform_add_clocking_signals(const struct vlltype&loc,
NetNet::PortType direction,
PExpr*skew,
std::list<pform_ident_t>*names)
{
ivl_assert(loc, pform_cur_clocking);
ivl_assert(loc, names);
/* Skews other than #0 / #1step are accepted but ignored for v1. */
(void)skew;
for (list<pform_ident_t>::iterator cur = names->begin()
; cur != names->end() ; ++cur) {
perm_string name = cur->first;
if (pform_cur_clocking->signals.find(name)
!= pform_cur_clocking->signals.end()) {
cerr << loc << ": error: Duplicate clocking signal '"
<< name << "' in clocking block '"
<< pform_cur_clocking->name() << "'." << endl;
error_count += 1;
continue;
}
PClocking::signal_t sig;
sig.direction = direction;
sig.skew = skew;
pform_cur_clocking->signals[name] = sig;
}
delete names;
}
bool pform_requires_sv(const struct vlltype&loc, const char *feature)
{
if (gn_system_verilog())

11
pform.h
View File

@ -235,6 +235,17 @@ extern void pform_add_modport_port(const struct vlltype&loc,
NetNet::PortType port_type,
perm_string name, PExpr*expr);
/*
* Clocking-block related functions (Tier A #4 vertical slice).
*/
extern void pform_start_clocking(const struct vlltype&loc, const char*name,
PEventStatement*ev);
extern void pform_end_clocking(const struct vlltype&loc);
extern void pform_add_clocking_signals(const struct vlltype&loc,
NetNet::PortType direction,
PExpr*skew,
std::list<pform_ident_t>*names);
/*
* This creates an identifier aware of names that may have been
* imported from other packages.

View File

@ -26,10 +26,31 @@
# include "PPackage.h"
# include "PWire.h"
# include "PModport.h"
# include "PClocking.h"
# include "Module.h"
# include "parse_api.h"
# include "ivl_assert.h"
using namespace std;
const PClocking* find_scope_clocking(const NetScope*scope, perm_string name)
{
if (!scope || scope->type() != NetScope::MODULE)
return 0;
map<perm_string,Module*>::const_iterator mod =
pform_modules.find(scope->module_name());
if (mod == pform_modules.end())
return 0;
map<perm_string,PClocking*>::const_iterator cb =
mod->second->clockings.find(name);
if (cb == mod->second->clockings.end())
return 0;
return cb->second;
}
/*
* Search for the hierarchical name. The path may have multiple components. If
* that's the case, then recursively pull the path apart until we find the
@ -340,6 +361,27 @@ bool symbol_search(const LineInfo*li, Design*des, NetScope*scope,
return true;
}
/* Clocking blocks are transparent for hierarchical
descent: bif.cb.req resolves to bif.req. Terminal
@(bif.cb) is handled in event elaboration. */
if (scope->type() == NetScope::MODULE &&
find_scope_clocking(scope, path_tail.name)) {
path.push_back(path_tail);
res->scope = const_cast<NetScope*>(scope);
res->net = 0;
res->eve = 0;
res->par_val = 0;
res->type = 0;
res->path_head = path;
if (debug_scopes || debug_elaborate) {
cerr << li->get_fileline() << ": symbol_search: "
<< "Clocking '" << path_tail.name
<< "' is transparent in "
<< scope_path(scope) << endl;
}
return true;
}
} else if (scope->find_interface_port_alias_array(path_tail.name)) {
bool flag = false;
hname_t path_item = eval_path_component(des, start_scope, path_tail, flag);