EDK笔记——自定义IP核
这篇笔记是我之前在调试MicroBlaze时记录下来的,当时在网上查了一些资料,发现都讲的不是特别清楚,所以自己整理了一个笔记,如有差错,希望大家指正。
在这次示例中,本文完成了一个改变流水灯的间隔时间以及按键检测的间隔时间可变的一个MicroBlaze程序,修改参数后不用再经过布局布线,方便调试。
典型的嵌入式程序设计流程如下:
如上图所示,在FPGA中开发嵌入式系统主要需要三个工具套件,分别为ISE,XPS和SDK。
ISE篇
先打开ISE,新建一个Project
我用的是黑金的AX309的板子
然后建一个Embedded Processor
XPS篇
建完后会自动打开XPS软件,点击Yes创建一个新的XPS Project
点击OK,使用AXI总线
选择Create a System for a Custom Board, Reference Clock Frequecy是板上的时钟频率,也可以是你用PLL生成的时钟然后输到MIcroBlaze系统的时钟频率,Optimization Strategy选择Area和Throughput都可以,在这里我选的是Area。点击Next
处理器频率选择100Mhz,Local Memory Size选择32KB,用于缓存你写的应用程序,尽量选大一些。不用添加其它外设,点击Finish即可
完了之后会生成一个基本的MicroBlaze系统,里面包含了MIcroBlaze系统所需的基本模块,如clock generator,axi4liite等等。
在这里,我们需要自己新建一个IP,在建IP之前,我们要先想好这个IP的功能和端口是什么,包括输入和输出端口,我目前要实现的功能是,通过检测按键KEY1,来开启和关闭流水灯,同时可以通过修改MIcroBlaze中的程序来改变流水灯的间隔时间以及按键检测的间隔时间,所以在这个自定义IP中,端口有两个,一个是控制4个LED灯亮的输出端口LED[3:0],一个是按键的输入端口dip。
首先,点击Hardware>>Create or Import Peripheral
点击next
选择Create templates for a new perphera,点击next
在这里有两个选择,上面那个是将你创建的外设安装到EDK的库中,这样下次再创建工程就可以直接利用你创建的外设,可重复利用,另外一个选项就是只用于当前XPS Project,在这里我们这个外设只在这个工程中使用,所以选择To an XPS Project,点击next。
为你创建的这个外设取名字,我这里取的是blink,你可以在description中添加对这个外设功能的描述。点击next
选择外设总线相连的协议,在这里选的是AXI4_Lite,点击next。
在这里需要勾选User logic software registers,表示我们是通过软件来更改寄存器的值,从而来更改我们所需要改变的参数的值。点击next
在这里我们选择寄存器的个数,一般有几个参数就选择几个寄存器,也可以把几个参数合在一个寄存器里,每个寄存器是32位的,总共可以选择32个寄存器,在这里我们需要修改两个参数(流水灯亮的间隔时间,按键检测的间隔时间),所以这里选择两个寄存器。
在这里不作修改,点击next
这里是选择是否生成仿真平台和仿真模型(Simulation Model),用于在ISim或Modelsim中进行仿真,在这里我们不需要仿真,不勾选,点击next
因为我们是用Verilog进行编程,所以需要勾选Generate stub“user logic” template in Verilog instead of VHDL,之后我们再对user logic模块进行修改时就可以用Verilog了,第三个勾选的话可以生成相关驱动,这样便可以利用驱动中的API函数了,不过不勾选也没关系,因为我们需要的函数在MIcroBlaze核里都有,在这里我们不勾选,点击next
点击finish
这个时候可以看到,在IP Catalog中的USER有我们创建的外设BLINK
在这里我们需要修改几个文件,首先右键BLINK>>view MPD,在## Ports处添加外设的端口LED[3:0]和dip
一个是user logic这里需要添加端口
最后一个就是generic map这里需要添加映射
通过软件控制的寄存器便是这两个寄存器
接下来就是自己为实现功能而写的一些模块,以下是为了实现功能而编写的Verilog文件
//User Logic //reg0 流水灯时间间隔 //reg1 按键检测时间间隔,用于防抖 //reg1 读入的时候读的是dip的状态 reg [31:0] timer; reg timer_enable; always @ (posedge Bus2IP_Clk) begin if (Bus2IP_Resetn == 1'b0) begin timer <= 32'b0; end else if (timer == 4*slv_reg0) begin timer <= 32'b0; end else if (timer_enable) begin timer <= timer + 1'b1; end else begin timer <= 32'b0; end end //按键间隔时间计数 reg [31:0] counter; always @ (posedge Bus2IP_Clk) begin if (Bus2IP_Resetn == 1'b0) begin counter <=32'b0; end else if (counter == slv_reg1) begin counter <= 32'b0; end else begin counter <= counter + 1'b1; end end //检测按键的下降沿 reg dip_temp1; reg dip_temp2; wire dip_neg; always @ (posedge Bus2IP_Clk) begin if (Bus2IP_Resetn == 1'b0) begin dip_temp1 <= 1'b1; end else if (counter == slv_reg1) begin dip_temp1 <= dip; end else begin dip_temp1 <= dip_temp1; end end //锁定检测dip的值 always @ (posedge Bus2IP_Clk) begin if (Bus2IP_Resetn == 1'b0) begin dip_temp2 <= 1'b1; end else begin dip_temp2 <= dip_temp1; end end assign dip_neg = dip_temp2 & (~dip_temp1); //流水灯的主要状态机 reg [3:0] current_state; reg [3:0] next_state; parameter State_idle = 4'd0; parameter First_LED = 4'd1; parameter Second_LED = 4'd2; parameter Third_LED = 4'd3; parameter Last_LED = 4'd4; always @ (posedge Bus2IP_Clk) begin if (Bus2IP_Resetn == 1'b0) begin current_state <= State_idle; end else begin current_state <= next_state; end end always @ (current_state,dip_neg,timer) begin next_state = 4'dx; case (current_state) State_idle: begin if (dip_neg) next_state = First_LED; else next_state = State_idle; end First_LED: begin if (dip_neg) next_state = State_idle; else if (timer == slv_reg0 - 1) next_state = Second_LED; else next_state = First_LED; end Second_LED: begin if (dip_neg) next_state = State_idle; else if (timer == 2*slv_reg0 - 1) next_state = Third_LED; else next_state = Second_LED; end Third_LED: begin if (dip_neg) next_state = State_idle; else if (timer == 3*slv_reg0 - 1) next_state = Last_LED; else next_state = Third_LED; end Last_LED: begin if (dip_neg) next_state = State_idle; else if (timer == 4*slv_reg0 - 1) next_state = First_LED; else next_state = Last_LED; end default: next_state = State_idle; endcase end always @ (posedge Bus2IP_Clk) begin if (Bus2IP_Resetn == 1'b0) begin timer_enable <= 1'b0; LED <= 4'b0000; end else begin case (next_state) State_idle: begin timer_enable <= 1'b0; LED <= 4'b0000; end First_LED: begin timer_enable <= 1'b1; LED <= 4'b0001; end Second_LED: begin timer_enable <= 1'b1; LED <= 4'b0010; end Third_LED: begin timer_enable <= 1'b1; LED <= 4'b0100; end Last_LED: begin timer_enable <= 1'b1; LED <= 4'b1000; end default: begin timer_enable <= 1'b0; LED <= 4'b0000; end endcase end end
然后再添加自己创建的外设BLINK,添加进来后,点击PORT栏,将LED和dip设为External Port,如下图所示
完成上述修改后,先在XPS中点击Hardware>>Generate Netlist,然后返回到ISE中,新建一个V文件,用作顶层文件,来例化这个MIcroBlaze核
然后,编写UCF文件,将端口信号与管脚一一对应起来
点击Finish,在生成的helloworld.c文件中进行程序编写
在这里,我先额外建了一个文件夹blink_led,放置写寄存器的操作
/* * blink_led.c * * Created on: 2018-5-23 */ #include "blink_led.h" #include "xio.h" //流水灯的间隔时间 void set_LED_interval(uint32_t bassaddr_p, uint32_t num_ms, uint32_t num_us) { //CLK 100MHZ XIo_Out32((bassaddr_p) + 0x00000000, num_ms*100000 + num_us*100); } //按键检测的间隔时间 void set_dip_interval(uint32_t bassaddr_p, uint32_t num_ms) { //CLK 100MHZ XIo_Out32((bassaddr_p + 0x00000004), num_ms*100000); }
其中寄存器的读写是靠xio.h中包含的API函数来进行操作的,所以要添加include“xio.h”,另外,此程序需要在helloword.c引用,所以也需要编写一个头文件,在helloword.c中进行程序的编写时需要包含进去
/* * blink_led.h * * Created on: 2018-5-23 */ #ifndef BLINK_LED_H_ #define BLINK_LED_H_ #include <stdint.h> void set_LED_interval(uint32_t bassaddr_p, uint32_t num_ms, uint32_t num_us); void set_dip_interval(uint32_t bassaddr_p, uint32_t num_ms); #endif /* BLINK_LED_H_ */
然后再在helloword.c中进行程序的编写
*/ #include <stdio.h> #include "platform.h" #include "blink_led/blink_led.h" #include "xil_printf.h" #define blink_LED_ADDR 0x7C600000 int main() { init_platform(); set_LED_interval(blink_LED_ADDR, 5000, 0); print("set_LED_interval Completed\n"); set_dip_interval(blink_LED_ADDR, 10); print("set_dip_interval Completed\n"); return 0; }
在Debug之前,先设置Debug Configuration,勾上Connect STDIO to Console,这样,打印出来的信息就会显示在Console栏中。
至此整个自定义IP过程完成,接下来就是上电,将程序烧写进去进行调试就可以了。