0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

AXI實(shí)戰(zhàn)(二)-AXI-Lite的Slave實(shí)現(xiàn)介紹

冬至子 ? 來源:小何的芯像石頭 ? 作者:五線譜是偶然來的 ? 2023-06-27 10:12 ? 次閱讀

概要-PG導(dǎo)讀

在PG142中,可以從 Overview中看到其實(shí)現(xiàn)的框圖:

圖片

可以看到,在AXI到UART中,是通過寄存器和FIFO進(jìn)行中介的。因?yàn)閺腁XI總線往里看,其控制的是就是地址上所映射的寄存器??梢钥吹皆谶@個(gè)IP中包含以下幾部分:

  1. AXI總線:實(shí)現(xiàn)總線握手和指定讀寫操作
  2. UART Lite 寄存器:
    • 狀態(tài)寄存器(STAT_REG)
    • 控制寄存器(CTRL_REG)
    • 接收數(shù)據(jù)FIFO(Receive Data FIFO)
    • 發(fā)送數(shù)據(jù)FIFO(Transmit Data FIFO)
  3. 串口控制模塊:
    • 發(fā)送控制
    • 接收控制
    • 中斷控制

所以本文所需要實(shí)現(xiàn)的東西也非常簡單,主要包括一個(gè)能與FIFO交互的串口模塊,AXI的總線控制,以及一些寄存器的設(shè)置和終端控制就可以了。

為了仿真期間可以更好地驗(yàn)證,所以我們還是可以加入一個(gè)串口回傳模塊進(jìn)行檢驗(yàn),所以所實(shí)現(xiàn)的框圖如下:

圖片

其中,Device在仿真的TB中加入。實(shí)際上也不需要加,直接將tx線連到rx就可以了。

然后通過IP框圖看看如何其頂層有什么可定義的功能:

IP框圖查看

圖片

可以看到,在此IP中需要定義AXI總線的時(shí)鐘頻率,波特率,數(shù)據(jù)位以及奇偶檢驗(yàn)位。實(shí)現(xiàn)過串口的朋友應(yīng)該知道,這里需要定義AXI的時(shí)鐘和波特率顯然是為了確定波特率時(shí)鐘的計(jì)數(shù)值。而數(shù)據(jù)位與奇偶校驗(yàn)位是為了確定并串轉(zhuǎn)換以及最后滑動(dòng)寄存器長度所需要。這些我們后面的代碼介紹都會講到。

需要注意的是,這里的AXI CLK Frequency是有上限的,詳見PG142-Ch2 Performance節(jié),有興趣的朋友也可以在實(shí)現(xiàn)完之后加點(diǎn)約束看看本文所實(shí)現(xiàn)的模塊能跑多快~

地址偏移

此處對應(yīng)PG142-CH2 Register Space節(jié)。這里需要為上面所說到的配置寄存器,狀態(tài)寄存器和讀寫FIFO安排地址。而在IP中設(shè)計(jì)的是相對地址:

圖片

因?yàn)榭偩€位寬是32,AXI按字節(jié)序排地址,所以地址偏移每個(gè)寄存器+4。

讀FIFO

讀FIFO的寄存器定義為:

圖片

在IP中,讀FIFO默認(rèn)深度為16,當(dāng)Master對空的讀FIFO發(fā)起讀的時(shí)候需要返回總線錯(cuò)誤(SLVERR),而且Master對讀FIFO發(fā)起寫事務(wù)將無效。其復(fù)位值為:

圖片

寫FIFO

寫FIFO的寄存器定義為:

圖片

同樣的,寫FIFO的默認(rèn)深度也是16.當(dāng)Master對滿的寫FIFO寫數(shù)據(jù)的時(shí)候需要返回總線錯(cuò)誤(SLVERR),而且Master對寫FIFO發(fā)起讀事務(wù)將返回0。其復(fù)位值為:

圖片

控制寄存器 Control Register (CTRL_REG)

控制寄存器的定義為:

圖片

控制寄存器包含中斷使能位和讀FIFO和寫FIFO的復(fù)位控制。這是一個(gè)只寫寄存器。向控制寄存器發(fā)出讀請求會返回0。每一位的定義為:

圖片

這里需要注意,中斷使能位復(fù)位值為0,所以后續(xù)的中斷處理我們需要先將中斷打開。

狀態(tài)寄存器Status Register (STAT_REG)

狀態(tài)寄存器的定義為:

圖片

狀態(tài)寄存器中包含了讀寫FIFO的空滿狀態(tài),中斷使能位與三個(gè)錯(cuò)誤位:

  1. Parity Error:奇偶校驗(yàn)錯(cuò)誤位,未指定時(shí)為常0
  2. Frame Error :幀錯(cuò)誤,停止位為0
  3. Overrun Error :過載錯(cuò)誤,收FIFO滿的情況下仍然接收到新的數(shù)據(jù)

錯(cuò)誤信息會一直保持到下一次讀狀態(tài)寄存器后歸0。需要注意的是,最后一位并不是讀FIFO的空信號,而是其”非空“信號。

中斷控制

這是一直貫穿在前面的中斷線管理,其作用在文檔中沒有單獨(dú)列出。但可見于Overview中的 Interrupt Control :當(dāng)中斷被使能時(shí),讀FIFO的非空信號以及發(fā)FIFO的空信號將以上升沿的方式給出中斷。

串口模塊實(shí)現(xiàn)

這一部分了解串口模塊的RTL實(shí)現(xiàn)的朋友可以跳過。在這個(gè)模塊中將實(shí)現(xiàn)串口與FIFO之間的控制邏輯以及錯(cuò)誤信號的生成。

串口發(fā)送模塊

串口發(fā)送模塊中采用狀態(tài)機(jī)+波特率時(shí)鐘計(jì)數(shù)編寫

波特率時(shí)鐘

首先根據(jù)系統(tǒng)時(shí)鐘和波特率計(jì)算出計(jì)算周期和定時(shí)器位寬,并定義出一個(gè)波特率周期和半波特率周期:

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);

然后根據(jù)計(jì)數(shù)值標(biāo)志位Baud_A_period可以寫出計(jì)數(shù)器:

// 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

最后因?yàn)殚_始位和結(jié)束位的位寬都是1, 所以針對發(fā)送的數(shù)據(jù)需要做數(shù)據(jù)位寬個(gè)計(jì)數(shù)器cnt_bit,由于奇偶檢驗(yàn)位的存在,這里還需要計(jì)算數(shù)據(jù)位寬(這里奇偶檢驗(yàn)位定義與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

發(fā)送狀態(tài)機(jī)

狀態(tài)機(jī)中包括:開始位,發(fā)送,以及結(jié)束位

// 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

第一段,狀態(tài)轉(zhuǎn)移:

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

第二段,狀態(tài)轉(zhuǎn)移條件:

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

第三段,狀態(tài)輸出:

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

輸出數(shù)據(jù)準(zhǔn)備

首先是計(jì)算奇偶檢驗(yàn)位(如果存在的話):

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

然后準(zhǔn)備發(fā)送的數(shù)據(jù),這里由于為了圖方便,FIFO用了FWFT,所以時(shí)序?qū)R比較簡單,這里會根據(jù)奇偶檢驗(yàn)位動(dòng)態(tài)生成:

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

這里的數(shù)據(jù)線序需要理一下,因?yàn)榘l(fā)送按tx_reg <= _send_data[cn\\t_bit];輸出,所以_send_data的最高位是奇偶校驗(yàn)位最晚輸出。

串口接收模塊

這里與發(fā)送模塊十分近似,僅列出錯(cuò)誤計(jì)算部分:

奇偶檢驗(yàn)出錯(cuò)

這里只需要采用連異或就可以了:

// 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錯(cuò)誤

對應(yīng)頂層的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的同時(shí)讀FIFO為滿即為overrun。

控制寄存器,狀態(tài)寄存器與中斷的實(shí)現(xiàn)

在完成串口模塊后,此時(shí)引入AXI中的寄存器定義,完成對FIFO的控制和狀態(tài)回傳。

對讀寫不同的寄存器,在這里引進(jìn)了一個(gè)refresh信號指示當(dāng)前什么寄存器被讀或?qū)懥?,有?/p>

input    [1:0] refresh, //refresh = {stat_refresh, ctrl_refresh}

控制寄存器

重看其定義如下:

圖片

配置寄存器非常簡單,只需要配合refresh進(jìn)行使能中斷和FIFO復(fù)位即可,如:

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

狀態(tài)寄存器

