很久很久没有更新过博客了,今天来扒一扒FPGA上CPU软核的使用。
主要完成的功能:使用的开发板是nexys 4 DDR,板上有16个switch以及16个LED,需要完成microblaze对led的控制以及将switch作为外部中断源。
一、自定义GPIO IP核
还是在Tools里面选择Create and Package IP,新建AXI4外设,本次需要新建两个GPIO外设,一个作为GPIO_IN,一个作为GPIO_OUT。
GPIO_OUT就是简单的将CPU下发的数据输出到PIN脚,输出需要有高阻态并且三态可通过CPU配置;GPIO_IN需要有中断功能,并且其触发方式(上升沿/下降沿触发,高/低电平触发)需要可配置。
1.GPIO_OUT:
首先将CPU下发的数据输出,即
output [31:0] reg_out, . . . assign reg_out = slv_reg0; . . .
其次我们要实现输出三态,最开始的想法是在IP核内部使用原语OBUFT,其原语如下:
OBUFT U_OBUFT1(
.I(I),//输入
.O(O),//输出
.T(T) //三态控制
);
后来经某师兄提醒,使用原语可能不方便后期移植,通用性比较差,所以改成了如下形式:
generate genvar i; for(i=0;i<C_S_AXI_DATA_WIDTH;i=i+1) assign reg_out[i] = slv_reg1[i] ? 1'bz : slv_reg0[i]; endgenerate
生成的电路如图所示:
打包生成IP核,例化的用户接口如图所示:
2.GPIO_IN:
在使用IP生成向导的时候注意勾选使能中断,自动生成的模块结构如图所示:
gpio_in_..._AXI没什么好说的,直接将PIN脚信号传递给CPU就可以了。gpio_in_..._AXI_INTR主要实现了中断逻辑,部分代码如下:
module axi_user_logic_gpio_in_v1_0_S_AXI_INTR # ( // Users to add parameters here // User parameters ends // Do not modify the parameters beyond this line // Width of S_AXI data bus parameter integer C_S_AXI_DATA_WIDTH = 32, // Width of S_AXI address bus parameter integer C_S_AXI_ADDR_WIDTH = 5, // Number of Interrupts parameter integer C_NUM_OF_INTR = 1, // Each bit corresponds to Sensitivity of interrupt : 0 - EDGE, 1 - LEVEL parameter C_INTR_SENSITIVITY = 32'hFFFFFFFF, // Each bit corresponds to Sub-type of INTR: [0 - FALLING_EDGE, 1 - RISING_EDGE : if C_INTR_SENSITIVITY is EDGE(0)] and [ 0 - LEVEL_LOW, 1 - LEVEL_LOW : if C_INTR_SENSITIVITY is LEVEL(1) ] parameter C_INTR_ACTIVE_STATE = 32'hFFFFFFFF, // Sensitivity of IRQ: 0 - EDGE, 1 - LEVEL parameter integer C_IRQ_SENSITIVITY = 1, // Sub-type of IRQ: [0 - FALLING_EDGE, 1 - RISING_EDGE : if C_IRQ_SENSITIVITY is EDGE(0)] and [ 0 - LEVEL_LOW, 1 - LEVEL_LOW : if C_IRQ_SENSITIVITY is LEVEL(1) ] parameter integer C_IRQ_ACTIVE_STATE = 1 ) ( // Users to add ports here input [31:0] sig_in, // User ports ends . . . //-- Number of Slave Registers 5 reg [0 : 0] reg_global_intr_en; //全局中断使能 reg [C_NUM_OF_INTR-1 :0] reg_intr_en; //具体到某一个中断的使能 reg [C_NUM_OF_INTR-1 :0] reg_intr_sts; //中断状态查询 reg [C_NUM_OF_INTR-1 :0] reg_intr_ack; //清除中断 reg [C_NUM_OF_INTR-1 :0] reg_intr_pending;//中断等待 reg [C_NUM_OF_INTR-1 :0] intr; //中断源 reg [C_NUM_OF_INTR-1 :0] det_intr; //检测中断信号 wire intr_reg_rden; wire intr_reg_wren; reg [C_S_AXI_DATA_WIDTH-1:0] reg_data_out; // reg [3:0] intr_counter; genvar i; integer j; reg [C_NUM_OF_INTR-1 :0] intr_all_tmp; //Xilinx自动生成的IP核intr_all存在多驱动的问题,所以添加了该信号 wire intr_all = |intr_all_tmp; reg [C_NUM_OF_INTR-1 :0] intr_ack_all_tmp; //同样是因为intr_ack_all多驱动 wire intr_ack_all = |intr_ack_all_tmp; wire s_irq; reg intr_all_ff; reg intr_ack_all_ff; . . . reg [31:0] sig_in_ff1,sig_in_ff2; //对输入信号打拍以消除亚稳态 always @(posedge S_AXI_ACLK) begin sig_in_ff1 <= sig_in; sig_in_ff2 <= sig_in_ff1; end generate for(i=0; i<= C_NUM_OF_INTR-1; i=i+1)//对输入信号消抖,采样16个时钟周期,全一则认为触发了中断 begin : debounce reg [3:0] intr_counter; always @ ( posedge S_AXI_ACLK ) if ( S_AXI_ARESETN == 1'b0 ) intr_counter[3:0] <= 4'hF; else if (sig_in_ff2[i] == 1'b1) begin if(intr_counter [3:0] != 4'h0) intr_counter[3:0] <= intr_counter[3:0] - 1; end else intr_counter[3:0] <= 4'hF; always @ ( posedge S_AXI_ACLK ) if ( S_AXI_ARESETN == 1'b0) intr[i] <= 1'b0; else begin if (intr_counter[3:0] == 0) intr[i] <= 1'b1; else intr[i] <= 1'b0; end end endgenerate generate for(i=0; i<= C_NUM_OF_INTR-1; i=i+1) begin : gen_intrall // detects interrupt in any intr input always @ ( posedge S_AXI_ACLK) begin if ( S_AXI_ARESETN == 1'b0 || intr_ack_all_ff == 1'b1) begin intr_all_tmp[i] <= 1'b0; end else begin // for(j=0; j<= C_NUM_OF_INTR-1; j=j+1) if (reg_intr_pending[i]) begin intr_all_tmp[i] <= 1'b1; end end end // detects intr ack in any reg_intr_ack reg bits always @ ( posedge S_AXI_ACLK) begin if ( S_AXI_ARESETN == 1'b0 || intr_ack_all_ff==1'b1) begin intr_ack_all_tmp[i] <= 1'b0; end else begin // for(j=0; j<= C_NUM_OF_INTR-1; j=j+1) if (reg_intr_ack[i]) begin intr_ack_all_tmp[i] <= 1'b1; end end end end endgenerate . . . // detect interrupts for user selected number of interrupts generate for(i=0; i<= C_NUM_OF_INTR-1; i=i+1) begin : gen_intr_detection if (C_INTR_SENSITIVITY[i] == 1'b1) begin: gen_intr_level_detect if (C_INTR_ACTIVE_STATE[i] == 1'b1) begin: gen_intr_active_high_detect always @ ( posedge S_AXI_ACLK ) begin if ( S_AXI_ARESETN == 1'b0 | reg_intr_ack[i] == 1'b1) begin det_intr[i] <= 1'b0; end else begin if (intr[i] == 1'b1) begin det_intr[i] <= 1'b1; end end end end else begin: gen_intr_active_low_detect always @ ( posedge S_AXI_ACLK ) begin if ( S_AXI_ARESETN == 1'b0 | reg_intr_ack[i] == 1'b1) begin det_intr[i] <= 1'b0; end else begin if (intr[i] == 1'b0) begin det_intr[i] <= 1'b1; end end end end end else begin:gen_intr_edge_detect wire [C_NUM_OF_INTR-1 :0] intr_edge; reg [C_NUM_OF_INTR-1 :0] intr_ff; reg [C_NUM_OF_INTR-1 :0] intr_ff2; if (C_INTR_ACTIVE_STATE[i] == 1) begin: gen_intr_rising_edge_detect always @ ( posedge S_AXI_ACLK ) begin if ( S_AXI_ARESETN == 1'b0 || reg_intr_ack[i] == 1'b1) begin intr_ff[i] <= 1'b0; intr_ff2[i] <= 1'b0; end else begin intr_ff[i] <= intr[i]; intr_ff2[i] <= intr_ff[i]; end end assign intr_edge[i] = intr_ff[i] && (!intr_ff2); always @ ( posedge S_AXI_ACLK ) begin if ( S_AXI_ARESETN == 1'b0 | reg_intr_ack[i] == 1'b1) begin det_intr[i] <= 1'b0; end else if (intr_edge[i] == 1'b1) begin det_intr[i] <= 1'b1; end end end else begin: gen_intr_falling_edge_detect always @ ( posedge S_AXI_ACLK ) begin if ( S_AXI_ARESETN == 1'b0 | reg_intr_ack[i] == 1'b1) begin intr_ff[i] <= 1'b1; intr_ff2[i] <= 1'b1; end else begin intr_ff[i] <= intr[i]; intr_ff2[i] <= intr_ff[i]; end end assign intr_edge[i] = intr_ff2[i] && (!intr_ff); always @ ( posedge S_AXI_ACLK ) begin if ( S_AXI_ARESETN == 1'b0 | reg_intr_ack[i] == 1'b1) begin det_intr[i] <= 1'b0; end else if (intr_edge[i] == 1'b1) begin det_intr[i] <= 1'b1; end end end end // IRQ generation logic reg s_irq_lvl; if (C_IRQ_SENSITIVITY == 1) begin: gen_irq_level if (C_IRQ_ACTIVE_STATE == 1) begin: irq_level_high always @ ( posedge S_AXI_ACLK ) begin if ( S_AXI_ARESETN == 1'b0 || intr_ack_all == 1'b1) begin s_irq_lvl <= 1'b0; end else if (intr_all == 1'b1 && reg_global_intr_en[0] ==1'b1) begin s_irq_lvl <= 1'b1; end end assign s_irq = s_irq_lvl; end else begin:irq_level_low always @ ( posedge S_AXI_ACLK ) begin if ( S_AXI_ARESETN == 1'b0 || intr_ack_all == 1'b1) begin s_irq_lvl <= 1'b1; end else if (intr_all == 1'b1 && reg_global_intr_en[0] ==1'b1) begin s_irq_lvl <= 1'b0; end end assign s_irq = s_irq_lvl; end end else begin: gen_irq_edge reg s_irq_lvl_ff; if (C_IRQ_ACTIVE_STATE == 1) begin: irq_rising_edge always @ ( posedge S_AXI_ACLK ) begin if ( S_AXI_ARESETN == 1'b0 || intr_ack_all == 1'b1) begin s_irq_lvl <= 1'b0; s_irq_lvl_ff <= 1'b0; end else if (intr_all == 1'b1 && reg_global_intr_en[0] ==1'b1) begin s_irq_lvl <= 1'b1; s_irq_lvl_ff <= s_irq_lvl; end end assign s_irq = s_irq_lvl && (!s_irq_lvl_ff); end else begin:irq_falling_edge always @ ( posedge S_AXI_ACLK ) begin if ( S_AXI_ARESETN == 1'b0 || intr_ack_all == 1'b1 ) begin s_irq_lvl <= 1'b1; s_irq_lvl_ff <= 1'b1; end else if (intr_all == 1'b1 && reg_global_intr_en[0] ==1'b1) begin s_irq_lvl <= 1'b0; s_irq_lvl_ff <= s_irq_lvl; end end assign s_irq = !(s_irq_lvl_ff && (!s_irq_lvl)); end end assign irq = s_irq; end endgenerate . . .
打包生成IP核,其用户接口如下:
至此,IP核的生成就做完了。
二、创建BD块
首先将生成的IP核添加到IP Catalog里:
添加IP模块
对模块进行例化配置:
本次开启了16个GPIO中断,中断的检测方式是高电平,触发方式也是高电平
开启了32个GPIO输出,均初始化为高阻态
先run block automation,勾选CPU中断控制器
对时钟模块进行配置,注意这里默认的是差分时钟,根据需要,我选择了单端时钟
在run connection automation的时候,注意复位信号。两个复位信号一个默认是高有效,一个低有效,如果你把这两个连到一个外部复位,需要使其复位电平保持一致。
最后,CPU的测试少不了串口,当然,如果你只做仿真的话,就不用添加串口了,如果要上板,最好是把串口也放进来,下面是总图:
分配一下地址,由于我在SDK里面建立了hello world工程,对CPU存储的要求略高,所以将两个mem都改成了256k。如果你建立的是空的工程并且不开启串口的话,估计使用默认的8KB存储空间也可以。
保存BD块,validate一下,没有错误的话就generate output product并且创建wrapper,然后可以直接导出到SDK,并且打开SDK进行CPU开发,然后将生成的ELF文件关联到vivado里,到此,就可以使用CPU核FPGA联合仿真了。
在BD块上右键关联elf文件,成功后会在design source里看到ELF文件。仿真如下:
如果你要上板的话,需要在vivado中生成bit流,并将其导入到SDK里,使用SDK进行程序的烧录。
SDK的主要代码如下:
1 #include <stdio.h> 2 #include "platform.h" 3 #include "xil_printf.h" 4 5 #include "xintc.h" 6 //#include "intc_header.h" 7 #include "AXI_USER_LOGIC_GPIO_OUT.H" 8 #include "AXI_USER_LOGIC_GPIO_IN.H" 9 10 #include "xintc_test.h" 11 12 int user_intr_flag = 0; 13 14 int main() 15 { 16 init_platform(); 17 IntcInit(INTC_DEVICE_ID);//中断初始化 18 print("Hello World\n\r"); 19 20 21 AXI_USER_LOGIC_GPIO_OUT_mWriteReg(XPAR_AXI_USER_LOGIC_GPIO_OUT_0_S00_AXI_BASEADDR, 4, 0xffff0000);//配置gpio_out低16位为输出 22 23 int i; 24 int intr_cnt=0; 25 unsigned int intr_status; 26 27 for (i=0; i>-1;i++) 28 { 29 AXI_USER_LOGIC_GPIO_OUT_mWriteReg(XPAR_AXI_USER_LOGIC_GPIO_OUT_0_S00_AXI_BASEADDR, AXI_USER_LOGIC_GPIO_OUT_S00_AXI_SLV_REG0_OFFSET, i);//循环向GPIO_OUT输出数据 30 printf("reg_out:%x\n\r",AXI_USER_LOGIC_GPIO_OUT_mReadReg(XPAR_AXI_USER_LOGIC_GPIO_OUT_0_S00_AXI_BASEADDR, AXI_USER_LOGIC_GPIO_OUT_S00_AXI_SLV_REG0_OFFSET));//反向读出GPIO PIN的状态 31 intr_status = AXI_USER_LOGIC_GPIO_IN_mReadReg(INTR_BaseAddr,REG_INTR_STS);//查询GPIO IN中断的状态 32 if(intr_status){ 33 printf("intr:%x,cnt:%d,intr_flag:%d\n\r",intr_status,++intr_cnt,user_intr_flag); 34 if(user_intr_flag){ 35 AXI_USER_LOGIC_GPIO_IN_mWriteReg(INTR_BaseAddr,REG_INTR_ACK,intr_status);//清除中断 36 user_intr_flag = 0; 37 } 38 39 } 40 int delay_cnt = 10000000;//1000000 41 while(delay_cnt--); 42 } 43 44 cleanup_platform(); 45 return 0; 46 } 47 48 void IntcInit(u16 DeviceId) 49 { 50 51 AXI_USER_LOGIC_GPIO_IN_mWriteReg(INTR_BaseAddr, REG_Global_INTR_EN, 1); 52 AXI_USER_LOGIC_GPIO_IN_mWriteReg(INTR_BaseAddr, REG_INTR_EN, 0xffffffff); 53 54 XIntc_Initialize(&InterruptController, DeviceId); 55 56 XIntc_Connect(&InterruptController, INTC_DEVICE_ID, 57 (XInterruptHandler)DeviceDriverHandler, 58 (void *)0); 59 XIntc_Enable(&InterruptController, INTC_DEVICE_ID); 60 61 microblaze_register_handler(XIntc_DeviceInterruptHandler, INTC_DEVICE_ID); 62 microblaze_enable_interrupts(); 63 XIntc_Start(&InterruptController, XIN_REAL_MODE); 64 65 // XGpio_InterruptEnable(&InterruptController, 1); 66 // XGpio_InterruptGlobalEnable(&InterruptController); 67 68 print("intr config done!\n\r"); 69 70 } 71 72 73 74 void DeviceDriverHandler(void *CallbackRef) 75 { 76 print("Entering interrup!\n\r"); 77 user_intr_flag = 1; 78 }
上板后串口接收到的数据: