The previous article mainly talked about some knowledge about AXI. With these theories, let us conduct some simple experimental tests to deepen our understanding of the AXI protocol. The platform used in this experiment is VIVADO2019.1.
1. First create a project, the name and path are decided by yourself.
2. Then click New and package a new IP core as shown in the figure.
3. Click NEXT
4. Select an IP core with an AXI4 interface, then click NEXT
5. Remember to modify the name, otherwise it is not easy to modify later, the path can be directly placed in the root directory of the project
6. We modify the name, and then select the AXI of the FULL interface, select the IP core as the host, and select 32bits for the data bit width
7. We choose to edit this IP, and we successfully call the IP
8. After the IP setting is completed, we can see the .v code inside, which is mainly a test module. Its function is to use the AXI4 interface to send 1024 data, and then read the data back to verify AXI4 by comparison. The function of this IP core. I made some comments on the generated code, you can understand it, the code does not need to be moved, it can be used directly.
`timescale 1 ns / 1 ps
module AXI4_v1_0_AXI_MASTER #
(
// Users to add parameters here
// User parameters ends
// Do not modify the parameters beyond this line
// Base address of targeted slave 基地址
parameter C_M_TARGET_SLAVE_BASE_ADDR = 32'h40000000,
// Burst Length. Supports 1, 2, 4, 8, 16, 32, 64, 128, 256 burst lengths 突发长度
parameter integer C_M_AXI_BURST_LEN = 16,
// Thread ID Width 写ID宽度
parameter integer C_M_AXI_ID_WIDTH = 1,
// Width of Address Bus 写地址宽度
parameter integer C_M_AXI_ADDR_WIDTH = 32,
// Width of Data Bus 写数据宽度
parameter integer C_M_AXI_DATA_WIDTH = 32,
// Width of User Write Address Bus
parameter integer C_M_AXI_AWUSER_WIDTH = 0,
// Width of User Read Address Bus
parameter integer C_M_AXI_ARUSER_WIDTH = 0,
// Width of User Write Data Bus
parameter integer C_M_AXI_WUSER_WIDTH = 0,
// Width of User Read Data Bus
parameter integer C_M_AXI_RUSER_WIDTH = 0,
// Width of User Response Bus
parameter integer C_M_AXI_BUSER_WIDTH = 0
)
(
// Users to add ports here
// User ports ends
// Do not modify the ports beyond this line
//用户IO
// Initiate AXI transactions 写开始信号
input wire INIT_AXI_TXN,
// Asserts when transaction is complete写完成信号
output wire TXN_DONE,
// Asserts when ERROR is detected 数据检测信号
output reg ERROR,
// Global Clock Signal. 全剧时钟
input wire M_AXI_ACLK,
// Global Reset Singal. This Signal is Active Low 全局复位信号
input wire M_AXI_ARESETN,
// Master Interface Write Address ID 写地址ID
output wire [C_M_AXI_ID_WIDTH-1 : 0] M_AXI_AWID,
// Master Interface Write Address 写地址
output wire [C_M_AXI_ADDR_WIDTH-1 : 0] M_AXI_AWADDR,
// Burst length. The burst length gives the exact number of transfers in a burst
output wire [7 : 0] M_AXI_AWLEN, //写长度
// Burst size. This signal indicates the size of each transfer in the burst
output wire [2 : 0] M_AXI_AWSIZE, //写宽度
// Burst type. The burst type and the size information,
// determine how the address for each transfer within the burst is calculated.
output wire [1 : 0] M_AXI_AWBURST, //写类型
// Lock type. Provides additional information about the
// atomic characteristics of the transfer.
output wire M_AXI_AWLOCK, //独占传输类型
// Memory type. This signal indicates how transactions
// are required to progress through a system.
output wire [3 : 0] M_AXI_AWCACHE, //存储类型,指明是否缓存
// Protection type. This signal indicates the privilege
// and security level of the transaction, and whether
// the transaction is a data access or an instruction access.
output wire [2 : 0] M_AXI_AWPROT, //安全保护
// Quality of Service, QoS identifier sent for each write transaction.
output wire [3 : 0] M_AXI_AWQOS, //服务质量QOS
// Optional User-defined signal in the write address channel. 用户自定义
output wire [C_M_AXI_AWUSER_WIDTH-1 : 0] M_AXI_AWUSER,
// Write address valid. This signal indicates that
// the channel is signaling valid write address and control information.
output wire M_AXI_AWVALID, // 写地址发送有效,主机发给从机
// Write address ready. This signal indicates that
// the slave is ready to accept an address and associated control signals
input wire M_AXI_AWREADY, //写地址接收有效,从机发给主机
// Master Interface Write Data. 写数据 32位
output wire [C_M_AXI_DATA_WIDTH-1 : 0] M_AXI_WDATA,
// Write strobes. This signal indicates which byte
// lanes hold valid data. There is one write strobe
// bit for each eight bits of the write data bus. 频闪信号32/8 = 4位
output wire [C_M_AXI_DATA_WIDTH/8-1 : 0] M_AXI_WSTRB,
// Write last. This signal indicates the last transfer in a write burst.
output wire M_AXI_WLAST, //写数据最后一位
// Optional User-defined signal in the write data channel.
output wire [C_M_AXI_WUSER_WIDTH-1 : 0] M_AXI_WUSER, //用户自定义
// Write valid. This signal indicates that valid write
// data and strobes are available 写数据发送有效,主到从
output wire M_AXI_WVALID,
// Write ready. This signal indicates that the slave
// can accept the write data. //写数据接收有效,从到主
input wire M_AXI_WREADY,
// Master Interface Write Response. 写响应ID
input wire [C_M_AXI_ID_WIDTH-1 : 0] M_AXI_BID,
// Write response. This signal indicates the status of the write transaction.
input wire [1 : 0] M_AXI_BRESP, //写响应信号
// Optional User-defined signal in the write response channel
input wire [C_M_AXI_BUSER_WIDTH-1 : 0] M_AXI_BUSER, // 用户自定义
// Write response valid. This signal indicates that the
// channel is signaling a valid write response.
input wire M_AXI_BVALID, //写响应信号发送有效,从到主
// Response ready. This signal indicates that the master
// can accept a write response.
output wire M_AXI_BREADY, //写响应信号接收有效,主到从
// Master Interface Read Address.读地址ID
output wire [C_M_AXI_ID_WIDTH-1 : 0] M_AXI_ARID,
// Read address. This signal indicates the initial
// address of a read burst transaction. 读地址
output wire [C_M_AXI_ADDR_WIDTH-1 : 0] M_AXI_ARADDR,
// Burst length. The burst length gives the exact number of transfers in a burst
output wire [7 : 0] M_AXI_ARLEN, //读突发长度
// Burst size. This signal indicates the size of each transfer in the burst
output wire [2 : 0] M_AXI_ARSIZE, //读突发大小
// Burst type. The burst type and the size information,
// determine how the address for each transfer within the burst is calculated.
output wire [1 : 0] M_AXI_ARBURST, //读突发类型
// Lock type. Provides additional information about the
// atomic characteristics of the transfer.
output wire M_AXI_ARLOCK, //总线锁信号
// Memory type. This signal indicates how transactions
// are required to progress through a system.
output wire [3 : 0] M_AXI_ARCACHE, //Cache类型,是否缓存
// Protection type. This signal indicates the privilege
// and security level of the transaction, and whether
// the transaction is a data access or an instruction access.
output wire [2 : 0] M_AXI_ARPROT, //保护类型
// Quality of Service, QoS identifier sent for each read transaction
output wire [3 : 0] M_AXI_ARQOS, //质量服务
// Optional User-defined signal in the read address channel.
output wire [C_M_AXI_ARUSER_WIDTH-1 : 0] M_AXI_ARUSER,
// Write address valid. This signal indicates that
// the channel is signaling valid read address and control information
output wire M_AXI_ARVALID, //读地址发送有效,主到从
// Read address ready. This signal indicates that
// the slave is ready to accept an address and associated control signals
input wire M_AXI_ARREADY, //读地址接收有效,从到主
// Read ID tag. This signal is the identification tag
// for the read data group of signals generated by the slave.
input wire [C_M_AXI_ID_WIDTH-1 : 0] M_AXI_RID, //读数据ID
// Master Read Data 读数据
input wire [C_M_AXI_DATA_WIDTH-1 : 0] M_AXI_RDATA,
// Read response. This signal indicates the status of the read transfer
input wire [1 : 0] M_AXI_RRESP, //读响应信号
// Read last. This signal indicates the last transfer in a read burst
input wire M_AXI_RLAST, //突发读传输的最后一个数据
// Optional User-defined signal in the read address channel.
input wire [C_M_AXI_RUSER_WIDTH-1 : 0] M_AXI_RUSER, //用户自定义信号
// Read valid. This signal indicates that the channel
// is signaling the required read data.
input wire M_AXI_RVALID, //读数据发送有效,从到主
// Read ready. This signal indicates that the master can
// accept the read data and response information.
output wire M_AXI_RREADY //读数据接收有效。主到从
);
// function called clogb2 that returns an integer which has the
//value of the ceiling of the log base 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
// C_TRANSACTIONS_NUM is the width of the index counter for
// number of write or read transaction.(4) C_TRANSACTIONS_NUM指读/写标识计数器的位宽
localparam integer C_TRANSACTIONS_NUM = clogb2(C_M_AXI_BURST_LEN-1);
// Burst length for transactions, in C_M_AXI_DATA_WIDTHs.
// Non-2^n lengths will eventually cause bursts across 4K address boundaries.
localparam integer C_MASTER_LENGTH = 12;
// total number of burst transfers is master length divided by burst length and burst size
localparam integer C_NO_BURSTS_REQ = C_MASTER_LENGTH-clogb2((C_M_AXI_BURST_LEN*C_M_AXI_DATA_WIDTH/8)-1);//12-6 = 6
// Example State machine to initialize counter, initialize write transactions,
// initialize read transactions and comparison of read data with the
// written data words.
parameter [1:0] IDLE = 2'b00, // This state initiates AXI4Lite transaction
// after the state machine changes state to INIT_WRITE
// when there is 0 to 1 transition on INIT_AXI_TXN 上升沿
INIT_WRITE = 2'b01, // This state initializes write transaction,
// once writes are done, the state machine
// changes state to INIT_READ
INIT_READ = 2'b10, // This state initializes read transaction
// once reads are done, the state machine
// changes state to INIT_COMPARE
INIT_COMPARE = 2'b11; // This state issues the status of comparison
// of the written data with the read data
reg [1:0] mst_exec_state;
// AXI4LITE signals
//AXI4 internal temp signals
reg [C_M_AXI_ADDR_WIDTH-1 : 0] axi_awaddr; //32位写地址
reg axi_awvalid;
reg [C_M_AXI_DATA_WIDTH-1 : 0] axi_wdata; //32位写数据
reg axi_wlast;
reg axi_wvalid;
reg axi_bready;
reg [C_M_AXI_ADDR_WIDTH-1 : 0] axi_araddr; //32位读地址
reg axi_arvalid;
reg axi_rready;
//write beat count in a burst 写计数5位
reg [C_TRANSACTIONS_NUM : 0] write_index;
//read beat count in a burst 读计数5位
reg [C_TRANSACTIONS_NUM : 0] read_index;
//size of C_M_AXI_BURST_LEN length burst in bytes 7位
wire [C_TRANSACTIONS_NUM+2 : 0] burst_size_bytes;
//The burst counters are used to track the number of burst transfers of C_M_AXI_BURST_LEN burst length needed to transfer 2^C_MASTER_LENGTH bytes of data.
reg [C_NO_BURSTS_REQ : 0] write_burst_counter; //7位
reg [C_NO_BURSTS_REQ : 0] read_burst_counter; //7位
reg start_single_burst_write;
reg start_single_burst_read;
reg writes_done;
reg reads_done;
reg error_reg;
reg compare_done;
reg read_mismatch;
reg burst_write_active;
reg burst_read_active;
reg [C_M_AXI_DATA_WIDTH-1 : 0] expected_rdata;
//Interface response error flags
wire write_resp_error;
wire read_resp_error;
wire wnext;
wire rnext;
reg init_txn_ff;
reg init_txn_ff2;
reg init_txn_edge;
wire init_txn_pulse;
// I/O Connections assignments
wire douta;
wire ena;
//I/O Connections. Write Address (AW)
assign M_AXI_AWID = 'b0;
//The AXI address is a concatenation of the target base address + active offset range
assign M_AXI_AWADDR = C_M_TARGET_SLAVE_BASE_ADDR + axi_awaddr;
//Burst LENgth is number of transaction beats, minus 1
assign M_AXI_AWLEN = C_M_AXI_BURST_LEN - 1;
//Size should be C_M_AXI_DATA_WIDTH, in 2^SIZE bytes, otherwise narrow bursts are used
assign M_AXI_AWSIZE = clogb2((C_M_AXI_DATA_WIDTH/8)-1);//2'b10; 4BYTE
//INCR burst type is usually used, except for keyhole bursts
assign M_AXI_AWBURST = 2'b01; //INCR自增类型
assign M_AXI_AWLOCK = 1'b0;
//Update value to 4'b0011 if coherent accesses to be used via the Zynq ACP port. Not Allocated, Modifiable, not Bufferable. Not Bufferable since this example is meant to test memory, not intermediate cache.
assign M_AXI_AWCACHE = 4'b0010;
assign M_AXI_AWPROT = 3'h0;
assign M_AXI_AWQOS = 4'h0;
assign M_AXI_AWUSER = 'b1;
assign M_AXI_AWVALID = axi_awvalid;
//Write Data(W)
assign M_AXI_WDATA = douta;
//All bursts are complete and aligned in this example
assign M_AXI_WSTRB = {(C_M_AXI_DATA_WIDTH/8){1'b1}};//4'B1111;
assign M_AXI_WLAST = axi_wlast;
assign M_AXI_WUSER = 'b0;
assign M_AXI_WVALID = axi_wvalid;
//Write Response (B)
assign M_AXI_BREADY = axi_bready;
//Read Address (AR)
assign M_AXI_ARID = 'b0;
assign M_AXI_ARADDR = C_M_TARGET_SLAVE_BASE_ADDR + axi_araddr;
//Burst LENgth is number of transaction beats, minus 1
assign M_AXI_ARLEN = C_M_AXI_BURST_LEN - 1;
//Size should be C_M_AXI_DATA_WIDTH, in 2^n bytes, otherwise narrow bursts are used
assign M_AXI_ARSIZE = clogb2((C_M_AXI_DATA_WIDTH/8)-1);
//INCR burst type is usually used, except for keyhole bursts
assign M_AXI_ARBURST = 2'b01;
assign M_AXI_ARLOCK = 1'b0;
//Update value to 4'b0011 if coherent accesses to be used via the Zynq ACP port. Not Allocated, Modifiable, not Bufferable. Not Bufferable since this example is meant to test memory, not intermediate cache.
assign M_AXI_ARCACHE = 4'b0010;
assign M_AXI_ARPROT = 3'h0;
assign M_AXI_ARQOS = 4'h0;
assign M_AXI_ARUSER = 'b1;
assign M_AXI_ARVALID = axi_arvalid;
//Read and Read Response (R)
assign M_AXI_RREADY = axi_rready;
//Example design I/O
assign TXN_DONE = compare_done;
//Burst size in bytes
assign burst_size_bytes = C_M_AXI_BURST_LEN * C_M_AXI_DATA_WIDTH/8;//16*32/8 = 64
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
//--------------------
//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.
// The AXI4 Write address channel in this example will continue to initiate
// write commands as fast as it is allowed by the slave/interconnect.
// The address will be incremented on each accepted address transaction,
// by burst_size_byte to point to the next address.
always @(posedge M_AXI_ACLK)
begin
if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1 )
begin
axi_awvalid <= 1'b0;
end
// If previously not valid , start next transaction
else if (~axi_awvalid && start_single_burst_write)
begin
axi_awvalid <= 1'b1;
end
/* Once asserted, VALIDs cannot be deasserted, so axi_awvalid
must wait until transaction is accepted */
else if (M_AXI_AWREADY && axi_awvalid)
begin
axi_awvalid <= 1'b0;
end
else
axi_awvalid <= axi_awvalid;
end
// Next address after AWREADY indicates previous address acceptance
always @(posedge M_AXI_ACLK)
begin
if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1)
begin
axi_awaddr <= 'b0;
end
else if (M_AXI_AWREADY && axi_awvalid)
begin
axi_awaddr <= axi_awaddr + burst_size_bytes; //突发长度为16,突发大小为4个字节,16*4=64
end
else
axi_awaddr <= axi_awaddr;
end
//--------------------
//Write Data Channel 写数据通道
//--------------------
//The write data will continually try to push write data across the interface.
//The amount of data accepted will depend on the AXI slave and the AXI
//Interconnect settings, such as if there are FIFOs enabled in interconnect.
//Note that there is no explicit timing relationship to the write address channel.
//The write channel has its own throttling flag, separate from the AW channel.
//Synchronization between the channels must be determined by the user.
//The simpliest but lowest performance would be to only issue one address write
//and write data burst at a time.
//In this example they are kept in sync by using the same address increment
//and burst sizes. Then the AW and W channels have their transactions measured
//with threshold counters as part of the user logic, to make sure neither
//channel gets too far ahead of each other.
//Forward movement occurs when the write channel is valid and ready
assign wnext = M_AXI_WREADY & axi_wvalid; //wnext表示握手成功
// WVALID logic, similar to the axi_awvalid always block above
always @(posedge M_AXI_ACLK)
begin
if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1 )
begin
axi_wvalid <= 1'b0;
end
// If previously not valid, start next transaction
else if (~axi_wvalid && start_single_burst_write)
begin
axi_wvalid <= 1'b1;
end
/* If WREADY and too many writes, throttle WVALID
Once asserted, VALIDs cannot be deasserted, so WVALID
must wait until burst is complete with WLAST */
else if (wnext && axi_wlast)
axi_wvalid <= 1'b0;
else
axi_wvalid <= axi_wvalid;
end
9. Then you add the IP core just generated in Block Design, add a DRAM, and connect the module. BRAM my configuration looks like this:
Then others need not be modified, just add directly, connect the modules after adding, and use a control module to connect them. After the connection is completed, as shown in the figure:
10. After completing the above work, we connected the AXI IP core with the BRAM, sent data to the BRAM through the AXI interface, and then read the data out, and verified the output results. TX_DONE indicates that the transmission is successful, if the wrong result is obtained, the erro_flag will be pulled high. We package this design into a .v file, the specific operation is as follows.
11. In this way, we get the top-level file of the test system, and then we write test incentives for it, and simulate and verify it.
module tb_design_1();
reg KEY;
reg SCLK;
reg SRST_N;
wire TX_done;
wire error_flag;
wire rsta_busy_0;
design_1 design_1(
.KEY(KEY),
.SCLK(SCLK),
.SRST_N(SRST_N),
.TX_done(TX_done),
.error_flag(error_flag),
.rsta_busy_0(rsta_busy_0)
);
always #10 SCLK = ~SCLK;
initial begin
KEY = 0;
SCLK = 0;
SRST_N = 0;
#201;
SRST_N = 1;
#100;
KEY = 1;
#200;
KEY = 0;
#200000;
$stop;
end
endmodule
12. As shown in the figure, the first half is for writing data, and the second half is for reading data. Finally, you can see that the transmission completion signal TX_DONE signal is pulled high, but erro_flag is not pulled high. We can also observe the data and get this result is correct.
13. In this way, we verified the correctness of the function through the AXI4 IP verification of BRAM reading and writing. If you are interested, you can watch the video of the punctual atom ZYNQ series, which talks about the experiment of using this IP core to read and write DDR3 on the PS side.
In the follow-up, I will summarize some knowledge about DDR3 and control DDR3 through MIG, and realize the experiment that the AXI4 IP core reads and writes DDR3.