Basic unit testing - verilog

I'm an experience programmer but new to HDL. I'm trying to figure out how to implement basic unit testing for my hardware designs for a class I'm taking. I'm aware of SVUnit, but I need to submit the code so I would prefer to just implement the barebones testing functionality myself. This will also help me learn more.
I'm having trouble figuring out what language constructs to use. All I really need to do is instantiate a component, drive the inputs, then verify the output values. The verification is where I'm stuck. Does that need to go into an always block?
Even pointing me in the right direction for what terms I should be googling would be very helpful. So far I've tried: verilog modelsim unit testing, verilog modelsim self-checking testbench, etc without too much success.
EDIT: Example:
Let's say I have a design for a 1 bit half adder. How would I write a testbench to exercise every possible input combination, and automatically verify that the output is correct?

As a first draft I would look at something like this.
reg clk = 0;
reg rst_n;
initial
begin
rst_n = 'bx;
#5
rst_n = 1'b0;
#20
rst_n = 1'b1;
end
always #(clk)
begin
clk = #10 ~clk;
end
reg a,b;
wire adder = a + b;
task test;
input i0,i1,o;
a = i0;
b = i1;
#1
if (adder !== o)
$display("Error:Incorrect output");
endtask
initial
begin
wait(rst_n === 1'b0);
#(posedge clk)
test(0,0,0);
#(posedge clk)
test(0,1,1);
#(posedge clk)
test(1,1,0);
#(posedge clk)
test(1,0,1);
end
Then you might as a second draft implement test data as something like:
wire [3:0] stim_data [1:0];
wire [3:0] expected_output;
always #(posedge clk)
if (!rst_n)
begin
cnt <= 2'b00;
end
else
begin
cnt <= cnt + 1;
end
assign {a,b} = stim_data[cnt];
always #(posedge clk)
if (!rst_n)
begin
end
else
begin
if (adder !== expected_output[cnt])
// add error message
end
Hopefully that should get you started.

Related

RTL if statements not matching with simulation

I am struggling to understand why the output flickers between 0 and 1, when the output should constantly remain 0 since the reduction OR gate of 000 is 0 not 1.
When I tried a different bit width, the problem suddenly disappeared. However, I would like to know what is going on rather than relying on randomness for correctness.
project_test .sv
`timescale 1ns/1ns
module project_test
( input logic clk, rst,
input logic in,
output logic [2:0] c
);
logic [2:0] out;
always#(posedge clk or posedge rst) begin
if(rst)
out <= 'b0;
else if(in) begin
if(|out)
out <= 'b0;
end
else
out <= out + 1'b1;
end
assign c = out;
endmodule: project_test
testbench.sv
`timescale 1ns/1ns
module testbench;
logic clk, rst;
logic in;
logic [2:0] c;
project_test project_test(
.clk(clk),
.rst(rst),
.in(in),
.c(c)
);
initial begin
clk = 0;
rst = 1;
in = 0 ;
#30
rst = 0;
#20;
in = 1;
#500;
rst=1;
#100ns;
$stop();
end
always#(clk) begin
#10ns clk <= !clk;
end
endmodule
Simulation output:
RTL viewer:
That is an improper way to generate a clock signal in the testbench. You should not have the clk signal in the sensitivity list because it keeps re-triggerring the always block. Your clock generator potentially adds events to the Verilog event queue, which can cause odd behavior. In fact, when I ran your code on the Cadence simulator, I did not see the clock toggling at all.
This is a more standard way to generate a clock:
always begin
#10ns clk = !clk;
end
You are using a very verbose implementation. Why can't you do something like below.
always#(posedge clk or posedge rst)
begin
if(rst)
out <= 'd0;
else
out <= (in) ? 'd0 : (out + 1'b1);
end

Driving a internal wire in my module from my interface task

EDIT: I tried out the methods mentioned below: I set my interface to wires instead of logic, and I drive 'Z from the driver that wants to relinquish control of the signal so that the other driver can take over. Still doesn't work as I see u_slave_dut not being driven from my interface. Any clues on what is wrong? My working example: https://www.edaplayground.com/x/4SSP
I am writing a testbench for a top-level module that has a number of sub-modules. I want to instantiate an interface and hook it up to one of my sub-modules, say my_submodule. Easy enough, and I can see my interface pins toggling when my_submodule pins are toggled. This makes for great observation.
Next, I decided I wanted to be able to toggle the pins myself from my interface (using a task). I realize this leads to 2 drivers on the same bus. So, is there a way for me to do this?
I created a small example of edaplayground to experiment, and here I see that none of my writes from my task in the interface actually toggle the pins on u_slave_dut. Also, I get no warnings from the compiler which bothers me too.
My working example is here: https://www.edaplayground.com/x/5fcP
testbench.sv
`include "my_interface.sv"
module tb;
bit clk = 1'b1;
bit control = 1'b0;
initial begin
forever begin
#5 clk = ~clk;
end
end
my_interface my_vif(clk);
assign my_vif.addr = (control)? tb.u_top.u_slave_dut.i_addr : 'hz;
assign my_vif.wdata = (control)? tb.u_top.u_slave_dut.i_wdata : 'hz;
assign my_vif.write = (control)? tb.u_top.u_slave_dut.i_write : 'hz;
top u_top(.clk(clk));
initial begin
#80 my_vif.master_write_something;
#160 $finish;
end
initial begin
$dumpfile("dump.vcd");
$dumpvars(0);
end
endmodule
interface.sv
interface my_interface(input clk);
logic [3:0] addr;
logic write;
logic [3:0] wdata;
logic [3:0] rdata;
logic resp;
clocking master_cb #(posedge clk);
input resp, rdata;
output addr, write, wdata;
endclocking
clocking slave_cb #(posedge clk);
input addr, write, wdata;
output resp, rdata;
endclocking
task master_write_something;
#(master_cb);
master_cb.write <= 1'b1;
#(master_cb);
master_cb.wdata <= 3'b101;
master_cb.addr <= 3'b111;
#(master_cb);
master_cb.write <= 1'b0;
endtask
task slave_write_something;
#(slave_cb);
slave_cb.resp <= 1'b1;
#(slave_cb);
slave_cb.rdata <= 3'b101;
#(slave_cb);
slave_cb.resp <= 1'b0;
slave_cb.rdata <= 3'b000;
endtask
endinterface
design.sv
module slave_dut (
input clk,
input [3:0] i_addr,
input [3:0] i_wdata,
input i_write,
output o_resp,
output [3:0] o_rdata
);
reg o_resp_reg;
reg [3:0] o_rdata_reg;
initial begin
o_resp_reg <= 1'b0;
o_rdata_reg <= 'h0;
end
always #(posedge clk) begin
if (i_write == 1'b1) begin
o_resp_reg <= 1'b1;
o_rdata_reg <= i_wdata;
end
else begin
o_resp_reg <= 1'b0;
o_rdata_reg <= 'h0;
end
end
assign o_resp = o_resp_reg;
assign o_rdata = o_rdata_reg;
endmodule : slave_dut
module master_dut (
input clk,
output [3:0] o_addr,
output [3:0] o_wdata,
output o_write,
input i_resp,
input [3:0] i_rdata
);
reg [3:0] o_addr_reg;
reg [3:0] o_wdata_reg;
reg o_write_reg;
initial begin
o_addr_reg <= 'h0;
o_wdata_reg <= 'h0;
o_write_reg <= 'h0;
repeat (2) #(posedge clk);
o_addr_reg <= 'hF;
o_wdata_reg <= 'hB;
o_write_reg <= 1'b1;
#(posedge clk);
o_addr_reg <= 'h0;
o_wdata_reg <= 'h0;
o_write_reg <= 'h0;
repeat (2) #(posedge clk);
o_addr_reg <= 'h4;
o_wdata_reg <= 'hD;
o_write_reg <= 1'b1;
#(posedge clk);
o_addr_reg <= 'h0;
o_wdata_reg <= 'h0;
o_write_reg <= 'h0;
end
assign o_addr = o_addr_reg;
assign o_wdata = o_wdata_reg;
assign o_write = o_write_reg;
endmodule : master_dut
module top(input clk);
wire [3:0] addr;
wire [3:0] wdata;
wire write;
wire resp;
wire [3:0] rdata;
master_dut u_master_dut (
.clk(clk),
.o_addr(addr),
.o_wdata(wdata),
.o_write(write),
.i_resp(resp),
.i_rdata(rdata)
);
slave_dut u_slave_dut (
.clk(clk),
.i_addr(addr),
.i_wdata(wdata),
.i_write(write),
.o_resp(resp),
.o_rdata(rdata)
);
endmodule
Any idea where I am going wrong?
I believe the one simulator you chose to run the simulator on does not give any warnings or errors. Any other simulator will correctly give the errors about mixing continuous assignments with procedural assignments from the clocking block.
If you are going to have the same signal driven from two different places, you need to deal with that by using a wire instead of a variable. You also need to turn off one of the drivers by setting it to high-impedance ('z), so the other driver can control the signal. This needs to be done regardless of whether the two drivers are within your design or between the design and testbench. I wrote a DVCon paper a few years ago that explains this in more detail.
As Dave explained, it is the only way to control the multi driver nets by enabling only one driver to drive the net at a time. And from hardware point of view as well, only one driver should drive any wire at a time.
This can be achieved in your design by using additional reg.
Please refer the following code.
module tb;
bit clk = 1'b1;
reg control = 1'b1;
initial begin
forever begin
#5 clk = ~clk;
end
end
my_interface my_vif(clk);
assign my_vif.addr = (control)? tb.u_top.u_slave_dut.i_addr : 'hz;
assign my_vif.wdata = (control)? tb.u_top.u_slave_dut.i_wdata : 'hz;
assign my_vif.write = (control)? tb.u_top.u_slave_dut.i_write : 'hz;
top u_top(.clk(clk));
initial begin
#80 my_vif.master_write_something (control);
#160 $finish;
end
initial begin
$dumpfile("dump.vcd");
$dumpvars(0);
end
endmodule
// Interface Task
task master_write_something (ref reg x);
#(master_cb);
x = 1'b0;
master_cb.write <= 1'b1;
#(master_cb);
master_cb.wdata <= 3'b101;
master_cb.addr <= 3'b111;
#(master_cb);
master_cb.write <= 1'b0;
endtask
And the dump file is as follow.

D-flip flop with 2 reset: synthesis error

I'm doing a synthesis of a digital block and I need a D-Flip-Flop with 2 asynchronous resets.
(The reason is that I will drive one reset with an available clock, and I will use the second one to reset all the registers of my digital block)
I prepared the following code:
module dff_2rst(q,qn,clk,d, rst,clear);
input clk,d, rst, clear ;
output q,qn;
reg q,qn;
always #(posedge clk or posedge rst or posedge clear) //asynchronous reset
begin
(* full_case, parallel_case *)
case({rst, clear})
2'b00: begin
q <= d;
qn<=~d;
end
default: begin
q <= 1'b0;
qn <=1'b1;
end
endcase
end
endmodule
But I get the following error:
The statements in this 'always' block are outside the scope of the synthesis policy. Only an 'if' statement is allowed at the top level in this always block. (ELAB-302)
*** Presto compilation terminated with 1 errors. ***
I also tried with
if(~rst & ~clear)
but I have errors too.
Do you have an idea to correct my code? Thanks a lot!
The standard way to write a async reset, set (clear) flip-flop in Verilog RTL is:
always #(posedge clk or posedge rst or posedge clear) begin
if (rst) begin
// Aysnc Reset
q <= 'b0 ;
end
else if (clear) begin
// Async Clear
q <= 'b0 ;
end
else begin
// Sync logic here
q <= d;
end
end
assign qn = ~n;
The little trick for qn requires it qn be a wire, currently defined as a reg. reg q,qn; should just be reg q;
Also for cleaner code a new header type is cleaner and avoids repetition:
module dff_2rst(
input clk,
input d,
input rst,
input clear,
output reg q,
output qn );
Thank you Morgan. Your code was inspiring to me. I couldn't use
assign qn=~q;
Because I got the error:
qn is not a valid left-hand side of a continuous assignment.
But I modified your code in the following manner and it works:
module dff_2rst(q,qn,clk,d, rst,clear);
input clk,d, rst, clear ;
output q,qn;
reg q,qn;
always #(posedge clk or posedge rst or posedge clear) //asynchronous reset
//
begin
if (rst) begin
// Aysnc Reset
q <= 'b0 ;
qn<= 'b1 ;
end
else if (clear) begin
// Async Clear
q <= 'b0 ;
qn<= 'b1 ;
end
else begin
// Sync logic here
q <= d;
qn<= ~d;
end
end
endmodule

Does not work as before Verilog initial construction in ModelSim Altera Edition 10.4

Since version 10.4, start problem with initial block. Like this:
reg [31:0] init_ram[15:0];
initial begin
init_ram[0] = 32'h1234_5678;
init_ram[1] = 32'h8765_4321;
...
end
always_ff #(posedge clk)
init_ram[addr] <= data;
Or
module test(
input clk,
...
output reg a
);
initial a = 1'b1;
always #(posedge clk)
a <= ...;
ModelSim 10.4 error:
Error (suppressible): (vlog-7061) {path} Variable 'init_ram' driven in
an always_ff block, may not be driven by any other process
In older versions all works well.
You don't know which ModelSim parameter should I change to fix it?
One of the problems with the always_ff and always_comb constructs is that they are supposed to check the synthesizability during simulation, except that there is no standard for what is synthesizable. If the intent is that the initial blocks are just for simulation, then you will need to change the always_ff to always or change the initial block to use force/release instead.
One idea for a work around to your problem is to add a reset
signal and use it to initialize the value of a register.
Well, probably this
would be a good way to give an initial value to your flip-flops.
This should be something like:
reg [31:0] init_ram[15:0];
input reset;
always_ff #(posedge clk) begin
if (reset) begin
init_ram[0] <= 32'h1234_5678;
init_ram[1] <= 32'h8765_4321;
end
else begin
init_ram[addr] <= data;
Or this:
module test(
input clk,
input reset,
...
output reg a
);
always #(posedge clk) begin
if (reset) begin
a <= 1'b1;
end
else begin
a <= ...;

Parse error when I try to use Verilog; testbenching an LFSR doesn't work

I am currently working on random number generation using Verilog. Sources have indicated that using Linear Feedback Shift Registers are one of the best ways to randomize MSBs. So I decided to code and testbench an LFSR. Snippet is below:
module lfsr_counter(clk, reset, ce, lfsr_done);
input clk, reset, ce;
output lfsr_done;
reg lfsr_done;
reg [10:0] lfsr;
initial lfsr_done = 0;
wire d0,lfsr_equal;
xnor(d0,lfsr[10],lfsr[8]);
assign lfsr_equal = (lfsr == 11'h359);
always #(posedge clk,posedge reset) begin
if(reset) begin
lfsr <= 0;
lfsr_done <= 0;
end
else begin
if(ce)
lfsr <= lfsr_equal ? 11'h0 : {lfsr[9:0],d0};
lfsr_done <= lfsr_equal;
end
end
endmodule
module testbench();
reg clk, reset, ce;
wire lfsr_done;
lfsr_counter dut(clk, reset, ce, lfsr_done); // Design Under Test
initial
begin
reset = 0;
clk = 1;
ce = 0;
#100
ce = 1;
#200 $finish;
end
//Generate Clock
always #10 clk = !clk;
endmodule
But I keep getting these parse errors:
I don't really get it. I'm using Verilogger Pro btw
I think always block terms are separated by or, not a comma.
always #(posedge clk or posedge reset) begin

Resources