The Last two month I was working for a porject of my lab. And I am responsible for designing a Floating-point ALU module with Verilog. Due to its confidentiality, I can’t publish the original code of what I am doing right now(Or maybe forever). But the bugs I have fixed and the way I do all those testings is worthy to share.
Important Mistakes and Lessons
1. Convert a Two’s complement to its true form
Suppose there’s a w-bit number in true form, you can convert it to two’s complement without worrying anything, becasue the two’s complement can represent one more number than true form with same bits. (-0 in true form is interpreted as -2^(w - 1) in two’s complement).
But, before you convert a two’s complement to a true form number, ***Please do check if it is the minimun number in two’s complement!!!***.
2. Truncating in true form and two’s complement
Due to the ALU only have limited bits for store the temperate result and all those rounding and normalization stuff is handled in another phase of the pipeline. So I need to truncate the temperate result before the rounding.
The difference between truncating them in both forms is actually quite obvious when you sit down to think about it, but at first I just thought storing two more bits than the length of final result can abvoid all the errors.Well , it dosen’t. At least Not all the errors
Different stages | True form | Two’s complent |
---|---|---|
5-bit original number | 1.1001 | 1.0111 |
Turncate to 4 bits | 1.100 | 1.011 |
Decimal result | -0.5 | -0.625 |
Clearly truncating a negative number in true form can make it greater while truncating in two’s complement making it less. This could lead to a mistake when you transform the two’s complement to true form and then do the rounding or other operation. Store more bits during truancating can only reduce the posssibility of bumping into this bug. You should always take care of this suitation.
3. Other binding bugs in Verilog
You’d better make a cleat table that tells what does each bit in a vector represent. This can save you tones of time in debuging.
Testing(Using shell and python)
Test should include both random data test and corner case test.
Corner case test
Corner case should cover erery typical case of input and output. Store all the test case in a distinct file and keep enrich it during your debug is a good idea.
Random data test
This part is also inportant. It’s almost inevitable that you would forget to consider some quirky cases that make your code don’t work out as you wish. The truncating bug is found when I do the random case at about 500th round.
Reference data
To generate the reference data for my ALU, I use python to simulate the ALU. When the output is different, you should check both the code in Verilog and in python.
A glipse of the auto test script and python simulation code
Auto test:
#!/bin/bash/
# Select test times here
times=300
# --------
rm -f DIFF_*.dat
RAWFILENAME1="RawTestData_f.dat"
NEWINPUTDATA1="Input_f.dat"
RAWFILENAME2="RawTestData_h.dat"
NEWINPUTDATA2="Input_h.dat"
# Corner case test
cat "corner_case_f.dat" | awk '{printf $1"\n"}' > $NEWINPUTDATA1
cat "corner_case_f.dat" | awk '{printf $2"\n"}' >> $NEWINPUTDATA1
cat "corner_case_h.dat" | awk '{printf $1"\n"}' > $NEWINPUTDATA2
cat "corner_case_h.dat" | awk '{printf $2"\n"}' >> $NEWINPUTDATA2
iverilog -o test_tb.out test_tb.v
./test_tb.out
python3 ./Reference_data.py -h
python3 ./Reference_data.py -f
# 1 to 12 is the result except sum
diff <(awk -F'\t' '{for(i=1;i<=12;i++){if($2 != ""){printf $i"\t"}}if($2 != "")printf "\n"}' Output_f.dat) <(awk -F'\t' '{for(i=1;i<=12;i++){if($2 != ""){printf $i"\t"}}if($2 != "")printf "\n"}' $RAWFILENAME1) > DIFF_f.dat
diff <(awk -F'\t' '{for(i=1;i<=12;i++){if($2 != ""){printf $i"\t"}}if($2 != "")printf "\n"}' Output_h.dat) <(awk -F'\t' '{for(i=1;i<=12;i++){if($2 != ""){printf $i"\t"}}if($2 != "")printf "\n"}' $RAWFILENAME2) > DIFF_h.dat
diff <(awk -F