赞
踩
目录
写了这么久的Verilog代码,还没有系统的总结过到底什么是FPGA,它相对于单片机、DSP到底有哪些优势?这里说一下我个人的看法。
FPGA(Field Programmable Gate Array,简称 FPGA),译文:现场可编程门阵列,一 种主要以数字电路为主的集成芯片,它内部主要是通过无数个门电路搭建起来的,它的优势在于利用硬件并行执行,打破了顺序执行的模式,在每个时钟周期内完成更多的处理任务,超越了数字信号处理器(DSP)的运算能力。这里简单的说一下流水线并行,比如一个工厂生产一件产品,需要10个步骤,如果采用流水线的生产模式,其所有步骤同时执行,那么平均下来,只需要生产该产品一个步骤的时间就可以生产出这一件产品,这就是它的优势所在。同一时刻,不同级在处理不同的数据,那么下一个时刻,就会输出相应的数据,这就是FPAG的优势所在。其次,我们都知道CPU、GPU 其内部构成都属于冯·诺依曼结构,将输入的机器语言翻译成指令后进行执行,同时为了与各个执行单元之间进行通信,还需要一定的内存共享,而FPGA 本质上是无指令、无需共享内存,它是根据硬件描述语言(Verilog)来生成相应的逻辑电路,从而进行驱动控制,大大降低了执行时间。因此相对于大多数的处理芯片来说,FPGA一个最大的优势就是数据处理的速度快,延迟小。
对于FPGA的应用呢,个人感觉会偏向于通信和算法两方面。首先呢,我们都知道通信领域需要高速的通信协议处理方式,尤其是5g的使用,自动驾驶、智能家居等;另一方面呢通信的协议可能根据不同的要求随时都在修改,不适合做成专门的芯片,所以能够灵活改变的功能的FPGA就成了首选。而对于算法,这就更显而易见了,FPGA能够处理多维信号,满足实时性的要求,在图像处理、信号处理等领域将会有更广阔的应用前景。
当然啦,上述讨论的都是FPGA未来的发展,而我目前对于FPGA的学习主要还是以实验室的一些项目为主,大部分项目还是通过FPGA进行数据采集,同步采集多路信号,并进行实时的传输,而涉及到的一些控制领域的信号主要还是通过单片机来实现的,比如通过单片机驱动电机、驱动网卡等,那么这就避免不了FPGA与单片机之间进行通信,而它们两者之间的通信,我们是设计了属于他们两者之间特有的通信协议来实现的,下面就详细的介绍一下,它们之间通信的实现过程。
FPGA与单片机在硬件电路上一般采取20~24根线进行连接,我们主要是通过控制这20根数据线来实现两者之间的数据的并行通信。通常而言,单片机作为整个系统的主机,FPGA作为整个系统的从机,因此要实现将FPGA的数据传给单片机,则需要单片机来进行读操作,FPGA完成相应的数据发送;要实现单片机将数据传给FPGA,则需要单片机进行写操作,FPGA完成相应的数据接收。
接下来我们对这20根数据线进行命名:
mcu_wr:该信号是单片机发给FPGA的写请求信号
mcurd_flag:该信号是FPGA发给单片机的读请求信号
mcu_clk:该信号是单片机发送给FPGA读或写的时钟信号
mcu_en:该信号是单片机发送给FPGA读或写的使能信号
mcu_data[15:0]:该信号是FPGA与单片机之间进行数据通信的数据信号(双向)
如下图所示:
通信协议制定:
(1).FPGA向单片机发送数据,即单片机读数据
首先,FPGA向单片机发送一个mcurd_flag信号,该信号持续一段时间的高电平(默认为低),即代表两者之间通信的开始,而单片机如果检测到该信号的上升沿则进入中断,向FPGA发送读取数据的使能信号mcu_en和时钟信号mcu_clk,FPGA根据接收到的使能和时钟信号将所要发送的数据传递到数据线mcu_data[15:0]上。信号mcu_en、mcu_clk默认状态都为高电平,其时序图如下:
(2).单片机向FPGA发送数据,即单片机写数据
首先,单片机向FPGA发送一个mcu_wr信号,该信号持续一段时间的高电平(默认为低),即代表两者之间通信的开始,同样FPGA如果检测到该信号的上升沿则进入数据接收状态,根据单片机发来的数据使能信号mcu_en、数据时钟信号mcu_clk、数据信号mcu_data来进行数据的接收。其时序图如下:
要求:FPGA将外接采集到的数据存入RAM中,为了同时便于单片机读取FPGA采集到的数据,FPGA内部采用两块RAM,通过乒乓操作分别对两块RAM进行不同时刻的读写操作。同时单片机也可以在读取完数据后对FPGA进行写操作,即完成两者之间的半双工通信。
其系统框图如下图所示:
其中,ram_chioce信号表示选择两块ram的使能信号,ramend_flag信号表示ram已经写满,这两个信号由前一级模块给出。
接下来给出实验的Verilog代码:
- module fpga_mcu(
- input sys_clk,
- input sys_res,
- input ram_chioce, //选择两块ram(乒乓ram)的使能信号,低电平选择ram1,高电平选择ram2
- input ramend_flag, //某一块RAM写满的结束标志
- input [15:0] ram1_data, //ram1的输入的数据
- input [15:0] ram2_data, //ram2的输入的数据
-
- input mcu_clk, //单片机输入读或写数据的时钟(不发送时为高电平)
- input mcu_en, //单片机输入读或写数据的使能(低电平有效)
- input mcu_wr, //单片机输出写数据的标志(高电平有效),持续一段时间的高电平,代表即将向FPGA中写数据
-
- output reg [7:0] ram1_raddr, //输出读ram1的地址
- output reg [7:0] ram2_raddr, //输出读ram2的地址
-
- output reg mcurd_flag, //FPGA输出数据读取标志
- inout [15:0] mcu_data //FPGA与单片机之间通信的数据
- );
-
- parameter IDLE =6'b000001; //空闲状态
- parameter READ =6'b000010; //单片机读取数据状态
- parameter WAIT_WR =6'b000100; //等待单片机写数据
- parameter WRITE =6'b001000; //单片机写数据状态
- parameter STOP =6'b010000; //结束
- parameter addr_max =10-1; //最后一个数据地址+1(RAM的深度为10)
- parameter waittime =1000; //超时时间
- reg [7:0] ram_addr;
- reg [5:0] stata;
- reg [3:0] mcuclk_buff;
- reg [3:0] mcuen_buff;
- wire mcuclk_flag;
- wire mcuen_flag;
- reg [3:0] cnt_mcuflag; //对输出信号高电平的持续时间进行计数
- wire [15:0] mcu_rddata;
- //单片机写数据变量定义
- wire mcuwr_flag;
- reg [3:0] mcuwr_buff;
- reg [31:0] mcu_wrdata;
- reg [15:0] wr_data;
- reg wr_rd; //判断读写状态,默认低电平是单片机读
- //单片机读取FPGA发送过去的数据
- //对单片机输入的信号打两拍,消除亚稳态
- assign mcuclk_flag = mcuclk_buff[3] & (~mcuclk_buff[2]) ; //取下降沿,同时为了让时钟信号相对于使能信号滞后一个时钟周期
- assign mcuen_flag = mcuen_buff[1] & (~mcuen_buff[0]); //取下降沿
- always@(posedge sys_clk or negedge sys_res)
- if(!sys_res)begin
- mcuclk_buff <= 4'd0;
- mcuen_buff <= 4'd0;
- end
- else begin
- mcuclk_buff <= {mcuclk_buff[2:0],mcu_clk};
- mcuen_buff <= {mcuen_buff[2:0],mcu_en};
- end
- always@(posedge sys_clk or negedge sys_res)
- if(!sys_res)
- stata <= IDLE;
- else begin
- case(stata)
- IDLE: if(mcuen_flag == 1'b1)
- stata <= READ;
- else if(mcuwr_flag == 1'b1)
- stata <= WAIT_WR;
- else
- stata <= IDLE;
- READ: if((ram_addr == addr_max && mcuclk_flag == 1'b1)|| mcu_en == 1'b1)
- stata <= STOP;
- else
- stata <= READ;
- WAIT_WR:if(mcuen_flag == 1'b1)
- stata <= WRITE;
- else
- stata <= WAIT_WR;
- WRITE: if(mcu_en == 1'b1)
- stata <= STOP;
- else
- stata <= WRITE;
- STOP: stata <= IDLE;
- default:;
- endcase
- end
-
- always@(posedge sys_clk or negedge sys_res)
- if(!sys_res)
- ram_addr <= 'd0;
- else if((stata == READ && mcuclk_flag == 1'b1 && ram_addr == addr_max) || (stata == STOP))
- ram_addr <= 'd0;
- else if(stata == READ && mcuclk_flag == 1'b1)
- ram_addr <= ram_addr + 1;
- else
- ram_addr <= ram_addr;
- always@(posedge sys_clk or negedge sys_res)
- if(!sys_res)begin
- ram1_raddr <= 8'bzzzz_zzzz;
- ram2_raddr <= 8'bzzzz_zzzz;
- end
- else if(ram_chioce == 0)begin
- ram1_raddr <= ram_addr;
- ram2_raddr <= 8'bzzzz_zzzz;
- end
- else begin
- ram2_raddr <= ram_addr;
- ram1_raddr <= 8'bzzzz_zzzz;
- end
-
-
- always@(posedge sys_clk or negedge sys_res)
- if(!sys_res)
- mcurd_flag <= 1'b0;
- else if((cnt_mcuflag != 0) || (ramend_flag == 1'b1))
- mcurd_flag <= 1'b1;
- else
- mcurd_flag <= 1'b0;
- always@(posedge sys_clk or negedge sys_res)
- if(!sys_res)
- cnt_mcuflag <= 'd0;
- else if(ramend_flag == 1'b1 || cnt_mcuflag != 0)
- cnt_mcuflag <= cnt_mcuflag + 1;
- else if(cnt_mcuflag == 7)
- cnt_mcuflag <= 0;
- else
- cnt_mcuflag <= cnt_mcuflag;
-
-
- always@(posedge sys_clk or negedge sys_res)
- if(!sys_res)
- wr_rd <= 1'b0;
- else if((stata == WAIT_WR && mcuen_flag == 1'b1) || stata == WRITE)
- wr_rd <= 1'b1;
- else
- wr_rd <= 1'b0;
-
- assign mcu_rddata =(ram_chioce==0)? ram1_data:ram2_data ;
- assign mcu_data = (wr_rd == 1'b0)? mcu_rddata: 16'bzzzz_zzzz;
- //fpga接收单片机发送来的数据
- assign mcuwr_flag = mcuwr_buff[2] & (~mcuwr_buff[3]); //取上升沿
- always@(posedge sys_clk or negedge sys_res)
- if(!sys_res)begin
- mcuwr_buff <= 4'd0;
- end
- else begin
- mcuwr_buff <= {mcuwr_buff[2:0],mcu_wr};
- end
-
-
- always@(posedge sys_clk or negedge sys_res)
- if(!sys_res)
- mcu_wrdata <= 'd0;
- else if(wr_rd == 1'b1)
- mcu_wrdata <= {mcu_wrdata[15:0],mcu_data};
- else
- mcu_wrdata <= 'd0;
-
- always@(negedge mcuclk_buff[2] or negedge mcuen_buff[2])
- if(mcuen_buff[2])begin
- wr_data <= 'd0;
- end
- else
- wr_data <= mcu_wrdata[31:16];
-
-
- endmodule

仿真tb文件代码:
- `timescale 1ns/1ns
- module tb_c();
- reg clk;
- reg res;
- reg ram_chioce;
- reg ramend_flag;
- reg [15:0]ram1_data;
- reg [15:0]ram2_data;
- reg mcu_clk;
- reg mcu_en;
- wire [7:0] ram1_raddr;
- wire [7:0] ram2_raddr;
- wire mcu_flag;
- wire [15:0]mcu_data;
- reg we_rd; //低电平代表单片机读,默认为低
- reg mcu_wr;
- reg [15:0]data; //单片机发送的数据
-
- reg [15:0]mem_ram1[9:0]; //定义两块ram,fpga从ram中读取数据发送给单片机
- reg [15:0]mem_ram2[9:0];
- reg [14:0]cnt_ramchoice; //对两块ram使能信号的计数
- initial begin
- clk <= 1'b0;
- res <= 1'b0;
- mcu_en <= 1'b1;
- mcu_wr <= 1'b0;
- we_rd <= 1'b0;
- #100 res <= 1'b1;
- end
-
- always #10 clk <= ~clk;
-
- always@(posedge clk or negedge res)
- if(!res)begin
- mem_ram1[0] <= 16'd0;
- mem_ram1[1] <= 16'd0;
- mem_ram1[2] <= 16'd0;
- mem_ram1[3] <= 16'd0;
- mem_ram1[4] <= 16'd0;
- mem_ram1[5] <= 16'd0;
- mem_ram1[6] <= 16'd0;
- mem_ram1[7] <= 16'd0;
- mem_ram1[8] <= 16'd0;
- mem_ram1[9] <= 16'd0;
- end
- else begin
- mem_ram1[0] <= 16'h1111;
- mem_ram1[1] <= 16'h2222;
- mem_ram1[2] <= 16'h3333;
- mem_ram1[3] <= 16'h4444;
- mem_ram1[4] <= 16'h5555;
- mem_ram1[5] <= 16'h6666;
- mem_ram1[6] <= 16'h7777;
- mem_ram1[7] <= 16'h8888;
- mem_ram1[8] <= 16'h9999;
- mem_ram1[9] <= 16'haaaa;
- end
-
- always@(posedge clk or negedge res)
- if(!res)begin
- mem_ram2[0] <= 16'd0;
- mem_ram2[1] <= 16'd0;
- mem_ram2[2] <= 16'd0;
- mem_ram2[3] <= 16'd0;
- mem_ram2[4] <= 16'd0;
- mem_ram2[5] <= 16'd0;
- mem_ram2[6] <= 16'd0;
- mem_ram2[7] <= 16'd0;
- mem_ram2[8] <= 16'd0;
- mem_ram2[9] <= 16'd0;
- end
- else begin
- mem_ram2[0] <= 16'h1234;
- mem_ram2[1] <= 16'haaaa;
- mem_ram2[2] <= 16'h1234;
- mem_ram2[3] <= 16'haaaa;
- mem_ram2[4] <= 16'h1234;
- mem_ram2[5] <= 16'haaaa;
- mem_ram2[6] <= 16'h1234;
- mem_ram2[7] <= 16'haaaa;
- mem_ram2[8] <= 16'h1234;
- mem_ram2[9] <= 16'haaaa;
- end
- always@(*)begin
- ram1_data <= mem_ram1[ram1_raddr];
- ram2_data <= mem_ram2[ram2_raddr];
- end
-
- always@(posedge clk or negedge res)
- if(!res)begin
- cnt_ramchoice <= 'd0;
- end
- else if(cnt_ramchoice == 4000-1)
- cnt_ramchoice <= 'd0;
- else
- cnt_ramchoice <= cnt_ramchoice + 1;
- always@(posedge clk or negedge res)
- if(!res)begin
- ram_chioce <= 1'b1;
- ramend_flag <= 1'b0;
- end
- else if(cnt_ramchoice == 2000-1)begin
- ram_chioce <= 1'b0;
- ramend_flag <= 1'b1;
- end
- else if(cnt_ramchoice == 4000-1)begin
- ram_chioce <= 1'b1;
- ramend_flag <= 1'b1;
- end
- else begin
- ram_chioce <= ram_chioce;
- ramend_flag <= 1'b0;
- end
- always @(*)
- begin
- if(mcu_flag==1)
- begin
- #100; mcu_en = 0;
- #1200; mcu_en = 1;
- #5000; mcu_wr = 1 ;
- #100; mcu_en = 0;
- mcu_wr = 0;
- #20 we_rd = 1;
- #1050; mcu_en = 1;
- #30 we_rd = 0;
- end
-
- end
- always @(posedge mcu_clk or negedge mcu_en)
- if(mcu_en)
- data <= 16'd0;
- else
- data <= data + 1;
-
- always @(posedge clk)
- begin
- if(mcu_en==0)
- #50 mcu_clk=~mcu_clk;
- else
- mcu_clk=1;
- end
-
-
-
- assign mcu_data = (we_rd == 1'b0)? 16'bzzzz_zzzz:data;
-
- fpga_mcu u_fpga_mcu(
- . sys_clk ( clk ) ,
- . sys_res ( res ) ,
- . ram_chioce ( ram_chioce ) ,
- . ramend_flag ( ramend_flag ) ,
- . ram1_data ( ram1_data ) ,
- . ram2_data ( ram2_data ) ,
- . mcu_clk ( mcu_clk ) ,
- . mcu_en ( mcu_en ) ,
- . mcu_wr ( mcu_wr ) ,
- . ram1_raddr ( ram1_raddr ) ,
- . ram2_raddr ( ram2_raddr ) ,
- . mcurd_flag ( mcu_flag ) ,
- . mcu_data ( mcu_data )
- );
-
-
-
-
-
- endmodule

下面给出仿真结果:图1是整体的仿真时序图;图2是单片机读数据部分的仿真时序图;图3是单片机写数据部分的仿真时序图。
图1
图2
图3
其实关于FPGA与单片机之间的通信协议还有很多种,比如两者之间只通过16根数据线进行数据通信,两者在相同时刻、按照相同的波特率进行数据的发送与接收。这些都可以人为拟定,只要注意在编写代码的时候两者能够统一即可。本文只是介绍了一种我在做项目的过程中用到的一种通信协议,当然可能还存在许多不足的地方,还望读者在阅读的过程中能够批评指正。
初次创作,难免文章中存在错误,希望读者能够及时纠正并给予私信,望大家共同进步!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。