【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
大家都知道fpga是好东西,但是大家不清楚的,是fpga可以帮助我们做什么。平时,我们在电商网站可以买到一些fpga开发板,但是这些开发板提供的demo案例,mcu也可以去做。这样就会造成一个疑问,似乎就没有必要学习fpga。但事实上,fpga本来就应该去做mcu和soc做不了的事情,而不应该重复他们已经做过的事情。
所以,fpga要做的,应该是数量很多的低速接口、定制的高速接口以及算法加速功能。其中算法加速往往是最容易被大家忽视的部分。不管是pin上的信号,还是bus上的信号,本质上是一回事。所以,算法加速也是fpga非常擅长的一个领域。
1、是算法加速,而不是发明新算法
有一些应用,本身其实是可以用软件实现的,比如检测轮询、图像处理等等。用软件实现,要么占用cpu资源,要么效率不高,但是用fpga可以极大提高运行效率。所以说这些算法本身就是存在的,并不是fpga发明的,我们用fpga,只是为了让运行速度更快一点。
2、先实现软件算法,再改成verilog代码
用fpga做算法加速,最好不要一上来就写verilog代码,这是不好的习惯。对于大公司来说,算法工程师、软件工程师、软件工程师往往是分开来的。算法工程师负责算法和matlab仿真,软件工程师负责算法实现,fpga工程则负责算法加速。如果项目不大的话,这个时候可以自己先把软件写好,等软件没有问题之后,再切换成verilog代码,一方面实现起来心里比较有底,二来如果出现错误,也有一个参考比对。
3、以gpadc作为说明,如何做算法加速
gpadc是一种常用的按键检测手段。很多芯片的外部pin不多,特别是sop或者qfn封装的时候,如果是这种情况,很多人都希望用adc实现按键的检测。因为只要adc检测的精度够,就可以实现多按键的检测,这样还不会浪费pin的资源。
但是这样一项工作,如果是软件去做,还是会稍微麻烦一点,它需要做到这么几点,
算法步骤:
1)循环检测电压;
2)如果电压大于阈值,调到4;
3)检测有无置位,没有跳到5,有则跳到1;
4)检测有无置位,有则恢复为0,最后都是回到1;
5)等待一段时间;
6)重新检测电压,记录电压、置位,发送按键信息,跳到1.
整个检测过程其实并不复杂,但是软件做的坏处就是比较费cpu。所以一般的soc都会在底层,也就是硬件层面实现按键检测。用户只要配置好寄存器,设置好驱动就可以了。
4、改造为verilog代码
这是很明显的循环检测case,如果使用verilog+状态机处理的方法,其实是再合适不过的。首先还是设计好状态机代码,
// about state machine
always@(posedge clk or negedge rst)
if(!rst)
state <= IDLE; // idle
else
state <= next_state;
always@(*)
case(state)
IDLE:
if(in_valid)
next_state = RECV; // receive data
else
next_state = IDLE;
RECV:
if(voltage_data >= 16'd200)
next_state = VOLT_HIGH;
else if(!key1_flag && !key2_flag && !key3_flag) // check voltage_data data
next_state = WAIT;
else
next_state = IDLE;
WAIT:
if(counter == 16'd20) // wait for timer expired
next_state = STABLE;
else
next_state = WAIT;
STABLE:
if(in_valid) // make sure data is valid
next_state = TRIG_KEY;
else
next_state = IDLE;
VOLT_HIGH:
next_state = IDLE;
TRIG_KEY:
next_state = IDLE;
default:
next_state = IDLE;
endcase
有了状态机之后,还需要记录下当前的电压数值,
// about voltage_data
always@(posedge clk or negedge rst)
if(!rst)
voltage_data <= 16'h0;
else if((state == IDLE || state == STABLE) && in_valid)
voltage_data <= voltage;
最后就是在不同的状态下,判断电压范围,并且设置不同的标志位信息,
// about key1
always@(posedge clk or negedge rst)
if(!rst)
key1_flag_prev <= 1'b0;
else
key1_flag_prev <= key1_flag;
always@(posedge clk or negedge rst)
if(!rst)
key1_flag <= 1'b0;
else if(key1_flag && state == VOLT_HIGH)
key1_flag <= 1'b0;
else if(!key1_flag && state == TRIG_KEY && voltage_data >= 16'd100 && voltage_data < 16'd200)
key1_flag <= 1'b1;
// about key2
always@(posedge clk or negedge rst)
if(!rst)
key2_flag_prev <= 1'b0;
else
key2_flag_prev <= key2_flag;
always@(posedge clk or negedge rst)
if(!rst)
key2_flag <= 1'b0;
else if(key2_flag && state == VOLT_HIGH)
key2_flag <= 1'b0;
else if(!key2_flag && state == TRIG_KEY && voltage_data >= 16'd50 && voltage_data < 16'd100)
key2_flag <= 1'b1;
// about key3
always@(posedge clk or negedge rst)
if(!rst)
key3_flag_prev <= 1'b0;
else
key3_flag_prev <= key3_flag;
always@(posedge clk or negedge rst)
if(!rst)
key3_flag <= 1'b0;
else if(key3_flag && state == VOLT_HIGH)
key3_flag <= 1'b0;
else if(!key3_flag && state == TRIG_KEY && voltage_data >= 16'd0 && voltage_data < 16'd50)
key3_flag <= 1'b1;
// about final result
assign key1 = key1_flag & !key1_flag_prev;
assign key2 = key2_flag & !key2_flag_prev;
assign key3 = key3_flag & !key3_flag_prev;
理论上,只要adc检测的精度够,那么可以识别出很多的按键,可以帮助大大节省pin资源。
5、用testbench做交叉验证
编写好verilog代码后,是非常忌讳立即去做fpga测试的。这个时候,我们可以准备一个testbench文件先做好仿真测试,
`timescale 1ns/1ps
module test();
reg clk;
reg rst;
reg in_valid;
reg[15:0] voltage;
wire key1;
wire key2;
wire key4;
initial begin
clk = 0;
rst = 1;
in_valid = 1;
voltage = 250;
#10 rst = 0;
#30 rst = 1;
#50 voltage = 80;
#15 voltage = 0;
#320 voltage = 250;
#10000 $finish;
end
initial begin
while(1)
clk = #5 ~clk;
end
voltage_top voltage_top(
.clk(clk),
.rst(rst),
.in_valid(in_valid),
.voltage(voltage),
.key1(key1),
.key2(key2),
.key3(key3)
);
initial begin
$dumpfile("hello.vcd");
$dumpvars(0, test);
end
endmodule
等到仿真没有问题,仿真的波形也ok的时候,就可以到实际板子看看具体的运行情况了。