很久很久没有更新过博客了,今天来扒一扒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 [:] reg_out,
.
.
.
assign reg_out = slv_reg0;
.
.
.
其次我们要实现输出三态,最开始的想法是在IP核内部使用原语OBUFT,其原语如下:
OBUFT U_OBUFT1(
.I(I),//输入
.O(O),//输出
.T(T) //三态控制
);
后来经某师兄提醒,使用原语可能不方便后期移植,通用性比较差,所以改成了如下形式:
generate
genvar i;
for(i=;i<C_S_AXI_DATA_WIDTH;i=i+)
assign reg_out[i] = slv_reg1[i] ? '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 = ,
// Width of S_AXI address bus
parameter integer C_S_AXI_ADDR_WIDTH = ,
// Number of Interrupts
parameter integer C_NUM_OF_INTR = ,
// Each bit corresponds to Sensitivity of interrupt : 0 - EDGE, 1 - LEVEL
parameter C_INTR_SENSITIVITY = '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 = 'hFFFFFFFF,
// Sensitivity of IRQ: 0 - EDGE, 1 - LEVEL
parameter integer C_IRQ_SENSITIVITY = ,
// 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 =
)
(
// Users to add ports here
input [:] sig_in,
// User ports ends
.
.
.
//-- Number of Slave Registers 5
reg [ : ] reg_global_intr_en; //全局中断使能
reg [C_NUM_OF_INTR- :] reg_intr_en; //具体到某一个中断的使能
reg [C_NUM_OF_INTR- :] reg_intr_sts; //中断状态查询
reg [C_NUM_OF_INTR- :] reg_intr_ack; //清除中断
reg [C_NUM_OF_INTR- :] reg_intr_pending;//中断等待
reg [C_NUM_OF_INTR- :] intr; //中断源
reg [C_NUM_OF_INTR- :] det_intr; //检测中断信号
wire intr_reg_rden;
wire intr_reg_wren;
reg [C_S_AXI_DATA_WIDTH-:] reg_data_out;
// reg [3:0] intr_counter;
genvar i;
integer j;
reg [C_NUM_OF_INTR- :] intr_all_tmp; //Xilinx自动生成的IP核intr_all存在多驱动的问题,所以添加了该信号
wire intr_all = |intr_all_tmp;
reg [C_NUM_OF_INTR- :] 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 [:] 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=; i<= C_NUM_OF_INTR-; i=i+)//对输入信号消抖,采样16个时钟周期,全一则认为触发了中断
begin : debounce
reg [:] intr_counter;
always @ ( posedge S_AXI_ACLK )
if ( S_AXI_ARESETN == 'b0 )
intr_counter[:] <= 'hF;
else if (sig_in_ff2[i] == 'b1) begin
if(intr_counter [:] != 'h0)
intr_counter[:] <= intr_counter[:] - ;
end
else
intr_counter[:] <= 'hF; always @ ( posedge S_AXI_ACLK )
if ( S_AXI_ARESETN == 'b0)
intr[i] <= 'b0;
else
begin
if (intr_counter[:] == )
intr[i] <= 'b1;
else
intr[i] <= 'b0;
end
end
endgenerate generate
for(i=; i<= C_NUM_OF_INTR-; i=i+)
begin : gen_intrall // detects interrupt in any intr input
always @ ( posedge S_AXI_ACLK)
begin
if ( S_AXI_ARESETN == 'b0 || intr_ack_all_ff == 1'b1)
begin
intr_all_tmp[i] <= '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] <= 'b1;
end
end
end // detects intr ack in any reg_intr_ack reg bits
always @ ( posedge S_AXI_ACLK)
begin
if ( S_AXI_ARESETN == 'b0 || intr_ack_all_ff==1'b1)
begin
intr_ack_all_tmp[i] <= '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] <= 'b1;
end
end
end end
endgenerate
.
.
.
// detect interrupts for user selected number of interrupts generate
for(i=; i<= C_NUM_OF_INTR-; i=i+)
begin : gen_intr_detection if (C_INTR_SENSITIVITY[i] == 'b1)
begin: gen_intr_level_detect if (C_INTR_ACTIVE_STATE[i] == 'b1)
begin: gen_intr_active_high_detect always @ ( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 | reg_intr_ack[i] == 1'b1)
begin
det_intr[i] <= 'b0;
end
else
begin
if (intr[i] == 'b1)
begin
det_intr[i] <= 'b1;
end
end
end end
else
begin: gen_intr_active_low_detect always @ ( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 | reg_intr_ack[i] == 1'b1)
begin
det_intr[i] <= 'b0;
end
else
begin
if (intr[i] == 'b0)
begin
det_intr[i] <= 'b1;
end
end
end end end
else
begin:gen_intr_edge_detect wire [C_NUM_OF_INTR- :] intr_edge;
reg [C_NUM_OF_INTR- :] intr_ff;
reg [C_NUM_OF_INTR- :] intr_ff2; if (C_INTR_ACTIVE_STATE[i] == )
begin: gen_intr_rising_edge_detect always @ ( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 || reg_intr_ack[i] == 1'b1)
begin
intr_ff[i] <= 'b0;
intr_ff2[i] <= '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 == 'b0 | reg_intr_ack[i] == 1'b1)
begin
det_intr[i] <= 'b0;
end
else if (intr_edge[i] == 'b1)
begin
det_intr[i] <= 'b1;
end
end end
else
begin: gen_intr_falling_edge_detect always @ ( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 | reg_intr_ack[i] == 1'b1)
begin
intr_ff[i] <= 'b1;
intr_ff2[i] <= '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 == 'b0 | reg_intr_ack[i] == 1'b1)
begin
det_intr[i] <= 'b0;
end
else if (intr_edge[i] == 'b1)
begin
det_intr[i] <= 'b1;
end
end end end // IRQ generation logic reg s_irq_lvl; if (C_IRQ_SENSITIVITY == )
begin: gen_irq_level if (C_IRQ_ACTIVE_STATE == )
begin: irq_level_high always @ ( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 || intr_ack_all == 1'b1)
begin
s_irq_lvl <= 'b0;
end
else if (intr_all == 'b1 && reg_global_intr_en[0] ==1'b1)
begin
s_irq_lvl <= '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 == 'b0 || intr_ack_all == 1'b1)
begin
s_irq_lvl <= 'b1;
end
else if (intr_all == 'b1 && reg_global_intr_en[0] ==1'b1)
begin
s_irq_lvl <= '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 == )
begin: irq_rising_edge always @ ( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 'b0 || intr_ack_all == 1'b1)
begin
s_irq_lvl <= 'b0;
s_irq_lvl_ff <= 'b0;
end
else if (intr_all == 'b1 && reg_global_intr_en[0] ==1'b1)
begin
s_irq_lvl <= '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 == 'b0 || intr_ack_all == 1'b1 )
begin
s_irq_lvl <= 'b1;
s_irq_lvl_ff <= 'b1;
end
else if (intr_all == 'b1 && reg_global_intr_en[0] ==1'b1)
begin
s_irq_lvl <= '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的主要代码如下:
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h" #include "xintc.h"
//#include "intc_header.h"
#include "AXI_USER_LOGIC_GPIO_OUT.H"
#include "AXI_USER_LOGIC_GPIO_IN.H" #include "xintc_test.h" int user_intr_flag = ; int main()
{
init_platform();
IntcInit(INTC_DEVICE_ID);//中断初始化
print("Hello World\n\r"); AXI_USER_LOGIC_GPIO_OUT_mWriteReg(XPAR_AXI_USER_LOGIC_GPIO_OUT_0_S00_AXI_BASEADDR, , 0xffff0000);//配置gpio_out低16位为输出 int i;
int intr_cnt=;
unsigned int intr_status; for (i=; i>-;i++)
{
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输出数据
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的状态
intr_status = AXI_USER_LOGIC_GPIO_IN_mReadReg(INTR_BaseAddr,REG_INTR_STS);//查询GPIO IN中断的状态
if(intr_status){
printf("intr:%x,cnt:%d,intr_flag:%d\n\r",intr_status,++intr_cnt,user_intr_flag);
if(user_intr_flag){
AXI_USER_LOGIC_GPIO_IN_mWriteReg(INTR_BaseAddr,REG_INTR_ACK,intr_status);//清除中断
user_intr_flag = ;
} }
int delay_cnt = ;//
while(delay_cnt--);
} cleanup_platform();
return ;
} void IntcInit(u16 DeviceId)
{ AXI_USER_LOGIC_GPIO_IN_mWriteReg(INTR_BaseAddr, REG_Global_INTR_EN, );
AXI_USER_LOGIC_GPIO_IN_mWriteReg(INTR_BaseAddr, REG_INTR_EN, 0xffffffff); XIntc_Initialize(&InterruptController, DeviceId); XIntc_Connect(&InterruptController, INTC_DEVICE_ID,
(XInterruptHandler)DeviceDriverHandler,
(void *));
XIntc_Enable(&InterruptController, INTC_DEVICE_ID); microblaze_register_handler(XIntc_DeviceInterruptHandler, INTC_DEVICE_ID);
microblaze_enable_interrupts();
XIntc_Start(&InterruptController, XIN_REAL_MODE); // XGpio_InterruptEnable(&InterruptController, 1);
// XGpio_InterruptGlobalEnable(&InterruptController); print("intr config done!\n\r"); } void DeviceDriverHandler(void *CallbackRef)
{
print("Entering interrup!\n\r");
user_intr_flag = ;
}
上板后串口接收到的数据: