概要-PG導讀
在PG142中,可以從 Overview中看到其實現的框圖:
可以看到,在AXI到UART中,是通過寄存器和FIFO進行中介的。因為從AXI總線往里看,其控制的是就是地址上所映射的寄存器。可以看到在這個IP中包含以下幾部分:
- AXI總線:實現總線握手和指定讀寫操作
- UART Lite 寄存器:
- 狀態寄存器(STAT_REG)
- 控制寄存器(CTRL_REG)
- 接收數據FIFO(Receive Data FIFO)
- 發送數據FIFO(Transmit Data FIFO)
- 串口控制模塊:
- 發送控制
- 接收控制
- 中斷控制
所以本文所需要實現的東西也非常簡單,主要包括一個能與FIFO交互的串口模塊,AXI的總線控制,以及一些寄存器的設置和終端控制就可以了。
為了仿真期間可以更好地驗證,所以我們還是可以加入一個串口回傳模塊進行檢驗,所以所實現的框圖如下:
其中,Device在仿真的TB中加入。實際上也不需要加,直接將tx線連到rx就可以了。
然后通過IP框圖看看如何其頂層有什么可定義的功能:
IP框圖查看
可以看到,在此IP中需要定義AXI總線的時鐘頻率,波特率,數據位以及奇偶檢驗位。實現過串口的朋友應該知道,這里需要定義AXI的時鐘和波特率顯然是為了確定波特率時鐘的計數值。而數據位與奇偶校驗位是為了確定并串轉換以及最后滑動寄存器長度所需要。這些我們后面的代碼介紹都會講到。
需要注意的是,這里的AXI CLK Frequency是有上限的,詳見PG142-Ch2 Performance節,有興趣的朋友也可以在實現完之后加點約束看看本文所實現的模塊能跑多快~
地址偏移
此處對應PG142-CH2 Register Space節。這里需要為上面所說到的配置寄存器,狀態寄存器和讀寫FIFO安排地址。而在IP中設計的是相對地址:
因為總線位寬是32,AXI按字節序排地址,所以地址偏移每個寄存器+4。
讀FIFO
讀FIFO的寄存器定義為:
在IP中,讀FIFO默認深度為16,當Master對空的讀FIFO發起讀的時候需要返回總線錯誤(SLVERR),而且Master對讀FIFO發起寫事務將無效。其復位值為:
寫FIFO
寫FIFO的寄存器定義為:
同樣的,寫FIFO的默認深度也是16.當Master對滿的寫FIFO寫數據的時候需要返回總線錯誤(SLVERR),而且Master對寫FIFO發起讀事務將返回0。其復位值為:
控制寄存器 Control Register (CTRL_REG)
控制寄存器的定義為:
控制寄存器包含中斷使能位和讀FIFO和寫FIFO的復位控制。這是一個只寫寄存器。向控制寄存器發出讀請求會返回0。每一位的定義為:
這里需要注意,中斷使能位復位值為0,所以后續的中斷處理我們需要先將中斷打開。
狀態寄存器Status Register (STAT_REG)
狀態寄存器的定義為:
狀態寄存器中包含了讀寫FIFO的空滿狀態,中斷使能位與三個錯誤位:
- Parity Error:奇偶校驗錯誤位,未指定時為常0
- Frame Error :幀錯誤,停止位為0
- Overrun Error :過載錯誤,收FIFO滿的情況下仍然接收到新的數據
錯誤信息會一直保持到下一次讀狀態寄存器后歸0。需要注意的是,最后一位并不是讀FIFO的空信號,而是其”非空“信號。
中斷控制
這是一直貫穿在前面的中斷線管理,其作用在文檔中沒有單獨列出。但可見于Overview中的 Interrupt Control :當中斷被使能時,讀FIFO的非空信號以及發FIFO的空信號將以上升沿的方式給出中斷。
串口模塊實現
這一部分了解串口模塊的RTL實現的朋友可以跳過。在這個模塊中將實現串口與FIFO之間的控制邏輯以及錯誤信號的生成。
串口發送模塊
串口發送模塊中采用狀態機+波特率時鐘計數編寫
波特率時鐘
首先根據系統時鐘和波特率計算出計算周期和定時器位寬,并定義出一個波特率周期和半波特率周期:
parameter CLK_FREQ = 100, // Mhz
parameter BAUD_RATE = 9600,
······
localparam Num_cycle = CLK_FREQ*1_000_000/BAUD_RATE; // Mhz
localparam CNT_DW = $clog2(Num_cycle);
reg [CNT_DW-1:0] cnt_baud;
wire Baud_A_period = (cnt_baud == Num_cycle-1);
wire Baud_Half_period = (cnt_baud == Num_cycle/2 -1);
然后根據計數值標志位Baud_A_period可以寫出計數器:
// baud cycle counter
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i)
cnt_baud <= {CNT_DW{1'b0}};
else if(state!= next_state || Baud_A_period)
cnt_baud <= {CNT_DW{1'b0}};
else
cnt_baud <= cnt_baud + 1'b1;
end
最后因為開始位和結束位的位寬都是1, 所以針對發送的數據需要做數據位寬個計數器cnt_bit,由于奇偶檢驗位的存在,這里還需要計算數據位寬(這里奇偶檢驗位定義與Xilinx保持一致):
parameter DW = 8,
parameter PARITY = 2'b10 // 00:no,2'b01:odd, 2'b10:even, 2'b11error
······
localparam Num_bit = DW + ^PARITY;
reg [3:0] cnt_bit;
wire Send_done = (cnt_bit == Num_bit -1);
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i)
cnt_bit <= 4'b0;
else if(state == S_SEND) begin
if(Baud_A_period)
cnt_bit <= cnt_bit + 1'b1;
else
cnt_bit <= cnt_bit;
end
else
cnt_bit <= 4'b0;
end
發送狀態機
狀態機中包括:開始位,發送,以及結束位
// FSM encoding
localparam S_IDLE = 2'b00;
localparam S_START = 2'b01; // start bit rec
localparam S_SEND = 2'b10; // data bits
localparam S_STOP = 2'b11; // stop bit
第一段,狀態轉移:
reg [1:0] state;
reg [1:0] next_state;
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i)
state <= S_IDLE;
else
state <= next_state;
end
第二段,狀態轉移條件:
wire Send_done = (cnt_bit == Num_bit -1);
always @(*) begin
case (state)
S_IDLE :
next_state = (!tx_fifo_empty) ? S_START : S_IDLE;
S_START :
next_state = Baud_A_period ? S_SEND : S_START;
S_SEND :
next_state = (Baud_A_period && Send_done) ? S_STOP : S_SEND;
S_STOP :
next_state = Baud_A_period ? S_IDLE : S_STOP;
default:
next_state = S_IDLE;
endcase
end
第三段,狀態輸出:
reg tx_reg;
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i)
tx_reg <= 1'b1;
else
case(state)
S_IDLE,S_STOP:
tx_reg <= 1'b1;
S_START:
tx_reg <= 1'b0;
S_SEND:
tx_reg <= _send_data[cnt_bit];
default:
tx_reg <= 1'b1;
endcase
輸出數據準備
首先是計算奇偶檢驗位(如果存在的話):
reg parity_cal;
always @(posedge clk_i) begin
if(!rst_n_i)
parity_cal <= 1'b0;
else if(state==S_START)
case (PARITY)
2'b00,2'b11:parity_cal <= 1'b0;
2'b01:
parity_cal <= ^tx_fifo_do ? 1'b0 : 1'b1;
2'b10:
parity_cal <= ^tx_fifo_do ? 1'b1 : 1'b0;
endcase
else
parity_cal <= parity_cal;
end
然后準備發送的數據,這里由于為了圖方便,FIFO用了FWFT,所以時序對齊比較簡單,這里會根據奇偶檢驗位動態生成:
reg [Num_bit-1:0] _send_data;
wire [Num_bit-1:0] _send_data_t;
generate
if(^PARITY == 1'b1)
assign _send_data_t = {parity_cal, tx_fifo_do};
else
assign _send_data_t = tx_fifo_do;
endgenerate
always @(posedge clk_i) begin
if(!rst_n_i)
_send_data < = {Num_bit{1'b0}};
else if(state == S_START)
_send_data <= _send_data_t;
else
_send_data <= _send_data;
end
這里的數據線序需要理一下,因為發送按tx_reg <= _send_data[cn\\t_bit];輸出,所以_send_data的最高位是奇偶校驗位最晚輸出。
串口接收模塊
這里與發送模塊十分近似,僅列出錯誤計算部分:
奇偶檢驗出錯
這里只需要采用連異或就可以了:
// parity_err check
reg parity_err_cal;
always @(*) begin
if(state==S_STOP&&Baud_Half_period)
case (PARITY)
2'b01: // odd
parity_err_cal = (^_rec_data == 1'b1) ? 1'b0:1'b1; // '1' occur odd - > right
2'b10: // even
parity_err_cal = (^_rec_data == 1'b0) ? 1'b0:1'b1; // '1' occur even - > right
default:
parity_err_cal = 1'b0;
endcase
else
parity_err_cal = 1'b0;
end
reg parity_err; // output
always @(posedge clk_i) begin
if(!rst_n_i)
parity_err <= 1'b0;
else
parity_err <= parity_err_cal;
end
停止位為0錯誤
對應頂層的Frame error,判斷也非常簡單:
// - > parity_err
always @(posedge clk_i) begin
if(!rst_n_i)
parity_err <= 1'b0;
else
parity_err <= parity_err_cal;
end
reg stop_0_err; // output
always @(posedge clk_i) begin
if(!rst_n_i)
stop_0_err <= 1'b0;
else
stop_0_err <= stop_0_err_cal;
end
至于overrun error直接判斷讀FIFO就好了,在寫讀FIFO的同時讀FIFO為滿即為overrun。
控制寄存器,狀態寄存器與中斷的實現
在完成串口模塊后,此時引入AXI中的寄存器定義,完成對FIFO的控制和狀態回傳。
對讀寫不同的寄存器,在這里引進了一個refresh信號指示當前什么寄存器被讀或寫了,有:
input [1:0] refresh, //refresh = {stat_refresh, ctrl_refresh}
控制寄存器
重看其定義如下:
配置寄存器非常簡單,只需要配合refresh進行使能中斷和FIFO復位即可,如:
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i)
tx_fifo_rst <= 1'b0;
else if(refresh[0])
tx_fifo_rst <= ctrl_reg[0];
else
tx_fifo_rst <= 1'b0;
end
狀態寄存器
重看其定義如下:
頭三個是錯誤位,后面指示的是FIFO的空滿狀態,由于錯誤狀態一旦被讀就會刷新,則可以定義:
reg [7:0] _stat_r;
reg [7:0] _stat_w;
wire [2:0] _err_r = {parity_err,stop_0_err, rx_fifo_wr&&rx_fifo_full};
always @(*) begin
if(refresh[1])
_stat_w = {3'b0, interrupt_en,
tx_fifo_full, tx_fifo_empty,rx_fifo_full,~rx_fifo_empty};
else
_stat_w = {_err_r|_stat_r[7-:3], interrupt_en,
tx_fifo_full, tx_fifo_empty,rx_fifo_full,~rx_fifo_empty};
end
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i)
_stat_r <= 8'b0000_0100;
else
_stat_r <= _stat_w;
end
assign stat_reg = {24'd0, _stat_r};
中斷的實現
在中斷的設計中,需要在讀FIFO的非空信號以及發FIFO的空信號將以上升沿的方式給出中斷,所以控制為:
reg _uart_interrupt;
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i)
_uart_interrupt <= 1'b0;
else if(interrupt_en && ~_uart_interrupt ) begin
if(tx_fifo_empty| ~rx_fifo_empty)
_uart_interrupt <= 1'b1;
else
_uart_interrupt <= 1'b0;
end
else
_uart_interrupt <= 1'b0;
end
AXI-Lite總線的實現
在經過以上的抽象后,我們僅需在AXI-Lite上放出這兩個寄存器和兩個FIFO就可以了,同時注意有部分特殊情況需要返回總線錯誤。為了方便銜接,在本文中絕大多數實現方式與Xilinx實現方式是一致的,也方便后續在提到優化的時候來指出Xilinx的代碼問題。
定義地址偏移量
這里的定義在后續的文章中可能會不一樣,但是整體是一致的:
localparam ADDRLSB = $clog2(C_AXI_DATA_WIDTH/8); // 字節序再取log - > 偏移位寬
// address mapping
localparam [C_AXI_ADDR_WIDTH-1 -ADDRLSB:0] UART_RX_FIFO = 'd0,
UART_TX_FIFO = 'd1,
UART_STA_REG = 'd2,
UART_CTR_REG = 'd3;
reg [C_AXI_ADDR_WIDTH-1 -ADDRLSB:0] axi_awaddr;
reg [C_AXI_ADDR_WIDTH-1 -ADDRLSB:0] axi_araddr;
需要注意這里的地址定義的是0->1->2->3 是經過ADDRLSB的計算移位的,在32位的情況下,ADDRLSB=2,所以當0123左移四位便得到了048C。
By the way, 這個ADDRLSB的寫法糾正了Xilinx之前的求法:
localparam integer ADDR_LSB = (C_S_AXI_DATA_WIDTH/32)+1;
Xilinx的這種寫法在位寬大于32的時候是跟上面的寫法對等的,但是顯然上面的寫法更加適合理解并適用于低位寬的AXI總線。
AddrWrite(AW)通道
此處除了寫入地址外,基本就是Xilinx的示例代碼
// --------------------------- AW channel -----------------------------------
reg aw_en;
always @( posedge S_AXI_ACLK )begin
if ( S_AXI_ARESETN == 1'b0 ) begin
axi_awready <= 1'b0; // 協議ready
aw_en <= 1'b1; // 模塊ready
end
else begin
if (~axi_awready && S_AXI_AWVALID && S_AXI_WVALID && aw_en) begin
axi_awready <= 1'b1;
aw_en <= 1'b0;
end
else if (S_AXI_BREADY && axi_bvalid) begin // 寫回應結束,模塊內恢復ready
axi_awready <= 1'b0;
aw_en <= 1'b1;
end
else begin
axi_awready <= 1'b0;
end
end
end
// latch aw addr
always @( posedge S_AXI_ACLK )begin
if ( S_AXI_ARESETN == 1'b0 )begin
axi_awaddr <= 2'b00;
end
else begin
if (~axi_awready && S_AXI_AWVALID && S_AXI_WVALID && aw_en) begin
// Write Address latching
axi_awaddr <= S_AXI_AWADDR[C_AXI_ADDR_WIDTH-1: ADDRLSB];
end
end
end
這個axi_awaddr就是前面所說過的偏移量,直接截掉低兩位后048C將變為0123。
Write(W)通道
在寫通道中,不像是例程中簡單,需要根據不同地址進行數據寫入。在這個IP中,只有寫FIFO和控制寄存器被允許寫入。這里的數據寫入判斷和Xilinx例程也是類似的,但是會精簡了S_AXI_WSTRB的判斷,因為我們的寄存器都只有低八位是有效的:
首先是握手:
always @( posedge S_AXI_ACLK ) begin
if ( S_AXI_ARESETN == 1'b0 )
axi_wready <= 1'b0;
else if (~axi_wready && S_AXI_WVALID && S_AXI_AWVALID && aw_en )
axi_wready <= 1'b1;
else
axi_wready <= 1'b0;
end
然后是根據地址判斷寫入的信息,第一個是寫FIFO:
reg _tx_fifo_wr;
reg [31:0] _tx_fifo_di;
always @( posedge S_AXI_ACLK ) begin
if ( S_AXI_ARESETN == 1'b0 ) begin
_tx_fifo_di <= 32'd0;
_tx_fifo_wr <= 1'b0;
end
else if (axi_wready && axi_awaddr==UART_TX_FIFO &&
S_AXI_AWVALID && (S_AXI_WSTRB[0] == 1'b1)) begin
_tx_fifo_wr <= ~tx_fifo_full;
_tx_fifo_di <= S_AXI_WDATA;
end
else begin
_tx_fifo_wr <= 1'b0;
_tx_fifo_di <= _tx_fifo_di;
end
end
最后是控制寄存器
reg [31:0] _ctrl_reg;
always @( posedge S_AXI_ACLK ) begin
if ( S_AXI_ARESETN == 1'b0 ) begin
_ctrl_reg <= 32'd0;
ctrl_refresh <= 1'b0;
end
else if (axi_wready && axi_awaddr==UART_CTR_REG &&
S_AXI_AWVALID && (S_AXI_WSTRB[0] == 1'b1)) begin
_ctrl_reg <= S_AXI_WDATA;
ctrl_refresh <= 1'b1;
end
else begin
ctrl_refresh <= 1'b0;
_ctrl_reg <= _ctrl_reg;
end
end
這里會同步有一個使能信號,如上文所述,到后面assign到refresh里面去了。
寫響應(B)通道
這里需要注意,當手冊上寫到,當Master往滿的FIFO寫數據時,需要報總線錯誤RESP_SLVERR,所以有:
// --------------------------- B channel -----------------------------------
always @( posedge S_AXI_ACLK ) begin
if ( S_AXI_ARESETN == 1'b0 ) begin
axi_bvalid <= 0;
axi_bresp <= 0;
end
else begin
if (axi_awready && S_AXI_AWVALID && ~axi_bvalid
&& axi_wready && S_AXI_WVALID) begin
axi_bvalid <= 1'b1;
axi_bresp <= (axi_awaddr==UART_TX_FIFO && tx_fifo_full) ? 2'b10 : 2'b00;
end
else if (axi_bvalid && S_AXI_BREADY) begin
// Read data is accepted by the master
axi_bvalid <= 1'b0;
end
end
end
AddrRead(AR)通道
在AR通道上和AW是一樣的
// --------------------------- AR channel -----------------------------------
always @( posedge S_AXI_ACLK ) begin
if ( S_AXI_ARESETN == 1'b0 ) begin
axi_arready <= 1'b0;
axi_araddr <= 2'b0;
end
else begin
if (~axi_arready && S_AXI_ARVALID) begin
// indicates that the slave has acceped the valid read address
axi_arready <= 1'b1;
// Read address latching
axi_araddr <= S_AXI_ARADDR[C_AXI_ADDR_WIDTH-1: ADDRLSB];
end
else begin
axi_arready <= 1'b0;
end
end
end
Read(R)通道
現在到了這個設計最難的一步了,就是假設我們的讀FIFO并不是一個FWFT的FIFO,那么其讀延遲會有兩個時鐘周期的延遲,所以這個時候需要先不拉高rvalid。所以這里小何的辦法是當其讀FIFO的時候寄存讀使能,當兩拍后檢測到其下降沿時再拉高rvalid,代碼如下:
// --------------------------- R channel -----------------------------------
reg _rx_fifo_rd,_rx_fifo_rd0,_rx_fifo_rd1;
always @( posedge S_AXI_ACLK ) begin
if ( S_AXI_ARESETN == 1'b0 ) begin
axi_rvalid <= 0;
axi_rresp <= 0;
_rx_fifo_rd <= 1'b0;
stat_refresh <= 1'b0;
end
else begin
stat_refresh <= 1'b0;
_rx_fifo_rd <= 1'b0;
if (axi_arready && S_AXI_ARVALID && ~axi_rvalid) begin
// Valid read data is available at the read data bus
axi_rvalid <= 1'b1;
if(axi_araddr==UART_RX_FIFO && ~rx_fifo_empty) begin // 2 clock latency
axi_rvalid <= 1'b0;
_rx_fifo_rd <=1'b1;
end
else if(axi_araddr==UART_STA_REG) // read register
stat_refresh <= 1'b1;
//When a read request to empty FIFO, a bus error (SLVERR) is generated
axi_rresp <= (axi_araddr==UART_RX_FIFO && rx_fifo_empty) ? 2'b10 : 2'b00;
end
else if({_rx_fifo_rd1,_rx_fifo_rd0} == 2'b10)
axi_rvalid <= 1'b1;
else if (axi_rvalid && S_AXI_RREADY)
// Read data is accepted by the master
axi_rvalid <= 1'b0;
else begin
axi_rvalid <=axi_rvalid;
axi_rresp <= axi_rresp;
end
end
end
這里利用了axi_arready握手完就會拉低的邏輯,然后時讀使能的寄存:
always @( posedge S_AXI_ACLK ) begin
if ( S_AXI_ARESETN == 1'b0 )
{_rx_fifo_rd0,_rx_fifo_rd1} <= 2'b00;
else
{_rx_fifo_rd1,_rx_fifo_rd0} <= {_rx_fifo_rd0,_rx_fifo_rd};
end
最后是選擇數據:
always @( posedge S_AXI_ACLK ) begin
if (!S_AXI_RVALID || S_AXI_RREADY) begin
casez (axi_araddr)
UART_RX_FIFO: axi_rdata <= rx_fifo_do;
UART_TX_FIFO: axi_rdata <= 32'd0;
UART_STA_REG: axi_rdata <= stat_reg;
UART_CTR_REG: axi_rdata <= 32'd0;
endcase
end
end
至此,AXI-Lite轉UART的全過程就設計完畢了~
仿真
在仿真章節中用到了上一小節中所介紹的"完美"主機,未知的朋友可以回看上一節的介紹,要是不管實現的話其實非常容易使用。如以下的使用步驟:
時鐘和復位
和一般TB寫法一致,此處略過
DUT例化連線,增加回環設備
在這里需要把我們設計的模塊連進interface,并且將串口模塊做一個簡單的回傳:
也就是說,這里我們并不需要實現Device,直接把用線回傳就可以了
如上一節講的一樣,這里我們直接連線:
module tb_axi_lite_uart #(
parameter DW = 8,
parameter CLK_FREQ = 100,
parameter BAUD_RATE = 115200,
parameter PARITY = 2'b10, // 00 - > no parity - > 2'b01:odd - > 2'b10:even - > 2'b11error
......
Axil_uart_top #(
.C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH ),
.C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH ),
.DW(DW ),
.CLK_FREQ(CLK_FREQ ),
.BAUD_RATE(BAUD_RATE ),.PARITY ( PARITY ))
Axil_uart_top_dut (
.S_AXI_ACLK (clk ),
.S_AXI_ARESETN (rst_n ),
.S_AXI_AWVALID (master.aw_valid ),
.S_AXI_AWREADY (master.aw_ready ),
.S_AXI_AWADDR (master.aw_addr ),
.S_AXI_AWPROT (master.aw_prot ),
.S_AXI_WVALID (master.w_valid ),
.S_AXI_WREADY (master.w_ready),
.S_AXI_WDATA (master.w_data ),
.S_AXI_WSTRB (master.w_strb ),
.S_AXI_BVALID (master.b_valid ),
.S_AXI_BREADY (master.b_ready ),
.S_AXI_BRESP (master.b_resp ),
.S_AXI_ARVALID (master.ar_valid ),
.S_AXI_ARREADY (master.ar_ready ),
.S_AXI_ARADDR (master.ar_addr ),
.S_AXI_ARPROT (master.ar_prot ),
.S_AXI_RVALID (master.r_valid),
.S_AXI_RREADY (master.r_ready ),
.S_AXI_RDATA ( master.r_data ),
.S_AXI_RRESP (master.r_resp ),
.uart_rx_phy (uart_tx_phy ),
.uart_tx_phy (uart_tx_phy ),
.uart_interrupt(uart_interrupt)
);
測試總線錯誤
根據Spec要求,需要在發FIFO滿的時候再發送數據和當讀FIFO為空時需要返回總線錯誤RESP_SLVERR,所以我們會有一下測試方法:
// testing RESP_SLVERR
lite_axi_master.read(axi_addr_t'(32'h0000_0000), axi_pkg::prot_t'('0), stat_data, resp);
assert (resp == axi_pkg::RESP_SLVERR) else $fatal(1, "Fail to assert RESP_SLVERR");
$display("%0t > RESP_SLVERR Received stat reg : %h RESP: %h", $time(), stat_data, resp);
while(wrong_resp == axi_pkg::RESP_OKAY)
lite_axi_master.write(axi_addr_t'(32'h0000_0004), axi_pkg::prot_t'('0),
axi_data_t'(0), axi_strb_t'(4'hF), wrong_resp);
$display("%0t > RESP_SLVERR Send FIFO : FULL RESP: %h", $time(),wrong_resp);
lite_axi_master.write(axi_addr_t'(32'h0000_000C), axi_pkg::prot_t'('0),
axi_data_t'(32'h0000_0003), axi_strb_t'(4'hF), resp);
assert (resp == axi_pkg::RESP_OKAY) else $fatal(1, "Fail to Reset ALL FIFO");
$display("%0t > Reset ALL FIFO RESP: %h", $time(),resp);
仿真后提示為:
可以看到成功觸發了錯誤。
測試讀寫功能
首先第一步是打開中斷:
// Write to config register - > enable interrupt
lite_axi_master.write(axi_addr_t'(32'h0000_000C), axi_pkg::prot_t'('0),
axi_data_t'(32'h0000_0010), axi_strb_t'(4'hF), resp);
assert (resp == axi_pkg::RESP_OKAY) else $fatal(1, "Fail to enable interrupt");
$display("%0t > Success to enable interrupt, with addr", $time(), data, resp);
lite_axi_master.read(axi_addr_t'(32'h0000_0008), axi_pkg::prot_t'('0), stat_data, resp);
$display("%0t > Received stat reg : %h RESP: %h", $time(), stat_data, resp);
此時我們可以監聽中斷信號,然后去讀狀態寄存器,發現讀FIFO非空就可以讀數據出來了,所以會一直讀狀態寄存器。小何在這里把Lite Master的讀display給注釋掉了,代碼如下:
for(int i=0;i< 40;i++) begin
lite_axi_master.write(axi_addr_t'(32'h0000_0004), axi_pkg::prot_t'('0),
axi_data_t'(i), axi_strb_t'(4'hF), resp);
// axi_data_t'(32'h0000_0052), axi_strb_t'(4'hF), resp);
assert (resp == axi_pkg::RESP_OKAY) else $fatal(1, "Fail to send data");
$display("%0t > transmit data %h RESP:%h ", $time(), i, resp);
// Read from it.
@(posedge uart_interrupt);
lite_axi_master.read(axi_addr_t'(32'h0000_0008),
axi_pkg::prot_t'('0), stat_data, resp);
assert (resp == axi_pkg::RESP_OKAY) else $fatal(1, "Fail to recv stat_data.");
while(stat_data[0]!=1'b1) begin
lite_axi_master.read(axi_addr_t'(32'h0000_0008),
axi_pkg::prot_t'('0), stat_data, resp);
// $display("%0t > Received stat reg : %h RESP: %h", $time(), stat_data, resp);
end
lite_axi_master.read(axi_addr_t'(32'h0000_0000), axi_pkg::prot_t'('0), data, resp);
assert (resp == axi_pkg::RESP_OKAY) else $fatal(1, "Fail to recv data.");
// Checking of the expecetd read data is handled in `proc_check_read_data`.
$display("%0t > receive data %h RESP:%h ", $time(), data, resp);
end
然后看一下display
數據也沒有什么大問題
查看波形
作為老仿真人,還是從波形來看看AXI的握手情況:
SystemVerilog下的仿真掉波形非常簡單,只要點interface類型,然后直接ctrl+W就可以了:
仿真IP
對相同的IP,在這里我們也做一次一樣的仿真,防止仿真平臺自己出錯:
得到了相同的效果,此時查看波形:
可以看到讀通道有一段是未知態,但是其中沒有握手就沒事情了。大體的握手波形是一致的。
-
定時器
+關注
關注
23文章
3246瀏覽量
114719 -
AXI總線
+關注
關注
0文章
66瀏覽量
14261 -
FIFO存儲
+關注
關注
0文章
103瀏覽量
5968 -
UART接口
+關注
關注
0文章
124瀏覽量
15288 -
狀態寄存器
+關注
關注
0文章
39瀏覽量
7081
發布評論請先 登錄
相關推薦
評論