本文是PL读写DDR3 实现PS和PL间的数据交互 的继续,深入分析其代码。
首先分析基本要求,或者需求分析,然后读写流程描述,实测采集的读写过程的波形图,最后分段代码分析,这个代码是上文中利用vivado 2018.2 自动生成的,也许做了一点修改比如注释。然后对代码分段分析,我想使用这些代码,所以必须分析弄懂。在引用中@5表示第5个always 代码段。
还在继续学习中,如果有什么问题,大家一起讨论,若有更明白说法的地方请赐教。
要想PL访问板上的DDR3存储器,必须借道Zynq中的“Memory Interfaces----DDR2/3,LPDDR2 Controller”(后文简称“DDR3 Controller”)。根据之前的经验,在Zynq系统中,ARM Core(CPU)能够访问硬核“DDR3 Controller”,根据经验可以确定“DDR3 Controller”一定是一个从设备,而PL要想访问“DDR3 Controller”的话,PL一定要是一个主设备,由PL发起读写操作。
“DDR3 Controller”是封装在Zynq子系统中的,因此,PL必须连接Zynq的从机接口。Zynq有两个从机接口,分别是“32b GP AXI Slave Ports”和“High Performance AXI 32b/64b Slave Ports”根据名称可以看出,一个是高性能的,另一个应该是普通的。之前Zynq作为主机连接AXI4-Lite从设备时,走的是“32b GP AXI Master Ports”,可以辅助证明,对于本节应用,走的接口应该是“32b GP AXI Slave Ports”。
交互数据将会经过Zynq子系统的内部总线控制器“Central Interconnect”转发给Memory Interfaces。
除了读写流程外,本代码也包含了一段测试代码,激发读写过程,代码内容如下:
利用向导生成的PL端AXI4-Lite Master IP 用户用例,在INIT_AXI_TXN负脉冲触发下,主机模块的逻辑是连续4次(次数默认为4,可通过修改参数C_M00_AXI_TRANSACTIONS_NUM的数值改变次数)向递增的地址区间写入4组测试数据,测试数据每次加1。然后主机模块自动读取刚才写入数据的地址内的数据,将读的的数据与写入数据进行比较,如果正确,主机IP的ERROR信号保持低电平,如果错误,ERROR给出高电平。然后触发结束后,TXN_DONE指示运行结束。
写入数据的内容是:C_M_START_DATA_VALUE 决定的,axi_wdata <= C_M_START_DATA_VALUE + write_index; write_index是0,1,2,3
读写的地址内容是 C_M_TARGET_SLAVE_BASE_ADDR决定的。偏置地址是0,4,8,12
这个模块的外部接口是:
input wire m00_axi_init_axi_txn,
output wire m00_axi_error,
output wire m00_axi_txn_done,
此外,除了复位,时钟,就是axi 总线了,我们的是Master 接口。
可以参考我的另一博文:AXI4 Lite 协议分析,可以查看读写时序分析图,信号线总结也一样。
1:写地址通道: AWVALID, AWADDR, AWREADY(O),AWPROT
2:写数据通道: WVALID, WDATA, WSTRB, WREADY(O)
3:写应答通道: BVALID(O), BRESP(O), BREADY
4:读地址通道: ARVALID, ARADDR,ARREADY(O),ARPROT
5:读数据通道: RVALID,RDATA,RREADY, RRESP
6:系统通道: ACLK, ARESETN
https://www.realdigital.org/doc/a9fee931f7a172423e1ba73f66ca4081 介绍不错,部分原文引用在此
https://www.realdigital.org/doc/c4d57104000339a55b764e5e5f21e28c 看波形不错。
Below, the sequence for an AXI4-Lite write is shown:
A description of the events in figure 4 follows:
Master 把地址和数据放通道上,同时拉高AWVALID WVALID,表面地址和数据有效,同时BREADY由Master 拉高,表明准备接收响应。
Slave 拉高AWREADY WREADY
数据和地址都有Valid Ready 信号,开始握手,同时Valid Ready 放低。这2个握手都发生时,Slave 有了地址和数据
Slave 拉高 BVALID,响应是2‘b00 也就是'OKEY'
下个时钟周期,写事务结束写响应的Ready Valid 都拉高
代码中是这样的:
设置写保护级别:assign M_AXI_AWPROT = 3'b000;
写使能:assign M_AXI_WSTRB = 4'b1111;
开始写过程时:start_single_write <= 1'b1;
write_issued <= 1'b1;
axi_awvalid <= 1'b1; axi_wvalid <= 1'b1;
WDATA,AWADDR准备好写的数据(M_AXI_AWREADY && axi_awvalid) ,和地址数据(M_AXI_WREADY && axi_wvalid)
M_AXI_AWREADY,M_AXI_WREADY,来自slave 的输入
*****我认为地址和数据应该先于awvalid,wvalid 才对,可这里好像后于,不知为什么
axi_awvalid <= 1'b0(M_AXI_AWREADY && axi_awvalid);
axi_wvalid <= 1'b0;(M_AXI_WREADY && axi_wvalid)
axi_bready <= 1'b1;(M_AXI_BVALID && ~axi_bready) 只有一个时钟的上脉冲,M_AXI_BVALID来自Slave
last_write 的时候,write_issued <= 1'b0; (axi_bready),然后start_single_write <= 1'b0;
Below, the sequence for an AXI4-Lite read is shown:
A description of the events in figure 3 follows:
Master 把地址放读地址通道上,ARVALID 拉高,指示地址有效,同时RREADY 也拉高,指示准备好接受数据。
Slave 拉高ARREADY,指示准备接收读地址总线的地址
ARVALID ARREADY都拉高了,下个时钟上升沿,握手发生,这之后,ARVALID ARREADY 放低,这个点,Slave接受地址
Slave 把读数据放读数据通道上,,拉高 RVALID,指示数据有效,Slave 也有RRESP响应,这里图上没有
RREADY RVALID 都拉高了,下一个时钟周期,读事务结束了,RREADY RVALID 放低。
读流程开始:start_single_read <= 1'b1;
read_issued <= 1'b1; (~axi_arvalid && ~M_AXI_RVALID)
axi_arvalid <= 1'b1; (start_single_read)
写读地址:axi_araddr <=.. (M_AXI_ARREADY && axi_arvalid)
axi_rready <= 1'b1;(M_AXI_RVALID && ~axi_rready) 一个时钟脉冲
expected_rdata <= (M_AXI_RVALID && axi_rready)
read_mismatch <= 1'b1;((M_AXI_RVALID && axi_rready) && (M_AXI_RDATA != expected_rdata))
并没有读取数据,简单与期望值比较一下。
axi_rready <= 1'b0; (axi_rready) 只有一个时钟高脉冲
这2个实测波形图,通过Vivado 下的集成逻辑分析仪ILA在开发板上实际测试获得。其方法请见 Vivado的集成逻辑分析仪ILA 在有sdk 下的应用入门。
axi4 写流程:
这个地址线分成了好几组,只有WDATA[2:0]是变化的,也不知道怎么控制能分成几组。
这个文件比较小,3kb大小,只是一个接口封装,列出所有接口,然后调用实例
// Instantiation of Axi Bus Interface M00_AXI
这行开始就是调用实例。
这个是我们分析的重点,代码实现都是在这里。
到 95,96行
output wire M_AXI_RREADY
);
一直是函数的说明,io等。
然后到110行,是2个辅助函数,
// function called clogb2 that returns an integer which has the
// value of the ceiling of the log base 2
function integer clogb2 (input integer bit_depth);
begin
for(clogb2=0; bit_depth>0; clogb2=clogb2+1)
bit_depth = bit_depth >> 1;
end
endfunction
// TRANS_NUM_BITS is the width of the index counter for
// number of write or read transaction.
localparam integer TRANS_NUM_BITS = clogb2(C_M_TRANSACTIONS_NUM-1);
到128行reg [1:0] mst_exec_state; 定义测试代码状态机,读,写,比较,空闲
这个不是读写的代码,而是测试调用读写的代码
在185行或者 // I/O Connections assignments前,都是寄存器声明
到212行或者 //Generate a pulse to initiate AXI transaction. 行前一组assign,io口对应寄存器变量
// I/O Connections assignments
//Adding the offset address to the base addr of the slave
assign M_AXI_AWADDR = C_M_TARGET_SLAVE_BASE_ADDR + axi_awaddr;
//AXI 4 write data
assign M_AXI_WDATA = axi_wdata;
assign M_AXI_AWPROT = 3'b000;
assign M_AXI_AWVALID = axi_awvalid;
//Write Data(W)
assign M_AXI_WVALID = axi_wvalid;
//Set all byte strobes in this example
assign M_AXI_WSTRB = 4'b1111;
//Write Response (B)
assign M_AXI_BREADY = axi_bready;
//Read Address (AR)
assign M_AXI_ARADDR = C_M_TARGET_SLAVE_BASE_ADDR + axi_araddr;
assign M_AXI_ARVALID = axi_arvalid;
assign M_AXI_ARPROT = 3'b001;
//Read and Read Response (R)
assign M_AXI_RREADY = axi_rready;
//Example design I/O
assign TXN_DONE = compare_done;
assign init_txn_pulse = (!init_txn_ff2) && init_txn_ff;
@1: 然后处理: init_txn_ff, init_txn_ff2,init_txn_ff 直接对应INIT_AXI_TXN输入,但init_txn_ff2 需要延后一个时钟
这段代码的作用就是得到一个init_txt_pulse,上升脉冲,作为程序启动的标识。init_txn_ff, init_txn_ff2在其他地方都没有用到,只有一个语句:
assign init_txn_pulse = (!init_txn_ff2) && init_txn_ff;
这个init_txn_pulse == 1'b1 几乎每个always 都用到,作为初始化,为程序运行做准备。
//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
@2: 230行或者//Write Address Channel 开始,处理Write Address 通道的 axi_awvalid
写地址通道有效,由start_single_write启动为1,M_AXI_AWREADY(由slave确认)告知结束,为0。
//--------------------
//Write Address Channel
//--------------------
// The purpose of the write address channel is to request the address and
// command information for the entire transaction. It is a single beat
// of information.
// Note for this example the axi_awvalid/axi_wvalid are asserted at the same
// time, and then each is deasserted independent from each other.
// This is a lower-performance, but simplier control scheme.
// AXI VALID signals must be held active until accepted by the partner.
// A data transfer is accepted by the slave when a master has
// VALID data and the slave acknoledges it is also READY. While the master
// is allowed to generated multiple, back-to-back requests by not
// deasserting VALID, this design will add rest cycle for
// simplicity.
// Since only one outstanding transaction is issued by the user design,
// there will not be a collision between a new request and an accepted
// request on the same clock cycle.
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 (issue of M_AXI_AWREADY by slave)
else if (M_AXI_AWREADY && axi_awvalid)
begin
axi_awvalid <= 1'b0;
end
end
end
@3: 277行或者// start_single_write triggers a new write
write_index,实际上只是0,1,2,3
@4: 297 行或者 //Write Data Channel
axi_wvalid,写数据有效,由start_single_write==1 发起,为1,在M_AXI_WREADY==1 时回落,M_AXI_WREADY是slave发起确定的。
//--------------------
//Write Data Channel
//--------------------
//The write data channel is for transfering the actual data.
//The data generation is speific to the example design, and
//so only the WVALID/WREADY handshake is shown here
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 (issue of M_AXI_WREADY by slave)
else if (M_AXI_WREADY && axi_wvalid)
begin
axi_wvalid <= 1'b0;
end
end
@5: 324行或者 //Write Response (B) Channel
axi_bready:要写的数据,数据地址都到达slave,而且被slave 确认了。M_AXI_BVALID(由slave反馈的)为高,则axi_bready 变高,一个时钟后回低,也就是说一个正脉冲。
//----------------------------
//Write Response (B) Channel
//----------------------------
//The write response channel provides feedback that the write has committed
//to memory. BREADY will occur after both the data and the write address
//has arrived and been accepted by the slave, and can guarantee that no
//other accesses launched afterwards will be able to be reordered before it.
//The BRESP bit [1] is used indicate any errors from the interconnect or
//slave for the entire write burst. This example will capture the error.
//While not necessary per spec, it is advisable to reset READY signals in
//case of differing reset latencies between master/slave.
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
//Flag write errors
assign write_resp_error = (axi_bready & M_AXI_BVALID & M_AXI_BRESP[1]);
@6: 365行//Read Address Channel
read_index,实际上只是0,1,2,3
@7: 385行开始,
axi_arvalid:读地址有效,由start_single_read启动,为1,M_AXI_ARREADY(由slave 发起)转为0
// A new axi_arvalid is asserted when there is a valid read address
// available by the master. start_single_read triggers a new read
// transaction
always @(posedge M_AXI_ACLK)
begin
if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1)
begin
axi_arvalid <= 1'b0;
end
//Signal a new read address command is available by user logic
else if (start_single_read)
begin
axi_arvalid <= 1'b1;
end
//RAddress accepted by interconnect/slave (issue of M_AXI_ARREADY by slave)
else if (M_AXI_ARREADY && axi_arvalid)
begin
axi_arvalid <= 1'b0;
end
// retain the previous value
end
@8: 409行//Read Data (and Response) Channel
处理 axi_rready :读响应, M_AXI_RVALID 由slave激发读有效后,给一个高脉冲,作为读响应
always @(posedge M_AXI_ACLK)
begin
if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1)
begin
axi_rready <= 1'b0;
end
// accept/acknowledge rdata/rresp with axi_rready by the master
// when M_AXI_RVALID is asserted by slave
else if (M_AXI_RVALID && ~axi_rready)
begin
axi_rready <= 1'b1;
end
// deassert after one clock cycle
else if (axi_rready)
begin
axi_rready <= 1'b0;
end
// retain the previous value
end
440行开始是//user logic 用户逻辑。虽然是用户逻辑,但也很重要,写地址的写,写数据的写,读地址的写,读数据
@9: 450-464:写地址的写,
//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
@10: 466-479:写数据的写:
// 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
@11: 481-494:读地址的写:
//Read Addresses
always @(posedge M_AXI_ACLK)
begin
if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1)
begin
axi_araddr <= 0;
end
// Signals a new write address/ write data is
// available by user logic
else if (M_AXI_ARREADY && axi_arvalid)
begin
axi_araddr <= axi_araddr + 32'h00000004;
end
end
@12: 498-510:获取期望的读数据,expected保存期望的读数据,用于比较。
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
@13: 511-616:测试主程序的状态机转换代码
复位初始状态是IDLE,有外部激发时,转为INIT_WRITE,
在INIT_WRITE状态时,write_done==1,转为INIT_READ
在INIT_READ状态下,read_done==1,转为INIT_COMPARE
在INIT_COMPARE状态下,一个周期转换为IDLE
//implement master command interface state machine
always @ ( posedge M_AXI_ACLK)
begin
if (M_AXI_ARESETN == 1'b0)
begin
// reset condition
// All the signals are assigned default values under reset condition
mst_exec_state <= IDLE;
start_single_write <= 1'b0;
write_issued <= 1'b0;
start_single_read <= 1'b0;
read_issued <= 1'b0;
compare_done <= 1'b0;
ERROR <= 1'b0;
end
else
begin
// state transition
case (mst_exec_state)
IDLE:
// This state is responsible to initiate
// AXI transaction when init_txn_pulse is asserted
if ( init_txn_pulse == 1'b1 )
begin
mst_exec_state <= INIT_WRITE;
ERROR <= 1'b0;
compare_done <= 1'b0;
end
else
begin
mst_exec_state <= IDLE;
end
INIT_WRITE:
// This state is responsible to issue start_single_write pulse to
// initiate a write transaction. Write transactions will be
// issued until last_write signal is asserted.
// write controller
if (writes_done)
begin
mst_exec_state <= INIT_READ;//
end
else
begin
mst_exec_state <= INIT_WRITE;
if (~axi_awvalid && ~axi_wvalid && ~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
end
INIT_READ:
// This state is responsible to issue start_single_read pulse to
// initiate a read transaction. Read transactions will be
// issued until last_read signal is asserted.
// read controller
if (reads_done)
begin
mst_exec_state <= INIT_COMPARE;
end
else
begin
mst_exec_state <= INIT_READ;
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
end
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
default :
begin
mst_exec_state <= IDLE;
end
endcase
end
end //MASTER_EXECUTION_PROC
@14: 618-630:测试是否最后一次写
//Terminal write count
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
@15: 632-648:判断写是否完成,write_done
//Check for last write completion.
//This logic is to qualify the last write count with the final write
//response. This demonstrates how to confirm that a write has been
//committed.
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
@16: 654-666:判断是否最后一次读
//Terminal Read Count
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
@17: 668-684:判断读是否完成
/*
Check for last read completion.
This logic is to qualify the last read count with the final read
response/data.
*/
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
@18: 690-701:数据比较,如果不匹配,则read_mismatch 为1,每一次外部激发清0
//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
@19: 703-714:错误指示的计算
读取数据不匹配,读响应错误,或写响应错误都有错误只是,每一次外部激发清0
// 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
我的文件位置在 D:\alinx\ip_repo\AzIP_AXI_Master_1.0\hdl 目录下。
AzIP_AXI_Master_v1_0.v 文件内容 90行
AzIP_AXI_Master_v1_0_M00_AXI.v 文件内容720 行
文件代码太长,不占文章太多内容了。都是PL读写DDR3 实现PS和PL间的数据交互 一文由vivado 产生的,若有需要可以留言联系我。