Clocking blocks have been introduced in SystemVerilog to address the problem of specifying the timing and synchronisation requirements of a design in a testbench.
module COUNTER (input Clock, Reset, Enable, Load, UpDn,
input [7:0] Data, output reg[7:0] Q);
always @(posedge Clock or posedge Reset)
if (Reset)
Q <= 0;
else
if (Enable)
if (Load)
Q <= Data;
else
if (UpDn)
Q <= Q + 1;
else
Q <= Q - 1;
endmodule
module Test_Counter;
timeunit 1ns;
reg Clock = 0, Reset, Enable, Load, UpDn;
reg [7:0] Data;
wire [7:0] Q;
reg OK;
// Clock generator
always
begin
#5 Clock = 1;
#5 Clock = 0;
end
// Test stimulus
initial
begin
Enable = 0;
Load = 0;
UpDn = 1;
Reset = 1;
#10; // Should be reset
Reset = 0;
#10; // Should do nothing - not enabled
Enable = 1; #20; // Should count up to 2
UpDn = 0;
#40; // Should count downto 254
UpDn = 1;
// etc. ...
end
// Instance the device-under-test
COUNTER G1 (Clock, Reset, Enable, Load, UpDn, Data, Q);
// Check the results
initial
begin
OK = 1;
#9;
if (Q !== 8'b00000000)
OK = 0;
#10;
if (Q !== 8'b00000000)
OK = 0;
#20;
if (Q !== 8'b00000010)
OK = 0;
#40;
if (Q !== 8'b11111110)
OK = 0;
// etc. ...
end
endmodule
module Test_Counter_w_clocking;
timeunit 1ns;
reg Clock = 0, Reset, Enable, Load, UpDn;
reg [7:0] Data;
wire [7:0] Q;
// Clock generator
always
begin
#5 Clock = 1;
#5 Clock = 0;
end
// Test program
program test_counter;
// SystemVerilog "clocking block"
// Clocking outputs are DUT inputs and vice versa
clocking cb_counter @(posedge Clock);
default input #1step output #4;
output negedge Reset;
output Enable, Load, UpDn, Data;
input Q;
endclocking
// Apply the test stimulus
initial begin
// Set all inputs at the beginning
Enable = 0;
Load = 0;
UpDn = 1;
Reset = 1;
// Will be applied on negedge of clock!
##1 cb_counter.Reset <= 0;
// Will be applied 4ns after the clock!
##1 cb_counter.Enable <= 1;
##2 cb_counter.UpDn <= 0;
##4 cb_counter.UpDn <= 1;
// etc. ...
end
// Check the results - could combine with stimulus block
initial begin
##1
// Sampled 1ps (or whatever the precision is) before posedge clock
##1 assert (cb_counter.Q == 8'b00000000);
##1 assert (cb_counter.Q == 8'b00000000);
##2 assert (cb_counter.Q == 8'b00000010);
##4 assert (cb_counter.Q == 8'b11111110);
// etc. ...
end
// Simulation stops automatically when both initials have been completed
endprogram
// Instance the counter
COUNTER G1 (Clock, Reset, Enable, Load, UpDn, Data, Q);
// Instance the test program - not required, because program will be
// instanced implicitly.
// test_COUNTER T1 ();
endmodule
cb.Data[2:0] <= 3'h2; // Drive 3-bit slice of Data in current cycle
##1 cb.Data <= 8'hz; // Wait 1 Clk cycle and then drive Data
##2 cb.Data[1] <= 1; // Wait 2 cycles, then drive bit 1 of Data
cb.Data <= ##1 Int_Data; // Remember the value of Int_Data, and then
// drive Data 1 Clk cycle later
cb.Data[7:4] <= 4'b0101;
cb.Data[7:4] <= 4'b0011; // Error: driven value of Data[7:4] is 4’b0xx1
// Interface definitions
interface DataBus (input Clock);
logic [7:0] Addr, Data;
modport TestR (inout Addr, inout Data);
modport Ram (inout Addr, inout Data);
endinterface
interface CtrlBus (input Clock);
logic RWn;
// RWn is output, as it is in the clocking block
modport TestR (output RWn);
// RWn is input, reversed than in the clocking block
modport Ram (input RWn);
endinterface
// Testbench defined as a program, with two clocking blocks
program TestRAM (DataBus.TestR DataInt,
CtrlBus.TestR CtrlInt);
clocking cb1 @(posedge DataInt.Clock);
inout #5ns DataInt.Data;
inout #2ns DataInt.Addr;
endclocking
clocking cb2 @(posedge CtrlInt.Clock);
output #10;
output RWn = CtrlInt.RWn; // Hierarchical expression
endclocking
initial begin
cb2.RWn = 0;
cb1.DataInt.Data = 1;
...
end
endprogram
module RAM (DataBus.Ram DataInt, CtrlBus.Ram CtrlInt);
logic [7:0] mem[0:255];
always @*
if (CtrlInt.RWn)
DataInt.Data = mem[DataInt.Addr];
else
mem[DataInt.Addr] = DataInt.Data;
endmodule
module Top;
logic Clk1, Clk2;
// Instance the interfaces
DataBus TheDataBus(.Clock(Clk1));
CtrlBus TheCtrlBus(.Clock(Clk2));
RAM TheRAM (.DataBus.Ram(TheDataBus.Ram),
.CtrlBus.Ram(TheCtrlBus.Ram)); // Connect them
TestRAM TheTest (.DataBus.TestR(TheDataBus.TestR),
.CtrlBus.TestR(TheCtrlBus.TestR));
endmodule
// Wait for the next change of Data signal from the cb clocking block
@(cb.Data);
// Wait for positive edge of signal cb.Ack
@(posedge cb.Ack);
// Wait for posedge of signal cb.Ack or negedge of cb.Req
@(posedge cb.Ack or negedge cb.Req);
// Wait for the next change of bit 2 of cb.Data
@(cb.Data[2]);
// Wait for the next change of the specified slice
@(cb.Data[7:5]);