AXI协议(二)-AXI-Lite主机解析及仿真

19 篇文章 34 订阅
18 篇文章 4 订阅

AXI协议(二)-AXI-Lite主端解析及仿真


在这一篇中,你将 可能学会:

  1. AXI-Lite主端的示例代码理解
  2. 如何自定义自己的AXI-Lite外设/接口(部分)
  3. 如何仿真验证AXI总线(小试牛刀罢了)

生成Master Axi-Lite示例IP

这个跟之前是一致的,唯一不同是接口模式选为主端

image-20210420162044166

Master IP代码解析

打开*_master_v1_0_M00_AXI.v,可以看见有三部分:

  1. Master模式下的AXI协议握手信号控制
  2. User Logic下有一个状态机
  3. User Logic下受状态机控制的其他信号

所以很显然,由于我们已经对AXI协议已经有一定的了解,只要从状态机出发,再去掌握他怎么控制一些关键信号的就完事了🌹

状态机控制解析

对应代码L493,首先用插件画出他的状态转移图:

image-20210420162852942

那差不多也就是初始化,然后循环写读了,在这状态机中,他还同时控制了以下信号:

写过程的:

  • start_single_write

  • write_issued

读过程的:

  • start_single_read

  • read_issued

检验:

  • compare_done

  • ERROR

下面我们一个个状态来讲

初始状态init_txn_pulse

从IDLE状态开始,先有初始化信号init_txn_pulse:

assign init_txn_pulse	= (!init_txn_ff2) && init_txn_ff;
//Generate a pulse to initiate AXI transaction.
always @(posedge M_AXI_ACLK)
	begin
	// Initiates AXI transaction delay    
	if (M_AXI_ARESETN == 0 )
		begin
		init_txn_ff <= 1'b0;
		init_txn_ff2 <= 1'b0;
		end
	else
		begin
		init_txn_ff <= INIT_AXI_TXN;
		init_txn_ff2 <= init_txn_ff;
		end                                                            
	end     

很简单的一个下降沿检测电路,也就是说INIT_AXI_TXN有一个下降沿的时候发起一次写读。

写状态writes_done

这里我们先不管底层是怎么实现的,就看看这些标志位是怎么改变的,先看writes_done:

always @(posedge M_AXI_ACLK)
begin
if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1)
	writes_done <= 1'b0;
	//The writes_done should be associated with a bready response
else if (last_write && M_AXI_BVALID && axi_bready)
	writes_done <= 1'b1;
else
	writes_done <= writes_done;
end

​ 可以看到,控制writes_done的信号线有AXI写响应通道和last_write,那再看看last_write又是什么:

parameter integer C_M_TRANSACTIONS_NUM	= 4

always @(posedge M_AXI_ACLK)
begin
if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1)
	last_write <= 1'b0;
// The last write should be associated with 
// a write address ready response       
else if ((write_index == C_M_TRANSACTIONS_NUM) && M_AXI_AWREADY)
	last_write <= 1'b1;
else
	last_write <= last_write;
end

​ 也就是说,last_write就是记录现在是不是在传最后一个字节了,writes_done还需要等到写响应通道有结果才会断言。注意这里的write_index,还没讲到

​ 此时回到状态机中,看关于写过程的两个关键信号start_single_write和write_issued。

INIT_WRITE:

······

if (~axi_awvalid && ~axi_wvalid &&	// AW和W通道ok
    ~M_AXI_BVALID && ~last_write &&	// 没到最后一个字节
    ~start_single_write && ~write_issued)
begin                                                           
	start_single_write <= 1'b1;                                   
	write_issued  <= 1'b1;                                        
end                                                             
else if (axi_bready)                                              
begin                                                           
	write_issued  <= 1'b0;                                        
end                                                             
else                                                              
begin                                                           
	start_single_write <= 1'b0; //Negate to generate a pulse      
end

可以看到start_single_write就像是一个脉冲信号(pulse),write_issued就像是一个使能位(en),都比较简单,具体怎么控制可以快进到下面。

读状态reads_done

这状态其实跟写状态思路上高度相似,先看reads_done:

always @(posedge M_AXI_ACLK)
begin
if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1)
	reads_done <= 1'b0;