重看其定義如下:

圖片

頭三個(gè)是錯(cuò)誤位,后面指示的是FIFO的空滿狀態(tài),由于錯(cuò)誤狀態(tài)一旦被讀就會刷新,則可以定義:

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};

中斷的實(shí)現(xiàn)

在中斷的設(shè)計(jì)中,需要在讀FIFO的非空信號以及發(fā)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總線的實(shí)現(xiàn)

在經(jīng)過以上的抽象后,我們僅需在AXI-Lite上放出這兩個(gè)寄存器和兩個(gè)FIFO就可以了,同時(shí)注意有部分特殊情況需要返回總線錯(cuò)誤。為了方便銜接,在本文中絕大多數(shù)實(shí)現(xiàn)方式與Xilinx實(shí)現(xiàn)方式是一致的,也方便后續(xù)在提到優(yōu)化的時(shí)候來指出Xilinx的代碼問題。

定義地址偏移量

這里的定義在后續(xù)的文章中可能會不一樣,但是整體是一致的:

localparam ADDRLSB = $clog2(C_AXI_DATA_WIDTH/8); // 字節(jié)序再取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 是經(jīng)過ADDRLSB的計(jì)算移位的,在32位的情況下,ADDRLSB=2,所以當(dāng)0123左移四位便得到了048C。

By the way, 這個(gè)ADDRLSB的寫法糾正了Xilinx之前的求法:

localparam integer ADDR_LSB = (C_S_AXI_DATA_WIDTH/32)+1;

Xilinx的這種寫法在位寬大于32的時(shí)候是跟上面的寫法對等的,但是顯然上面的寫法更加適合理解并適用于低位寬的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;  //  協(xié)議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 // 寫回應(yīng)結(jié)束,模塊內(nèi)恢復(fù)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

這個(gè)axi_awaddr就是前面所說過的偏移量,直接截掉低兩位后048C將變?yōu)?123。

Write(W)通道

在寫通道中,不像是例程中簡單,需要根據(jù)不同地址進(jìn)行數(shù)據(jù)寫入。在這個(gè)IP中,只有寫FIFO和控制寄存器被允許寫入。這里的數(shù)據(jù)寫入判斷和Xilinx例程也是類似的,但是會精簡了S_AXI_WSTRB的判斷,因?yàn)槲覀兊募拇嫫鞫贾挥械桶宋皇怯行У?

首先是握手:

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

然后是根據(jù)地址判斷寫入的信息,第一個(gè)是寫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

這里會同步有一個(gè)使能信號,如上文所述,到后面assign到refresh里面去了。

寫響應(yīng)(B)通道

這里需要注意,當(dāng)手冊上寫到,當(dāng)Master往滿的FIFO寫數(shù)據(jù)時(shí),需要報(bào)總線錯(cuò)誤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)通道

現(xiàn)在到了這個(gè)設(shè)計(jì)最難的一步了,就是假設(shè)我們的讀FIFO并不是一個(gè)FWFT的FIFO,那么其讀延遲會有兩個(gè)時(shí)鐘周期的延遲,所以這個(gè)時(shí)候需要先不拉高rvalid。所以這里小何的辦法是當(dāng)其讀FIFO的時(shí)候寄存讀使能,當(dāng)兩拍后檢測到其下降沿時(shí)再拉高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握手完就會拉低的邏輯,然后時(shí)讀使能的寄存:

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

最后是選擇數(shù)據(jù):

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轉(zhuǎn)UART的全過程就設(shè)計(jì)完畢了~

仿真

在仿真章節(jié)中用到了上一小節(jié)中所介紹的"完美"主機(jī),未知的朋友可以回看上一節(jié)的介紹,要是不管實(shí)現(xiàn)的話其實(shí)非常容易使用。如以下的使用步驟:

時(shí)鐘和復(fù)位

和一般TB寫法一致,此處略過

DUT例化連線,增加回環(huán)設(shè)備

在這里需要把我們設(shè)計(jì)的模塊連進(jìn)interface,并且將串口模塊做一個(gè)簡單的回傳:

也就是說,這里我們并不需要實(shí)現(xiàn)Device,直接把用線回傳就可以了

圖片

如上一節(jié)講的一樣,這里我們直接連線:

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)
  );

測試總線錯(cuò)誤

根據(jù)Spec要求,需要在發(fā)FIFO滿的時(shí)候再發(fā)送數(shù)據(jù)和當(dāng)讀FIFO為空時(shí)需要返回總線錯(cuò)誤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);

仿真后提示為:

圖片

可以看到成功觸發(fā)了錯(cuò)誤。

測試讀寫功能

首先第一步是打開中斷:

// 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);

此時(shí)我們可以監(jiān)聽中斷信號,然后去讀狀態(tài)寄存器,發(fā)現(xiàn)讀FIFO非空就可以讀數(shù)據(jù)出來了,所以會一直讀狀態(tài)寄存器。小何在這里把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

圖片

數(shù)據(jù)也沒有什么大問題

查看波形

作為老仿真人,還是從波形來看看AXI的握手情況:

SystemVerilog下的仿真掉波形非常簡單,只要點(diǎn)interface類型,然后直接ctrl+W就可以了:

圖片

仿真IP

對相同的IP,在這里我們也做一次一樣的仿真,防止仿真平臺自己出錯(cuò):

圖片

得到了相同的效果,此時(shí)查看波形:

圖片

可以看到讀通道有一段是未知態(tài),但是其中沒有握手就沒事情了。大體的握手波形是一致的。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 定時(shí)器
    +關(guān)注

    關(guān)注

    23

    文章

    3218

    瀏覽量

    113665
  • AXI總線
    +關(guān)注

    關(guān)注

    0

    文章

    66

    瀏覽量

    14215
  • FIFO存儲
    +關(guān)注

    關(guān)注

    0

    文章

    103

    瀏覽量

    5944
  • UART接口
    +關(guān)注

    關(guān)注

    0

    文章

    124

    瀏覽量

    15199
  • 狀態(tài)寄存器
    +關(guān)注

    關(guān)注

    0

    文章

    38

    瀏覽量

    7049
收藏 人收藏

    評論

    相關(guān)推薦

    Zynq中AXI4-LiteAXI-Stream功能介紹

    Zynq中AXI4-Lite功能 AXI4-Lite接口是AXI4的子集,專用于和元器件內(nèi)的控制寄存器進(jìn)行通信。AXI-Lite允許構(gòu)建簡單的元件接口。這個(gè)接口規(guī)模較小,對設(shè)計(jì)和驗(yàn)證方
    的頭像 發(fā)表于 09-27 11:33 ?8614次閱讀
    Zynq中<b class='flag-5'>AXI4-Lite</b>和<b class='flag-5'>AXI</b>-Stream功能<b class='flag-5'>介紹</b>

    XADC和AXI4Lite接口:定制AXI引腳

    你好,我有一個(gè)關(guān)于XADC及其AXI4Lite接口輸入的問題。我想在Microzed 7020主板上測試XADC,在通過AXI4Lite接口將Zynq PL連接到XADC向?qū)В▍⒁姷谝粋€(gè)附件)之后
    發(fā)表于 11-01 16:07

    有沒有一種標(biāo)準(zhǔn)的方式到達(dá)PL AXI-Lite總線?

    嗨,我將通過測試驗(yàn)證這一點(diǎn),但我對AXI-Lite外設(shè)“寄存器寫入”如何出現(xiàn)在AXI-Lite總線上有疑問。AXI標(biāo)準(zhǔn)表明數(shù)據(jù)和地址可以非常相互獨(dú)立地出現(xiàn),從靈活性的角度來看這是很好的,但是
    發(fā)表于 04-12 13:45

    AXI_Lite總線使用方法

    提示:文章寫完后,目錄可以自動(dòng)生成,如何生成可參考右邊的幫助文檔目錄一、總覽、實(shí)戰(zhàn)效果1.PL 寫數(shù)據(jù)給PS效果2.PS寫數(shù)據(jù)給PL效果總結(jié)前言沒看過上一篇的去看一下上一章節(jié)對AXI_Lite
    發(fā)表于 01-10 08:00

    AMBA 4 AXI4、AXI4-LiteAXI4-流協(xié)議斷言用戶指南

    您可以將協(xié)議斷言與任何旨在實(shí)現(xiàn)AMBA?4 AXI4的接口一起使用?, AXI4 Lite?, 或AXI4流? 協(xié)議通過一系列斷言根據(jù)協(xié)議檢
    發(fā)表于 08-10 06:39

    ZYNQ通過AXI-Lite與PL交互-FPGA

    詳細(xì)介紹AXI總線
    發(fā)表于 02-28 21:03 ?1次下載

    AXI-4 Lite接口協(xié)議仿真波形解析

    AXI-4 Lite可以看作是AXI-4 Memory Mapped的子集,從下面的示例圖中就可見一斑。最直接的體現(xiàn)是AXI-4 Lite
    的頭像 發(fā)表于 09-23 11:18 ?3399次閱讀
    <b class='flag-5'>AXI</b>-4 <b class='flag-5'>Lite</b>接口協(xié)議仿真波形解析

    一文詳解ZYNQ中的DMA與AXI4總線

    在ZYNQ中,支持AXI-Lite,AXI4和AXI-Stream三種總線,但PS與PL之間的接口卻只支持前兩種,AXI-Stream只能在PL中實(shí)
    的頭像 發(fā)表于 09-24 09:50 ?4982次閱讀
    一文詳解ZYNQ中的DMA與<b class='flag-5'>AXI</b>4總線

    FPGA程序設(shè)計(jì):如何封裝AXI_SLAVE接口IP

    在FPGA程序設(shè)計(jì)的很多情形都會使用到AXI接口總線,以PCIe的XDMA應(yīng)用為例,XDMA有兩個(gè)AXI接口,分別是AXI4 Master類型接口和AXI-Lite Master類型接
    的頭像 發(fā)表于 10-30 12:32 ?4225次閱讀
    FPGA程序設(shè)計(jì):如何封裝<b class='flag-5'>AXI_SLAVE</b>接口IP

    PCIE通信技術(shù):通過AXI-Lite ip配置的VDMA使用

    XDMA是Xilinx封裝好的PCIE DMA傳輸IP,可以很方便的把PCIE總線上的數(shù)據(jù)傳輸事務(wù)映射到AXI總線上面,實(shí)現(xiàn)上位機(jī)直接對AXI總線進(jìn)行讀寫而對PCIE本身TLP的組包和解包無感。
    的頭像 發(fā)表于 12-28 10:17 ?3029次閱讀

    ZYNQ中DMA與AXI4總線

    和接口的構(gòu)架 在ZYNQ中,支持AXI-Lite,AXI4和AXI-Stream三種總線,但PS與PL之間的接口卻只支持前兩種,AXI-Stream只能在PL中
    的頭像 發(fā)表于 11-02 11:27 ?4183次閱讀
    ZYNQ中DMA與<b class='flag-5'>AXI</b>4總線

    AXI 總線交互分為 Master / Slave 兩端

    在 AMBA 系列之 AXI 總線協(xié)議初探 中,了解到 AXI 總線交互分為 Master / Slave 兩端,而且標(biāo)準(zhǔn)的 AXI 總線支持不同的位寬,既然是總線,那么必須要支持總線
    的頭像 發(fā)表于 02-08 11:44 ?1.5w次閱讀

    AXI4 、 AXI4-Lite 、AXI4-Stream接口

    AXI4 是一種高性能memory-mapped總線,AXI4-Lite是一只簡單的、低通量的memory-mapped 總線,而 AXI4-Stream 可以傳輸高速數(shù)據(jù)流。從字面意思去理解
    的頭像 發(fā)表于 07-04 09:40 ?7815次閱讀

    自定義AXI-Lite接口的IP及源碼分析

    在 Vivado 中自定義 AXI4-Lite 接口的 IP,實(shí)現(xiàn)一個(gè)簡單的 LED 控制功能,并將其掛載到 AXI Interconnect 總線互聯(lián)結(jié)構(gòu)上,通過 ZYNQ 主機(jī)控制,后面對 Xilinx 提供的整個(gè)
    發(fā)表于 06-25 16:31 ?2856次閱讀
    自定義<b class='flag-5'>AXI-Lite</b>接口的IP及源碼分析

    AXI傳輸數(shù)據(jù)的過程

    AXI-Stream,其中AXI-LiteAXI-Full都是基于memory map的形式實(shí)現(xiàn)數(shù)據(jù)傳輸(即包括地址總線),而AXI-St
    的頭像 發(fā)表于 10-31 15:37 ?861次閱讀
    <b class='flag-5'>AXI</b>傳輸數(shù)據(jù)的過程