Professional Documents
Culture Documents
Equivalence Checking Between C and RTL: Indian Institute of Technology Guwahati
Equivalence Checking Between C and RTL: Indian Institute of Technology Guwahati
Guwahati
Bachelor of Technology
Author: Supervisor:
Thesis Submitted
BACHELOR OF TECHNOLOGY
By
This is to certify that the thesis entitled “Equivalence Checking between C and RTL”,
submitted by me to the Indian Institute of Technology Guwahati, for the award of the degree
of Bachelor of Technology, is a bonafide work carried out by me under the supervision of
Dr. Chandan Karfa. The content of this thesis, in full or in parts, have not been submitted
to any other University or Institute for the award of any degree or diploma. I also wish to
state that to the best of my knowledge and understanding nothing in this report amounts
to plagiarism.
................................
Rakesh Reddy Theegala
Computer Science and Engineering,
Indian Institute of Technology Guwahati,
Guwahati-781039, Assam, India.
1
Certificate
This is to certify that the thesis entitled “Equivalence Checking between C and RTL”,
by Rakesh Reddy Theegala (170101071), a undergraduate student in the Computer Science
and Engineering, Indian Institute of Technology Guwahati, for the award of the degree of
Bachelor of Technology, is a record of an original research work carried out by him under my
supervision and guidance. The thesis has fulfilled all requirements as per the regulations
of the institute and in my opinion has reached the standard needed for submission. The
results embodied in this thesis have not been submitted to any other University or Institute
for the award of any degree or diploma.
................................
Dr. Chandan Karfa
Computer Science and Engineering,
Indian Institute of Technology Guwahati,
Guwahati-781039, Assam, India.
Date
18-04-2020
2
Abstract
The current work proposes a formal method for checking equivalence between a given
behavioural specification in C and the Register transfer level (RTL) behaviour produced by
the High-level synthesis (HLS). High level synthesis contains multiple optimization phases.
So input behaviour is modified several ways to finally mapped to RTLs. So there might be
chance of introducing error which differs output behaviour from input behaviour. HLS is
widely used in industry, so it is necessary to have a verification method to check HLS tool’s
correctness. Our work proposes a novel framework to check the correctness of HLS tool i.e.
equivalence between input behaviour and output behaviour generated by HLS tool. A C
code is reverse engineered from the RTL first. This is possible because the RTLs produced
by HLS tool has a separable controller FSM. Both behaviours are represented in C code
in my work. The main component of the work is to check the equivalence between two C
codes. Specifically, two codes are given, and an input file is given where some variables
have defined which are input to both these C codes and an output file where some variables
have defined on which we have check equivalence. Our method identifies the traces in the
C code, identifies the correspondence of traces between two behaviours using a data driven
approach and then formally prove the equivalence using a well known SMT solver. We
then propose a path based equivalence checking to improve the scalability of our method
further. The proposed approaches are tested on the results of Vivado HLS tool.
3
Acknowledgement
In the first place, I would like to take this opportunity to express my sincere gratitude
and regards to my thesis supervisor Dr. Chandan Karfa, Department of Computer Science
and Engineering, IIT Guwahati for his valuable guidance, support and encouragement. His
excellent advice and efforts helped me to learn valuable and useful lessons, which encouraged
me a lot in this project work and also would help me in my future vocation.
Also, I would like to thank my parents and all my friends for their immense support
and co-operation. They taught me great lessons and were the most significant support in
my life. Finally, I would like to thank once again Chandan Sir without whom this project
would have been just a distant reality.
4
Contents
Abstract 3
Acknowledgement 4
List of Figures 7
1 Introduction 1
1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Problem Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3 Contributions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.4 Chapter Organizations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2 Literature Survey 5
2.1 Formal Verification of HLS . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.1.1 Phase-wise Verification of HLS . . . . . . . . . . . . . . . . . . . . . 5
2.1.2 Simulation based Verification of HLS . . . . . . . . . . . . . . . . . 7
2.2 End-to-End Verifcation of HLS . . . . . . . . . . . . . . . . . . . . . . . . 8
2.3 Limitations of Existing Methods . . . . . . . . . . . . . . . . . . . . . . . . 9
5
3.3 Equivalent Paths and Outputs . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.4 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.5 Drawbacks of Algorithm-1 . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Bibliography 37
References 37
6
List of Figures
7
Chapter 1
Introduction
1. Pre-processing step involves breaking the code into three address format, specifying
the no of the input port to use, deciding the basic blocks for the final version of
code and forming the intermediate representation (CDFG). This step also includes
analysing data dependency, performing live analysis and applying compiler optimisa-
tions.
2. The next step is the scheduling phase is operations are assigned with different time-
1
Fig. 1.1 High-level Synthesis
steps, which decide the way operations must be scheduled. Algorithms like ASAP,
ALAP, list scheduling, etc. are used for scheduling purpose and are categorized based
on resource and timing constraints
3. The allocation and binding phase allocates registers for variables and function units
(FUs) for the operations in each of the control steps. The goal is to optimize re-
sources in a way that register and FU sharing is maximum and without hampering
the functionality of existing operations..
2
1.1 Motivation
As described in the above phases, input behaviour is modified in several ways to finally
mapped to RTLs. Moreover, many optimizations are applied in each phase, like minimiza-
tion of time steps to schedule the operations, apply code motions during scheduling, etc.
So there is no one to one correspondence between output and input of HLS. Because of
multiple phases and changes, there might be a chance of error introduced. So we need to
find a verification method for the HLS tools correctness. There are many tools in industry
as well as academia that can be used for high-level synthesis. Some of the examples are
Spark, Legup, Bambu, Vivado HLS, etc. As these tools are widely used, it is very important
to check the correctness of these tools, i.e. the equivalence of output and input, which is a
C code and RTL code. There are some methods to check equivalence between C, and RTL
one of them is simulation. But obviously, we can’t try to simulate every input test cases
and check the direct value output equivalent. Another method is actually verifying using
formal methods [5]. [4] proposes equivalence checking in datapath and controller generation
phase whereas [3] deals with register sharing verification. All the methods mentioned above
deal with different phases of equivalence checking. But there is no end-to-end method for
checking equivalence. So our motivation is to give a complete end-to-end method for the
equivalence checking of c and RTL for verification of the HLS tool.
Given a C code and its corresponding RTL generated using an HLS tool, we want to check
whether the two codes are equivalent or not.
1.3 Contributions
3
• We proposed a basic equivalence checking algorithm in which we find all the traces
in both input and output behaviour. After finding traces, we showed trace level
equivalence using SMT solver, i.e. which shows trace equivalence between traces of
input, output behaviour. To reduce the time complexity, we introduced a data-driven
approach.
• We come up with a new framework to reduce the time complexity, Data-driven cut-
point based equivalence checking. In this framework, we break the behaviour in
between. Specifically, we introduced cutpoints in each behaviour, and we showed
equivalence of paths between cutpoints instead of traces of the complete program.
Since output behaviour is an RTL code of input behaviour, there exists a correla-
tion between variables in input behaviour and register in output behaviour. But we
don’t know the correlations, so we used invariant generator Daikon to identify the
correlation between register and variables, which is a data-driven method. Once we
know relations at each cutpoint, equivalence checking is done by the SMT solver, thus
performing path level equivalence at cutpoints.
• We tested both frameworks on a commercial HLS tool, Vivado HLS from Xilinx. We
collected some standard benchmarks and generated corresponding RTL using Vivado
HLS. We checked the output equivalence between source C code and generated RTL.
Thus our frameworks work for real-world HLS tool.
The rest of the thesis is organized as follows: Chapter 2 contains literature. Chapter 3
contains Basic algorithm for equivalence checking. Chapter 4 contains new data driven cut
point based equivalency checking with implementation details. Finally chapter 5 contains
results and my future work plans.
4
Chapter 2
Literature Survey
There has been a lot of research in the field of equivalence checking of two programs.
Considering the equivalence of RTL code generated from C like behavioural code, the task
of equivalence checking is a little tricky. There are mainly two approaches to equivalence
checking: a phase-wise equivalence checking, i.e. testing equivalence after different phases
of HLS and the other being an end-to-end equivalence checking.
In phase-wise equivalence checking, the main idea is to show the equivalence for the input
and output FSMDs for each of the HLS phases like scheduling, allocation and binding, dat-
apath and control path generation phase, etc. On the contrary, the end to end equivalence
is done using SMT based approach, data-driven and simulation-based approaches, which
may or may not require any information from the tool.
Several works of literature have focused on the equivalence checking of various phases of
HLS. The work mentioned in the verification of datapath and controller generation
phase HLS [4] have proposed a method to generate the FSMD using the controller and
5
datapath interaction. Another FSMD produced after the allocation and binding phase
is also considered. The equivalence between both the FSM is then verified to check the
correctness of the controller. The main focus of this literature is the generation of FSMD
using the datapath interaction and controller. To check the flow of data, the control
assertion pattern generated by the controller needs to be feed in the circuit, which executes a
set of micro-operations to complete the corresponding register transfer operation. The task
deduces to identifying micro-operations for a given assertion pattern. This is achieved using
a rewriting algorithm that takes an RT operation as input and generates corresponding
micro-operations as the output. Using the combination of micro-operation generated as
a part of the datapath and the controller, the FSM can be realized. Finally, a state-
based equivalence algorithm is used to verify the correctness of the generated controller
with respect to the FSM of the previous phase using a state by state equivalence checking
algorithm. Another work proposed in [3] verifies the register sharing using a formal method.
Their main aim is to show the equivalence of allocation and binding phase. The two
behaviours before and after the datapath synthesis are modelled as FSMDs. A mapping
is formulated between the variables and the registers before and after the allocation and
binding phase, respectively. The equivalence is shown for the mapping by comparing the
computations for their FSMDs. Finally, the verification is done by using path covers and
cut-point based equivalence checking. For that, the state-wise equivalence of all the paths
forming the path covers is proved.
The cutpoints based methods, as explained in [8] is also somewhat similar to one men-
tioned in in [5], which focuses on equivalence checking of scheduling phase of HLS
. In this work, the behaviour that is given input to the scheduler is represented as FSMD
M0, and the behaviour that is recorded as the output of the scheduler is represented as
FSMD M1. The path traces of M0 is checked for equivalence with M1, and the same is
repeated by exchanging both the FSMD. A recent work related to scheduling phase verifica-
tion is proposed in [1]. This work extends the path based equivalence checking (PBEC) to
6
overcome the limitation of split and merge type of constructs for which other PBEC based
equivalence methods fails. It uses value propagation (VP) based verification of scheduling
which can handle code motion and control structure based modification over loops. It
outperforms the existing state of the art approaches.
7
generated for the behavioural template. The behavioural templates generated, unlike our
proposed method, does not represent the original RTL completely. Similarly is the case
with [2] or the works mentioned in [7]. Verilator simulation tool [9] generates a generic
C++ executable from Verilog RTL but does not produce a synthesizable C program that
can be directly used for High-level synthesis.
8
2.3 Limitations of Existing Methods
The phase-wise equivalence checking performs good, but with the condition that we require
some information from the tool and some intermediate results to show the equivalence, and
which is not always available. For an end to end equivalence checking, the limitations are
different and depend on the approach. For example, for data-driven equivalence, checking
a lot of data covering all the path flows of the program is required. Some work for an end to
end verification of HLS is error-prone and unable to identify the exact sub-task where the
error occurs. Some of them require exponential time in some cases to check equivalence.
The simulation-based approaches have targeted accelerating the simulation using various
methods. However, their only aim is to accelerate the design, but the design flow of RTL is
not preserved for obtaining the results. Some other simulation-based methods are targeting
different results like design space exploration, optimizations, etc., and their primary focus
is not actually speeding up the simulation process. All these works do not provide support
for RTL generated from Xilinx Vivado, and some have constraints like support for single-
module RTL designs with no components or interfaces.
9
Chapter 3
The basic idea is to check whether the c and RTL code is equivalent. That is, they give
the same output in every possible input. To do this, we first convert the RTL to a C code
using the method given in [KSM10]. This part is already implemented, and we assume that
RTL to c conversion is correct without any error. we got a c code that is equivalent to the
RTL code, so the problem statement reduces to check whether two c codes are equivalent
or not. (refer to Figure 3.1)
10
We want to get the correspondence formula of two c codes in smt-lib format, and we
use z3 to check whether both are equivalent or not. z3 is an SMT solver developed by
Microsoft. When we get two c codes in Bool formula, let’s say formula1 and formula2, we
pass the following forumla and check if it has a solution.
If yes, then we can say that two c codes are not equivalent and z3 also give a solution
to the formula, so we also have proof, i.e. test case whether the output of two c codes is
different. If the equation has no solution, then we can say that two c codes are equivalent.
BUT equation (1) is SAT which is undecidable problem. Even though Z3 has some good
heuristics, in the worst case, z3 takes exponential time, which is costly and not affordable.
So we have to keep formula size as less as possible. So instead of passing the whole code
in a formula, we have to give a formula corresponding to each trace. This will work faster
than direct comparison as a whole.
We can further break down the formula to make it smaller and hence increase the chances
of Z3 finishing the equivalence checking in time. That idea is also discussed in the next
chapters.
To break down the formula we are using KLEE. KLEE is a dynamic symbolic execution
engine built on top of the LLVM compiler infrastructure. Klee gives the traces for given c
file as input. Specifically,We provide klee with a input c file and a set of input variables
for the c file and a set of output variable for the c file then klee gives traces and for each
trace, for each output variable klee print its value in terms of input variables if we follow
the trace. We will use this traces expression for equivalence checking in trace level.
For implementing the given idea, we also need to do some changes in Klee’s source code.
The changes are in the function managing klee print expression call from Klee. We just
11
change the function to print the path constraint and the output expression both in smtlib
format instead of printing only the output expression in kquery format. While printing, we
also try to print the expressions and path constraints in a way which would make crawling
easier. This is because we finally want to automate the processor and we will need to
differentiate between path constraints and output expressions.
3.2 Algorithm 1
We mentioned in last section that the idea is to compare the C codes trace-wise instead
of comparing them as a whole. But for that we actually need to know which trace in first
code maps to which trace in the second code
One naive way to do that is to check all paths in the input C with all traces in the
second program. But that takes more time. So we propose algorithm-1 for efficient way to
identifying correspondence of traces. But first, we need to do some pre-processing in traces
corresponding to one C code itself. We actually try to merge traces which have the same
outputs. So we have less no of equally potential no of traces for one trace in the source
program.
Let’s assume C1, C2 be the set of conditions in source code and generated code respec-
tively after merging. Let Out1, Out2 be set of outputs in source code and generated code
respectively. Now we assume that there can be more than one output variables in a code
and hence N represents number of output variables
12
For most of the cases, for one path in source program there exist exactly one equivalent
path in generated program. But due to composite output functions we might not able to
merge some paths. Then there is possible set of paths in source combined to equivalent to
a set of paths in generated paths For example set of paths in source program be
Because of this potential many to many equivalent paths.We divide equivalence checking
into two parts.In first part, we check the paths that has exactly one potential path and
equivalence check between their outputs .In Second part, We need to do equivalence check
between remaining paths
For the first part, we find potential one to one equivalent paths using tracing. KLEE
gives trace input for each path. Consider path C1x and let Trace test case following this
path is T. We run generated code 2 with T and find path C2y satisfying T. if C1x exactly
equivalent to C2y then we use OutMatch function to check Whether outputs are same for
corresponding path C1x ,C2y . If outputs are not equivalent, we can stop algo and return
saying that both programs are not equivalent. This computation takes care all potential
one to one equivalent paths
13
For second part, we are left with set paths in each programs.For a path C1x in source
program it is enough and sufficient to consider paths in C2y if
C1x ∩ C2y ! = ∅
For paths that have intersection not null, we check whether outputs are equal or not.
For each path in source paths if outputs are equal with outputs of intersected paths in
generated paths, we can conclude both programs are equivalent
14
Algorithm 1: C to RTL EqCheck (C, RTL-C)
Input: Input-C, RTL-C
Result: Equivalent, May not equivalent
T0 = findTrace(C); T1 = findTrace(RTL-C);
T0 = mergeTrace (T0 ); T1 = mergeTrace (T1 );
copyT0 = T0 ; copyT1 = T1 ; flag = 0;
while T0 6= φ do
τ0 = select a trace from T0 ;
TC = getTestcase(τ0 );
τ1 = getCorrespondingTrace(T1 , TC);
// let C1x = condition for trace τ0
// let C2y = condition for trace τ1
if (CondM atch(C1x , C2y )) then
if (OutM atch(C1x , C2y , Out1x , Out2y )) then
//τ0 and τ1 are equivalent
removeTrace (τ0 , copyT0 );
removeTrace (τ1 , copyT1 );
end
else
//τ0 and τ1 may not equivalent
Report “May not equivalent” and Exit;
end
end
else
flag = 1;
end
endif
removeTrace (τ0 , T0 );
end
endwhile
if (flag = 1) then
foreach τ0 ∈ copyT0 do
foreach τ1 ∈ copyT1 do
// let C1x = condition for trace τ0
// let C2y = condition for trace τ1
if isIntersectiontrue(C1x , C2y ) then
if (!(OutM atch(C1x , C2y , Out1x , Out2y ))) then
Report “May not equivalent” and Exit;
end
end
Report Equivalent;
15
3.4 Results
16
equivalence checking takes more time.
• If the formulas are too big, Z3 might not stop and hence we may not get any output.
• As we are using Klee, we cannot deal with loops having the variable length as these
loops can’t be unfolded.
So, to deal with these two problems, we propose the new algorithm, which we call a data-
driven cutpoint based algorithm.
17
Chapter 4
As mentioned in the previous chapter, breaking down the code into traces is not enough.
Source C code and generated C code are similar in Functionality. They do the same
execution on input variables.This infers that there will be an indirect equivalence between
the variables in both programs, i.e., if the value of one expression is stored in one variable
then most probably that expression will be calculated and stored in some register while
doing HLS synthesis, i.e. in corresponding RTL C code, there will be a variable in which the
expression is evaluated and stored in it. So if we are able to find the equivalence between
the variables of two programs, then simply checking the relation between output variables
of two program will be enough. But directly finding the relation between variables of two
programs at the end of variables is not possible. So we break the code into chunks, i.e.
we introduce some breakpoints in both programs which we call as cutpoints. We say that
cut point c1x in program 1 is equivalent to c2x in program 2 if the program1 before c1x is
computed by program2 before c2x. For example, if there is a loop in program1 then in the
18
generated program also there will be a corresponding loop. So the point of starting a loop
in both programs are equivalent i.e. they are dong similar execution on input variables till
that point. So most probably variables calculated in program1 before cut point c1x will
be calculated by some variables in program2. We will find this relation between these live
variables and use this to find the relation at next pair of equivalence cut point, and we will
do this till we reach the last cut points (end of the program is considered as one cut point
which is last cut point). Cut-points are chosen such that there is more no of an equivalence
relation. Our goal is to show equivalency of the last pair of cut point (end of programs
are considered as one cut point). To find the relation which we call invariant, we use a
data-driven model described in [8]. Basically, to find the relation at the cut point pair, we
will simulate the program before cut points and find the values of variables and based on
the value of variables, we will find the relation. But because of this less number of test case
we might get false invariant, i.e. there is a test case where this invariant is failing but we
are not inserted that in the test case for simulation. So we have to prove the invariant and
find the true invariant and discard the false invariant. To do this, we use Klee and z3 in
the same way algorithm 1 used but in this algorithm instead making input variables equal
as input condition, here we make true invariant before cut point as input condition and
program between the cut point as a new program and instead of checking output variable
equal we check invariant obtained through data-driven model and find the true invariants.
Since I am only using the program between the cut point, the size of the path formulas
will be less, which is taking care of the first drawback of Algorithm I. Also, in [8] data-
driven model is given to check the equivalency of loops. We use the same model to solve
the second drawback of the algorithm 1.
19
between live variables at l1x in program1 to live variables at l2x, this relation is called at
invariant. Our final goal is to find the invariant at <c1n, c2n>(program endpoints). We
will do that inductively.
Let us define adjacent cut points before going to the inductive method. <c1x, c1y> will
be adjacent cut points pair if there is a path from c1x to c1y without reaching another
cut point in between. We will calculate for adjacent cutpoint of each cutpoint using by
simulation of source and generated program. c1y is called the child of c1x, or adjacent
cutpoint of c1y and c1x is called as parent cutpoint of c1y
Inductive method: We will calculate invariant at each adjacent cutpoint of the initial
cutpoint starting from the initial cut point using the code corresponding to the path for
adjacent point, Klee and smt we find all true invariants (the invariant that mathematically)
removing false invariants. This preprocessing for the initial cutpoint make at least one other
cutpoint whose parents preprocessing is completed. Once we are done with this preprocess
for the initial cutpoint, we will do it for cutpoints whose parents preprocessing is finished.
We will do this till this preprocessing is finished for each cutpoint and finally check whether
the invariant at the last cut point has output variables equal invariant or not.
To deal with the loops, cut point are chosen in such a way that it will decompose
the program into loop-free segments using three cutpoints, i.e., cutpoint 1 before for loop
condition cut point2 just after loop condition and cut point3 at the end of the loop. Detailed
explanation is given in [8].
For my current work, I am assuming cut point are given and their corresponding, adjacent
cutpoints in the script.txt file in order of induction method and code corresponding for paths
between adjacents cutpoints. Inductively, I have to find and prove the invariant at each
cut point till the last cut point
20
Algorithm 2: Induction method
while each adjacent pair <c1x,c1y>,<c2x,c2y> in script.txt do
// run klee on code for path from c1x->c1y, c2x->c2y assuming Ix
is true
CC,VAR EXPR = run klee(ix,c1x,c1y,c2x,c2y);
// algorithm 3
Invarient check at y(CC,VAR EXPR,Iy);
end
for each Out1x,Out2x in Output variables do
if Out1x == Out2x ∈ / In then
Report “May not equivalent” and Exit;
end
end
Report equivalent;
I mentioned that to prove equivalency at a cut point, and we need invariants at that cut
point. I used the Daikon invariant detector for this purpose. Daikon reports likely program
invariants. Daikon basically gives a relation between the variables in one program, but
we need the relation between the variables of two programs. To implement this thing, I
combined both codes in one file invarient combine code.c in the following way:-
Combine program to find invariant between two programs1, 2
21
10 line; \\ for each line in l11 to l1x
11 ........
12 ........
13 var1 = var \\ for each var in program1
14 ......
15 }
16
17 void fun2(){
18 int var = var2 \\for each var in program1
19 ........
20 ........
21 line \\ for each line in l21 to l2x
22 ........
23 ........
24 var2 = var \\ for each var in program1
25 ........
26 ........
27 }
28 void combine(){
29 fun1();
30 fun2();
31 \\ INVARIANT POINT
32 }
33 int main(){
34 for i = 0 t0 no_test_cases:
35 for var in input_variables:
36 scanf("%d",var1);
37 var2 = var1
38 }
While combining both codes, I need to make a difference between variables in code1 and
code2 so for each variable in program1 concatenated “1” at the end and similarly “2” in pro-
22
gram2. Daikon gives invariant at each function start and endpoints i.e. at fun1:::ENTER ,
fun1:::EXIT, fun2:::ENTER , fun2:::EXIT ,combine:::ENTER combine:::EXIT , main:::ENTER
, main:::EXIT.
We are interested in comine:::EXIT point where both programs executed, and this com-
bine function will be executed no test cases times. So daikon sees the values of variables
exist at combine:::EXIT, i.e. var1 and var2 no test cases times. Since we are pasting code
from l11 to l1x and l21 to l2x, invariants obtained will be Ix. Based on these values Daikon
given the relation between variables which is invariant. But Daikon gave invariants based
on the test cases ran, i.e. these invariants will be true only for the given test cases, there
might be a test case where the invariant will fail. So the next step is to find out the true
invariants and discard falsified invariant that occur due to lack of sufficient test cases.
Above section explains how to find Invariant Ix at cutpoint cx . This section explains how
to use the Ix to find the equivalency at cx. Since we are proving using induction algorithm,
we also know previous invariants at c1x’s ancestors, which are always true. We use Klee
and z3 to find the invariant which are true for every possible test case and discard some
falsified invariant. Following combine code is used for the implementation:-
Combine program to get output expression for each variables of programs 1, 2
23
9
10 void fun1(){
11 int var = var1 \\for each var in program1
12 ........
13
22 void fun2(){
23 int var = var2 \\for each var in program1
24 ........
25 ........
26
36 void combine(){
37 fun1();
38 fun2();
39 \\ INVARIANT POINT
40 }
24
41 int main(){
42 klee_make_symbolic(&var1,sizeof(var1), var1 ); // for each var in program1
43 ...........
44 ...........
45
53 combine();
54 klee_print_expr( var21 := ,var1) //for each var in program1
55 ............
56 ............
57
Proving equivalence between cut point is enough to show equivalency for the whole
program using the induction method. Klee will give the expression for each variables in
terms of input variables for each path. For each invariant and path using the expression we
will pass that formula to z3 which gives a test case if the invariant is false. is true function
implementation is explained in algorithm 1.
25
Algorithm 3: Invariant Finding Algorithm
invariant flag = True;
failed inv = ””;
fail invs = [];
idx = 0;
while invariant flag is false do
Ix = run daikon.sh x; // run daikon for new invariant for new
testcase set
invariant flag = true;
// failed inv still present in Ix,do the extra computation
if failed inv ∈ Ix then
fail invs.append(failed inv);
idx = idx+1;
end
bf idx = idx;
for each i in Ix[ bf idx+1: ] do
flag = true;
for each c in CC do
cur flag,test case = is true(i,c,VAR EXPR);
if cur flag is False then
invariant flag = false;
insert testcase(test case);
break;
end
end
if invariant flag is false then
break;
end
idx = idx + 1;
end
end
// Extra computation to solve invariants in fail inv.txt
correct inv = [];
for each i in fail invs do
inv flag = algorithm1(i,l1x,l2x);
if inv flag then
correct inv.append(i);
end
end
Ix.extend(correct inv);
26
Let’s assume that Ix is a set of invariant at cut point x and var21, var22 and CC be set of
paths in the file. For each I,c, we will pass these through is true function with VAR EXPR,
which is output expression of variables that are involved in invariant i, this is true with
the help of Z3 gives cur flag and test case. cur flag will be false if the invariant i is false
for a test case if c path is followed and that test case on which i fails will be returned
in test case else test case will be null. When invariant flag becomes false, we break the
loop and run the daikon again to find new invariants generated by the new test case. But
we found a loophole because of insufficient invariants at parent cutpoint, i.e. c path may
not have enough precondition. Hence, smt might say that given invariants is not always
true and gives a test case on which is false, but actually, it may be true invariant and
running Daikon on that test case still produce i. So to solve this issue, we push all this
type of invariants in the fail invs list and leaving i, we continue the execution of remaining
invariants. After the above process, fail invs have some invariants that need to be proved
or discarded. To do this, we use algorithm 1 but only considering part of the program
instead of considering the whole program, i.e. program till the cutpoint. Algorithm 1 may
take time, but it produces correct results. Through this, we discard falsified invariants and
only leave Ix with invariants proven mathematically.
4.4 Conclusion
In this chapter, we went through why we shifted to the cut point algorithm, how it is
solving the drawback of Algorithm 1 using induction method in Algorithm 2, how to find
invariants at a cutpoint. After finding the invariants, we explained Algorithm 3 to prove
the correctness of invariants using SMT solver.
27
Chapter 5
5.1 Results
28
Listing 5.1 program 1
Listing 5.2 program 2
1
38 int main(){
2 int main(){
39 int in1, in2, in3, in4, in7, in8,
3
in9, in10, in14, in12, in15,
4 int in1, in2, in3, in4, in7, in8,
in17, in19, in20, in22, in24,
in9, in10, in14, in12, in15,
in27, in28, in29, in32, out13,
in17, in19, in20, in22, in24,
out30, out31;
in27, in28, in29, in32, out13,
40 //cut 0
out30, out31;
41 int t5, t6, t11, t16, t18, t21,
5 //cut 0
t23, t25, t26;
6 int t5, t6, t11, t16, t18, t21,
42 int return_port;
t23, t25, t26;
43 out30 = 0;
7 int return_port;
44 t5 = in3 - in4;
8 out30 = 0;
45 t6 = in7 + in8;
9 t5 = in3 - in4;
46 out13 = in14 - in15;
10 t6 = in7 + in8;
47 //cut 1
11 out13 = in14 - in15;
48 if (in2 == in1)
12 t11 = t6 + in12;
49 {
13 //cut 1
50 t11 = t6 + in12;
14
51 t16 = t11 - in17;
15 if (in2 == in1)
52 }
16 t16 = t11 - in17;
53 else
17 else
54 {
18 {
55 t18 = in19 + in20;
19 t18 = in19 + in20;
56 if (in9 < in10)
20 if (in9 < in10)
57 {
21 {
58 t11 = t6 + in12;
22 t21 = t11 + in22;
59 t21 = t11 + in22;
23 t23 = t5 - in24;
60 t23 = t5 - in24;
24 t25 = t21 + t23;
61 t25 = t21 + t23;
25 t26 = t25 + in27;
62 t26 = t25 + in27;
26 }
63 }
27 else
64 else
28 t26 = t5 + t18;
65 t26 = t5 + t18;
29 t16 = t26 - in28;
66 t16 = t26 - in28;
30 out30 = t26 + in29;
67 out30 = t26 + in29;
31 }
68 }
32 out31 = t16 + in32;
69 out31 = t16 + in32;
33 return_port = out13 + out30 + out31;
70 return_port = out13 + out30 + out31;
34 //cut 2
71 //cut 2
35
72 return out13 + out30 + out31;
36 return out13 + out30 + out31;
73 }
37 }
Input to program1 are variables on line no 4 and for program 2 at line no 38. Output
port to program1 is return port and for program 2 is return port; To each variables name
in the first program i concatenated 21 and 22 to variable in second programs just to make
difference between variables in program1 and program2. Our goal is show return port21 =
29
return port22.
If you see the difference between the programs is t11 in first code is calculated at line no
12 in program 1 but in program 2 t11 is calculated whenever it is needed i.e at line no 50
and 58.code is shifted later in program 2, since these type of code motions happens HLS
this example gives the analogy how data driven and cut point is working.
Invariant at cut0 is input variables are same
1 in121 == in122
2 in221 == in222
3 in321 == in322
4 in421 == in422
5 in721 == in722
6 in821 == in822
7 in921 == in922
8 in1021 == in1022
9 in1221 == in1222
10 in1421 == in1422
11 in1521 == in1522
12 in1721 == in1722
13 in1921 == in1922
14 in2021 == in2022
15 in2221 == in2222
16 in2421 == in2422
17 in2721 == in2722
18 in2821 == in2822
19 in2921 == in2922
20 in3221 == in3222
Invariant at cut1 :-
iteration 1:
1 in121 == in122
2 in221 == in222
30
3 in321 == in322
4 in421 == in422
5 in721 == in722
6 in821 == in822
7 in921 == in922
8 in1021 == in1022
9 in1421 == in1422
10 in1221 == in1222
11 in1521 == in1522
12 in1721 == in1722
13 in1921 == in1922
14 in2021 == in2022
15 in2221 == in2222
16 in2421 == in2422
17 in2721 == in2722
18 in2821 == in2822
19 in2921 == in2922
20 in3221 == in3222
21 out1321 == out1322
22 t521 == t522
23 t621 == t622
24 in321 - in421 - t521 == 0
25 in721 + in821 - t621 == 0
26 in1421 - in1521 - out1321 == 0
27 in1221 + t621 - t1121 == 0
since all invariant produced by daikon is true,algorithm 1 stopped in one iteration i.e algo
1 entered the while loop only once.
Invariant at cut2:-
iteration 1
31
1 in121 == in122
2 in221 == in222
3 in321 == in322
4 in421 == in422
5 in721 == in722
6 in821 == in822
7 in921 == in922
8 in1021 == in1022
9 in1421 == in1422
10 in1221 == in1222
11 in1521 == in1522
12 in1721 == in1722
13 in1921 == in1922
14 in2021 == in2022
15 in2221 == in2222
16 in2421 == in2422
17 in2721 == in2722
18 in2821 == in2822
19 in2921 == in2922
20 in3221 == in3222
21 out1321 == out1322
22 out3021 == out3022
23 out3121 == out3122
24 t521 == t522
25 t621 == t622
26 t1621 == t1622
27 t1821 == t1822
28 t2121 == t2122
29 t2321 == t2322
30 t2521 == t2522
31 t2621 == t2622
32 return_port21 == return_port22
32
33 in321 - in421 - t521 == 0
34 in721 + in821 - t621 == 0
35 in1421 - in1521 - out1321 == 0
36 in1221 + t621 - t1121 == 0
37 in3221 - out3121 + t1621 == 0
38 t2121 + t2321 - t2521 == 0
At cut2 also invariants produced by daikon is true in first iteration. If you observe the
invariant, one of them is return port21 == return port22. and we proved these invariants
actually true for all possible test cases. So programs are equivalent.
Results on some Test benchmarks
The experiment results of our benchmarks are shown in Table 5.1. For each benchmark,
the 2nd (#in) and 3rd (#out) show the number of inputs and outputs for each benchmark,
respectively. We have recorded the number of code lines (#line) and variables (#var) of
the input C in 4th and 5th columns, respectively. The 6th and 7th columns represent the
number of lines (#line) and registers (#regs) in Verilog RTL code generated by Vivado
HLS, respectively. The 8th and 9th columns show the number of lines (#line) and the
number of variables (#var) in generated RTL-C code, respectively.The 10th , 11th are no of
cuts points in input C and generated RTL-C code. The 12th contains the no of invariants
at each cutpoint which is mathematically proven removing the falsified invariants.13th is
the total time spent by our equivalent checking framework to solve all invariants at each
cut point, thus proving the equivalence
33
5.2 Observations
• We found that if Daikon didn’t produce enough invariants, then we have to do the
extra computation in Algorithm 2 which is similar to Algorithm 1 i.e. time spent for
proving will be more.
• If we look into the huge no of invariants generated at each cutpoint, so many of them
are not useful. But we are proving them mathematically, which is costly in terms of
time.
• From the testbench, we have observed that if Daikon produce enough invariants, then
our algorithm doesn’t need to do the extra computation, thus saving a lot of time.
• In generated RTL-C code, each variable is mapped to one real-time register, and only
those registers are used later in RTL-C code. So invariants that map the variable in
RTL-c to variable in C code will be enough and sufficient for Algorithm 2.
From the observations, we need fewer and sufficient invariants to be generated at each
cutpoint so that our algorithm works efficiently. So instead of directly comparing C code
and generated RTL-C code, we compare generated C code with scheduled C code generated
from source C code. [1] proposed the method to generate equivalent Scheduled C code to
C code. Generated C code has the same structure as the scheduled C code, i.e. states will
be the same. Other research under Prof, Karfa shows the mapping between registers in
generated C code to the variables in Scheduled C code can be recovered 100% for most
of the test cases. Hence, working on Scheduled C code and generated would give better
results in terms of time. So my future work is to show the equivalence between scheduled
C code and RTL.
34
Algorithm 4: Algorithm-1
Input: Input-C, RTL-C
Result: Equivalent, May not equivalent
T0 = findTrace(C); T1 = findTrace(RTL-C);
T0 = mergeTrace (T0 ); T1 = mergeTrace (T1 );
copyT0 = T0 ; copyT1 = T1 ; flag = 0;
//First part
while T0 6= φ do
τ0 = select a trace from T0 ;
TC = getTestcase(τ0 );
τ1 = getCorrespondingTrace(T1 , TC);
if ((cτ0 ≡ cτ1 ) ∧ (sτ0 ≡ sτ1 )) then
//τ0 and τ1 are equivalent
removeTrace (τ0 , copyT0 );
removeTrace (τ1 , copyT1 );
end
else if ((cτ0 ≡ cτ1 ) ∧ (sτ0 6= sτ1 )) then
//τ0 and τ1 may not equivalent
Report “May not equivalent” and Exit;
else
flag=1;
end
endif
removeTrace (τ0 , T0 );
end
endwhile
//second part
if (flag = 1) then
foreach τ0 ∈ copyT0 do
foreach τ1 ∈ copyT1 do
if (cτ0 ∧ cτ1 6= φ) then
if (cτ0 ∧ cτ1 ∧ sτ0 6= sτ1 ) then
Report “May not equivalent” and Exit;
end
end
Report Equivalent;
35
Publication out of this work:
M. Abderrahman, R. Reddy, J. Patidar, C. Karfa, “C to RTL Translation Validation of
High-level Synthesis”, in 29th IFIP/IEEE International Conference on Very Large Scale
Integration. (under review)
Tools we used:
• KLEE Documentation
36
References
[2] Shubhani Gupta, Aseem Saxena, Anmol Mahajan, and Sorav Bansal. Effective use of
smt solvers for program equivalence checking through invariant-sketching and query-
decomposition. In Olaf Beyersdorff and Christoph M. Wintersteiger, editors, Theory
and Applications of Satisfiability Testing – SAT 2018, pages 365–382, Cham, 2018.
Springer International Publishing.
[3] Chandan Karfa, Chittaranjan A. Mandal, Dipankar Sarkar, and Chris Reade. Register
sharing verification during data-path synthesis. In Computing: Theory and Applica-
tions, 2007. ICCTA ’07. International Conference on,, pages 135–140. IEEE, 2007.
[4] Chandan Karfa, Dipankar Sarkar, and Chitta Mandal. Verification of datapath and
controller generation phase in high-level synthesis of digital circuits. IEEE Trans. on
CAD of Integrated Circuits and Systems, 29(3):479–492, 2010.
[5] Chandan Karfa, Dipankar Sarkar, Chitta Mandal, and P. Kumar. An equivalence-
checking method for scheduling verification in high-level synthesis. IEEE Transactions
on Computer-Aided Design of Integrated Circuits and Systems, 27(3):556–569, 2008.
37
[6] Anushree Mahapatra, Yidi Liu, and Benjamin [Carrion Schafer]. Accelerating cycle-
accurate system-level simulations through behavioral templates. Integration, 62:282 –
291, 2018.
[7] Anushree Mahapatra and Benjamin Carrión Schäfer. Veriintel2c: Abstracting rtl to c
to maximize high-level synthesis design space exploration. Integr., 64:1–12, 2019.
[8] Rahul Sharma, Eric Schkufza, Berkeley Churchill, and Alex Aiken. Data-driven equiv-
alence checking. SIGPLAN Not., 48(10):391–406, October 2013.
38