Xilinx-ZYNQ7000系列-学习笔记(27):AXI时序分析
一、AXI基本知识
此部分之前的博客写过,大家请参考Xilinx-ZYNQ7000系列-学习笔记(10):AXI总线
下面将AXI_LITE各信号所表示的意义拿来:
官方给出的AXI握手协议如下:
AXI4 所采用的是一种 READY,==VALID ==握手通信机制,简单来说主从双方进行数据通信前,有一个握手的过程。 传输源 产生 VLAID 信号来指明何时数据或控制信息有效。而 目地源产生 READY 信号来指明已经准备好接受数据或控制信息。传输发生在 VALID和 READY 信号同时为高的时候。
二、AXI读写监测时序图
2.1 AXI写
(1)得到axi_awready信号,表示写地址准备好了。 该信号表明从机已准备好接受地址和相关的控制信号。
(2)给axi_awaddr信号赋值,S_AXI_AWADDR为写地址,从PS端输出进来。
(3)得到axi_wready信号,表示写准备好了。 该信号表示从机可以接受写数据。
(4)寄存器写使能信号slv_reg_wren,用于指示什么时候向slv_reg中写数。
assign slv_reg_wren = axi_wready && S_AXI_WVALID && axi_awready && S_AXI_AWVALID;
当slv_reg_wren拉高,开始判断axi_awaddr地址内的值,进而确定向哪个寄存器中写入数据,写入的数据由S_AXI_WDATA输入进来,并在S_AXI_WSTRB写选通信号拉高时写入(该信号指示哪些字节通道保存有效数据, 写数据总线的每八位有一个写选通位 )。整体过程如下图:
(5)写应答有效信号axi_bvalid,该信号表示通道正在发出有效的写响应信号。
2.2 AXI读
(1)得到axi_arready信号,表示读地址准备好了。 该信号表明从机已准备好接受地址和相关的控制信号。 得到axi_araddr信号,表示读地址。
(2)得到axi_rvalid信号,此信号表示通道正在发送所需的读取数据信号。
(3)寄存器写使能信号slv_reg_rden,用于指示什么时候从slv_reg中读数。
assign slv_reg_rden = axi_arready & S_AXI_ARVALID & ~axi_rvalid;
在任意触发状态下,reg_data_out都在被赋值,至于为哪个slv_reg的值由地址axi_araddr地址来决定。当slv_reg_rden拉高,reg_data_out的值被赋给axi_rdata,即输出出去,此时PS端即可读取到寄存器的值。整体过程如下图:
2.3 结合上位机分析完成程序
module data_addr(
input wire sclk,
input wire [31:0] pl_addr, //写入fpga的地址
output wire [31:0] pl_data, //根据地址查找得到的数据
input wire [7:0] GPS_addr, //写入GPS的地址
output wire [7:0] GPS_data //查找得到的GPS数据
);
reg [31:0] pl_data_r;
reg [7:0] GPS_data_r;
always @(*) begin
case(GPS_addr)
8'd1: GPS_data_r <= 31'd11;
8'd2: GPS_data_r <= 31'd12;
8'd3: GPS_data_r <= 31'd13;
8'd4: GPS_data_r <= 31'd14;
8'd5: GPS_data_r <= 31'd15;
default: GPS_data_r <= 31'd00;
endcase
end
always @(*) begin
case(pl_addr)
31'd1: pl_data_r <= 31'd10000;
31'd2: pl_data_r <= 31'd20000;
31'd3: pl_data_r <= 31'd30000;
31'd4: pl_data_r <= 31'd40000;
31'd5: pl_data_r <= 31'd50000;
default: pl_data_r <= 31'd88888;
endcase
end
assign pl_data = pl_data_r;
assign GPS_data = GPS_data_r;
endmodule
首先从代码可以看出,向PL端的寄存器写指定的内容会输出指定的值。
int main()
{
int pl_data;
char gps_data;
while(1){
for(int i = 0;i<6;i++){
XUartPl_WriteReg(XPAR_YIHUI_MPU_V1_0_BASEADDR, PL_ADDR, i);
pl_data = XUartPl_ReadReg(XPAR_YIHUI_MPU_V1_0_BASEADDR, PL_DATA);
printf("The data of PL ADDR[%d] = %d\n\r",i,pl_data);
sleep(1);
}
for(int j = 0;j<6;j++){
XUartPl_WriteReg(XPAR_YIHUI_MPU_V1_0_BASEADDR, GPS_ADDR, j);
gps_data = XUartPl_ReadReg(XPAR_YIHUI_MPU_V1_0_BASEADDR, GPS_DATA);
printf("The data of GPS ADDR[%d] = %d\n\r",j,gps_data);
sleep(1);
}
}
return 0;
}
向(AXI基地址+0)地址写数,即为向slv0为写入寄存器,写入的内容给了pl_addr。
从(AXI基地址+4)地址读数,即从slv1寄存器读取数据,读取的内容由pl_data得到。
向(AXI基地址+8)地址写数,即为向slv2为写入寄存器,写入的内容给了GPS_addr。
从(AXI基地址+12)地址读数,即从slv3寄存器读取数据,读取的内容由GPS_data得到。
以上是组合逻辑状态下,若为时序逻辑时,读写AXI需要进行打一拍操作。具体做法是:
写操作:
S_AXI_AWADDR 、S_AXI_AWPROT、S_AXI_AWVALID、S_AXI_AWREADY、S_AXI_WDATA、S_AXI_WSTRB、S_AXI_WVALID、S_AXI_WREADY不进行操作。内部信号axi_awaddr、slv_reg_wren需要延后一拍(axi_awaddr_d1、slv_reg_wren_d1),通过条件:
if(slv_reg_wren_d1 == 1'b1 && axi_awaddr_d1 == xxx(地址偏移量))
可以使用相应寄存器写入后的值,这里用延一拍的数据正好是当前写入后的寄存器中的新数据,不至于错拍。
读操作:
S_AXI_ARADDR、S_AXI_ARPROT、S_AXI_ARVALID、S_AXI_ARREADY、S_AXI_RDATA、S_AXI_RRESP 、S_AXI_RVALID 、S_AXI_RREADY不进行操作。内部信号,axi_rvalid、axi_rresp、slv_reg_rden需要延后一拍(axi_rvalid_d1、axi_rresp_d1、slv_reg_rden_d1),通过条件:
if (slv_reg_rden_d1) // 刘大利修改
begin
axi_rdata <= reg_data_out; // register read data
end
可以等一个时钟周期再将reg_data_out的数值发出去,等这个时钟周期的目的是等需要被读取出去的新数据生成并成功赋给slv_reg,这样reg_data_out才会被更新,新数才能发送出去:
always@(slv_reg_rden,axi_araddr) // slv_reg_rden配合地址构成组合逻辑,生成fifo的读使能信号
begin
if(slv_reg_rden == 1'b1 && axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] == OFFSET_UART_RX_DOUT)
UART_rx_rd_en <= 1'b1;
else
UART_rx_rd_en <= 1'b0;
end
assign slv_reg81 = {20'h0, UART_rx_dout}; // FIFO输出连接到slv_reg81