Hardware verification and debugging

Yuhei Horibe
4 min readMay 26, 2020

--

Simulation result of multiplier

Summary

In previous articles, I explained how basic digital circuits are designed. In this article, I’ll explain how those circuits are verified/debugged.

Hardware debugging

Debugging hardware is different from debugging software. To debug software, we usually use;

  • Debugger
  • Printings/logs/messages/asserts

Second one apparently doesn’t work for hardware, since hardware can’t handle characters without the help of software in most cases. Also, HDL is not a program (it’s not executed in programming order), stepping through the code is not the option. HDL is basically the description of interfaces/connections between modules, and the behavior of the state machines. Also, in HDL, most blocks work concurrently.

So when debugging hardware, we usually use;

  • Simulator
  • Logic analyzer/Oscilloscope
  • JTAG debugger

Simulation is the most common way to debug hardware. We can run behavioral simulation, timing simulation and so on before making actual silicon chip, because it costs quite a lot to make actual silicon chip. FPGA is also used to debug hardware, but there are some limitations, for example, the size of the circuit, speed, and so on. Also, making FPGA validation platform costs a lot as well.

Hardware simulation

There are many hardware simulators available, and some of them are free, or, free with limited functionalities. I’m using ModelSim, which is one of the most popular simulator. There’s a free edition as well.

Installing ModelSim is a bit tricky, since this program uses 32bit libraries. Here’s what I referred when I installed this.

Also note that, if firewall blocks loop-back traffic (127.0.0.1), simulation doesn’t start.

As I explained above, simulation is quite important to verify the design. There are many techniques and libraries like OVM and UVM, but it’s not available for free edition (I tried, but didn’t work due to those limitations). So I won’t explain stuff like OVM, UVM and coverage (in most cases, it’s enough for hobbyists like me).

Testbench

To run the simulation, we need to write “testbench”. Testbench is a program to generate various input signals (stimulus) to verify the functionalities of the designed hardware.

For the combinational circuit, testbench is pretty straight forward. What we want to do is, generate all the possible combinations of the input signals. Or if there are too much combinations and it’s not realistic, we can see some representational patterns.

Here’s an example.

`timescale 1ns/1nsmodule adder_test;
localparam C_WIDTH = 4;
reg reset;
reg [C_WIDTH-1:0] a;
reg [C_WIDTH-1:0] b;
wire [C_WIDTH:0] y;
initial begin
reset <= 1'b0;
#20;
reset <= 1'b1;
end
always #5 begin
if (!reset) begin
a <= 0;
b <= 0;
end else begin
if ((a == ((1 << C_WIDTH) - 1)) && (b == ((1 << C_WIDTH) - 1))) begin
$finish;
end else begin
a <= a + 1;
if (a == ((1 << C_WIDTH) - 1)) begin
b <= b + 1;
end else begin
b <= b;
end
end
end
end
adder #(.C_WIDTH(C_WIDTH)) DUT (
.a(a),
.b(b),
.y(y)
);
endmodule

Because this test bench is verilog source code, the syntax is almost the same as hardware design, but at the same time, there are some exceptions since this is a program. For example, “initial” keyword is only for test benches, and not available for hardware description (Also note that, System Verilog is Object Oriented programming language, specialized in verification).

In initial block, things can be described like a program. In this block, basically, things happen in described order. #20 is equivalent to sleep(20) in this case. It waits 20 time units, then go to the next line.

Time unit is defined at the first line. In this case, it’s 1ns. This defines the resolution of the simulation. This is important for the timing simulation, but it can be anything in this case.

always” block is executed periodically (every 5 time units) in this example (“always” block is quite important in HDL, so it’ll be explained in detail later).

Basically, what this testbench does is, increment counter “a” every 5ns, and if counter “a” reaches the maximum value, it increments “b” (“a” becomes “0” at the same time because of overflow). If both a and b reach the maximum value, it stops the simulation ($finish keyword).

Simulation result is shown below.

Simulation result of adder

So counter “a” is incremented every 5ns, and when it reaches “0xf”, counter “b” gets incremented (“a” becomes “0” at the same time). Also, the output Y is always the result of “a + b”. So we can say this adder is working as expected from the simulation.

--

--

No responses yet