//The reads_done should be associated with a read ready response
else if (last_read && M_AXI_RVALID && axi_rready)
	reads_done <= 1'b1;
else
	reads_done <= reads_done;
end

基本跟writes_done一个东西,再看last_read:

parameter integer C_M_TRANSACTIONS_NUM	= 4

always @(posedge M_AXI_ACLK)
begin
if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1)
	last_read <= 1'b0;
//The last read should be associated with a read address ready response
else if ((read_index == C_M_TRANSACTIONS_NUM) && (M_AXI_ARREADY) )
	last_read <= 1'b1;
else
	last_read <= last_read;
end

这个也是一致的,所以大致可以猜到,在状态机控制中:

  • start_single_read
  • read_issued

这两个的控制也是一致的:

if (~axi_arvalid && ~M_AXI_RVALID &&
    ~last_read && 
    ~start_single_read && ~read_issued)
	begin
		start_single_read <= 1'b1;
		read_issued  <= 1'b1;
	end
else if (axi_rready)
	begin
	read_issued  <= 1'b0;
	end
else
	begin
	start_single_read <= 1'b0; //Negate to generate a pulse
	end

​ 这里应该有细心的朋友留意到了,由于AXI中读通道和读响应是合并了的,所以这里的判断条件稍微改了一下。

比较状态compare_done

这里是直达IDLE的,这里直接看状态机:

INIT_COMPARE:
	begin
// This state is responsible to issue the state of comparison
// of written data with the read data. If no error flags are set,
// compare_done signal will be asseted to indicate success.
		ERROR <= error_reg;
		mst_exec_state <= IDLE;
		compare_done <= 1'b1;
	end

这里先看compare_done是干啥的:

// Asserts when AXI transactions is complete
		output wire  TXN_DONE,
······
assign TXN_DONE	= compare_done;

简单,再看error_reg:

// Register and hold any data mismatches, or read/write interface errors
always @(posedge M_AXI_ACLK)
begin
if (M_AXI_ARESETN == 0  || init_txn_pulse == 1'b1)
	error_reg <= 1'b0;
//Capture any error types
else if (read_mismatch || write_resp_error || read_resp_error)
	error_reg <= 1'b1;
else
	error_reg <= error_reg;
end

可以看到,有三种检测的错误类型:

  1. read_mismatch 即读回来的数和写出去的数不一致:
//Data Comparison                                                                   
always @(posedge M_AXI_ACLK)
begin
	if (M_AXI_ARESETN == 0  || init_txn_pulse == 1'b1)
		read_mismatch <= 1'b0;
	// The read data when available (on axi_rready) 
	// is compared with the expected data
	else if ((M_AXI_RVALID && axi_rready) && 
			 (M_AXI_RDATA != expected_rdata))
		read_mismatch <= 1'b1;
	else
		read_mismatch <= read_mismatch;
end

其中,expected_rdata是在读端单独做判断:

always @(posedge M_AXI_ACLK)                                  
	begin                                                     
	if (M_AXI_ARESETN == 0  || init_txn_pulse == 1'b1)
		begin                                                 
		expected_rdata <= C_M_START_DATA_VALUE;
		end
		// Signals a new write address/ write data is
		// available by user logic
	else if (M_AXI_RVALID && axi_rready)
		begin
		expected_rdata <= C_M_START_DATA_VALUE + read_index;
		end
	end

​ 这里可以看到,当时发送端的数据也是采用C_M_START_DATA_VALUE + index的,不过这个后面再讲。

  1. write_resp_error写响应错误
//Flag write errors
assign write_resp_error = (axi_bready & M_AXI_BVALID & M_AXI_BRESP[1]);

为什么这个是错误呢?回看上一篇中:

image-20210409232144661

可见,在M_AXI_BRESP中,高位为1就是错误。

  1. read_resp_error读响应错误
//Flag write errors
assign read_resp_error = (axi_rready & M_AXI_RVALID & M_AXI_RRESP[1]);  

同上,一模一样。

状态机小结

回看整个状态机的,我们可以看到,User logic主要交互的信号有:

  • INIT_AXI_TXN 发起写读过程
  • TXN_DONE 传输完成标志
  • ERROR 错误信息

而与AXI交互的重要信号有:

  • start_single_write(发起一次写的pulse)
  • start_single_read(发起一次读的pulse)

接下来我们就根绝这些信号,再分析一次AXI-Lite信号

AXI-Lite Master协议解析

同样有5个通道:地址写(AW),写(W),写响应(WRB),地址读(AR),读®,其实在上一篇中已经讲过一次了,这里希望启发到大家的是,真正要用的时候,要怎么控制。

由于读写过程中,过程高度相似,这里只介绍地址写(AW),写(W),写响应(WRB)这三个通道

这里放出上篇中的端口表,这里需要留意,端口方向都是反的!!,因为上篇中讲的是从机

image-20210410033227435

还需要注意的是,我们这里的是主机,握手时主要控制的是valid信号

写地址通道

注意,文章的代码顺序和它本身顺序是不一致的,这里主要按照上面的分通道来写,会比较简洁明了一些。

assign M_AXI_AWPROT	= 3'b000;

always @(posedge M_AXI_ACLK)
begin
//Only VALID signals must be deasserted during reset per AXI spec
//Consider inverting then registering active-low reset for higher fmax
if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1)
	begin
	axi_awvalid <= 1'b0;
	end
	//Signal a new address/data command is available by user logic
else
	begin
	if (start_single_write)
		begin
		axi_awvalid <= 1'b1;
		end
	//Address accepted by interconnect/slave 
	else if (M_AXI_AWREADY && axi_awvalid)
		begin
		axi_awvalid <= 1'b0;
		end
	end
end         

//Write Addresses                                        
always @(posedge M_AXI_ACLK)                                  
	begin                                                     
	if (M_AXI_ARESETN == 0  || init_txn_pulse == 1'b1)                                
		begin                                                 
		axi_awaddr <= 0;                                    
		end                                                   
		// Signals a new write address/ write data is         
		// available by user logic                            
	else if (M_AXI_AWREADY && axi_awvalid)                  
		begin                                                 
		axi_awaddr <= axi_awaddr + 32'h00000004;            
															
		end                                                   
	end

​ 这里可以看到,主要就是start_single_write来的时候,拉起valid,在收到awready的时候握手完成拉低。

​ 这里axi_awaddr也是指明在写入时时顺序写的,这里也是,在AW握手成功后才会将新地址加上,此时axi_awvalid已经被拉低了。地址为什么是加4,可以看看上一篇教程。

​ 从代码中也可以看到,xilinx官方所要求的 valid信号不可以根据ready来拉高,这一个原则在后面也是有的

写通道

这里跟上面是一致的,主要是数据的准备那一块还有一点逻辑没讲

always @(posedge M_AXI_ACLK)                                        
begin
	if (M_AXI_ARESETN == 0  || init_txn_pulse == 1'b1)
	begin
		axi_wvalid <= 1'b0;
	end
//Signal a new address/data command is available by user logic
	else if (start_single_write)
	begin
		axi_wvalid <= 1'b1;
	end
//Data accepted by interconnect/slave
	else if (M_AXI_WREADY && axi_wvalid)
	begin
	axi_wvalid <= 1'b0;
	end
end

always @(posedge M_AXI_ACLK)
begin
if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1)
	begin
	write_index <= 0;
	end
	// Signals a new write address/ write data is
	// available by user logic
else if (start_single_write)
	begin
	write_index <= write_index + 1;
	end
end

// Write data generation                                      
always @(posedge M_AXI_ACLK)                                  
	begin                                                     
	if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1 )
		begin                                                 
		axi_wdata <= C_M_START_DATA_VALUE;                  
		end                                                   
	// Signals a new write address/ write data is           
	// available by user logic                              
	else if (M_AXI_WREADY && axi_wvalid)                    
		begin                                                 
		axi_wdata <= C_M_START_DATA_VALUE + write_index;    
		end                                                   
	end

​ 这里也是回应到上面所说的,在这个例子中,数据生成用的是C_M_START_DATA_VALUE+ index的方法,write_index的生成方法也是看start_single_write。相应地,在读中,就会有read_index,用的start_single_read。这里不再赘述

写响应通道

这里的握手信号和写其实是反过来的,因为你是接收从机的写结束信号嘛:

always @(posedge M_AXI_ACLK)
begin
if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1)
	begin
	axi_bready <= 1'b0;
	end
// accept/acknowledge bresp with axi_bready by the master
// when M_AXI_BVALID is asserted by slave
else if (M_AXI_BVALID && ~axi_bready)
	begin
	axi_bready <= 1'b1;
	end
// deassert after one clock cycle
else if (axi_bready)
	begin
	axi_bready <= 1'b0;
	end
// retain the previous value
else
	axi_bready <= axi_bready;
end

握手还是那一套,简单。

AXI协议实现总结

通过看代码,其实只要修改start_single_readstart_single_write这两个信号,握手部分就不用管了,最后还要考虑地址和数据怎么对应起来就完事了。

仿真

完整工程建立

所以这里的仿真其实十分简单,我们直接把主机和从机接起来完事了:

这里我们拉一主一从,从就拿我们上一节中生成的ip就完了:

然后点 Run Connection Automation(直接连也可以,但是我懒了…):

此时会发现,我们控制的几个引脚没引出,这里的,右键那几个端口,Create Port(Ctrl +K):

image-20210420181156316

完成之后,整体就长这样:

然后打包成wrapper:

image-20210420181451618

我这里之前已经有了,也是一样的操作

然后打开Windows->Address Editor:

image-20210420184735847

看到从机的地址之后,修改主机的起始地址:

image-20210420184829462
仿真流程

​ 在AXI总线的仿真中,有好几种方法,这里也介绍一下:

  1. Xilinx官方出的VIP,是一个专用来仿真AXI的IP,他有一个示例工程,是用SystemVerilog搞的,笔者还不会。。。(这就去学
  2. 自己根据协议写TB

那这里由于我们主端和从端的外设都有了,我们就直接接上得了。

  1. 建立一个仿真文件_tb.v
  2. 用插件生成一个tb,并亿点点修改
module top_tb();
// design_1_wrapper Parameters
parameter PERIOD  = 10;
// design_1_wrapper Inputs
reg   clk_100MHz                           = 0 ;
reg   m00_axi_init_axi_txn                 = 1 ;
reg   reset_rtl_0                          = 0 ;
reg   reset_rtl_0_0                        = 0 ;
    
// design_1_wrapper Outputs
initial
begin
    forever #(PERIOD/2)  clk_100MHz=~clk_100MHz;
end

initial
begin
    #(PERIOD*2) reset_rtl_0_0  =  1;
    #(PERIOD*10) reset_rtl_0_0  = 0;
    #(PERIOD*10) reset_rtl_0_0  = 1;
end

design_1_wrapper  u_design_1_wrapper (
    .clk_100MHz              ( clk_100MHz             ),
    .m00_axi_init_axi_txn    ( m00_axi_init_axi_txn   ),
    .reset_rtl_0             ( reset_rtl_0            ),
    .reset_rtl_0_0           ( reset_rtl_0_0          )
);

initial
begin
    #(PERIOD*500) m00_axi_init_axi_txn  = 1;
    #(PERIOD*10) m00_axi_init_axi_txn  = 0;
    #(PERIOD*2)  m00_axi_init_axi_txn  = 1;
    #(PERIOD*1000)
    $stop;
end
endmodule

在vivado中用自带仿真工具,点行为级仿真,然后直接拉AXI总线下来观察:

image-20210420190128849

从端同理,然后把主端的几个关键信号也拉上,就可以看到图啦

image-20210420190437642

具体波形就不讲为什么了,原理上面都有。有兴趣的朋友可以动手做一下~

​ 这里需要注意,如果是自己写的IP想接到这里的话,在package IP哪里会有让你对应AXI总线对线的操作,这样就可以在仿真的时候以这种好看的形式存在了,但是这个后面才会说到。

小结

经过这一节,相信大家会对AXI-Lite有更深的认识了,怎么去做自己的AXI-Lite其实在上面状态机中就哭呀归纳出来了,但实际上AXI-Lite只是AXI协议中最简单的一部分,后一节会开始讲AXI-FULL,然后会讲一两个IP来实际操作,再后面才会讲到AXI-Stream,但是AXI-Stream的实战会多一点。

如果你觉得有一点收获的话

也欢迎关注我的个人公众号,小何的芯像石头:

recommend

  • 9
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小何的芯像石头

谢谢你嘞,建议用用我的链接

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值