본문 바로가기
Digital Design/SoC

[AMBA] AXI Protocol 설계(Verilog) - ④ Simple AXI Slave 정리

by 스테고사우르스 2023. 2. 26.

들어가기 앞서...

 

"  AXI는 말 그대로 Interface이기 때문에 실체라고 할게 딱히 없습니다.

 

Master와 Slave를 연결하는 Bus의 규격을 ARM에서 정해놓은 것인데요,

저와 같은 학부생들은 Master와 Slave를 구하기 어렵습니다.

 

BFM(Bus Functional Model)을 짜서 Bus의 동작을 시뮬레이션해도 되지만

그건 조금 어려워서 아직 공부 중 입니다.

그래서 제멋대로 AXI Slave를 Verilog로 짜보겠습니다.

목적은 오로지 AMBA를 복습하고 기능을 직접 확인해보기 위함입니다!  "

 

조금 더 공부한 후에 정확한 코드를 올려보겠습니다.

 


 

앞서 3개의 게시물을 통해 AXI Protocol에 대해 알아보았습니다.

Signal Description: https://metastable.tistory.com/44

Write Transaction: https://metastable.tistory.com/45

Read Transaction: https://metastable.tistory.com/46

 

이번 게시물에서는 코드를 잘 정리해서 Simple AXI Slave로 만들어보겠습니다.

 

다시 한 번 말씀드리지만 동작만 확인하기 위해 마음대로 짠 코드이니 복사해가는 걸 권장드리진 않습니다..!

여러분도 직접 짜본 후에,

직접 짠 코드와 git에서 받은 코드의 시뮬레이션을 비교해 보시는걸 추천드립니다.

 

Slave는 AXI Protocol의 동작을 시뮬레이션으로 확인해보기 위해 임의로 만든 것이기 때문에

아무 기능을 하지 않는다는 점 참고해주세요.

물론 합성 불가능합니다.

 

Master는 따로 만들지 않았고 Testbench에서 직접 넣어주었습니다.

나중에 Master를 만들게 되면 그때 다시 공유하겠습니다.

 


Verilog Code

DUT

`timescale 1ns / 1ps

module axi_slave(
    input i_clk,
    input i_resetn,
    
    /*************    Write Address Channel   ************/
    input                   [3:0]           i_awid,
    input                   [7:0]           i_awaddr,
    input                   [3:0]           i_awlen,
    input                   [2:0]           i_awsize,
    input                   [1:0]           i_awburst,
    input                                   i_awvalid,
    
    output   reg                            o_awready,
    
    /**************    Write Data Channel    **************/
    input                   [3:0]           i_wid,
    input                  [15:0]           i_wdata,
    input                                   i_wlast,
    input                                   i_wvalid,
    
    output   reg                            o_wready,
    
    /************    Write Response Channel    *************/
    input                                   i_bready,
    
    output   reg            [3:0]           o_bid,
    output   reg            [1:0]           o_bresp,
    output   reg                            o_bvalid,
    
    /*************    Read Address Channel    **************/
    input                   [3:0]           i_arid,
    input                   [7:0]           i_araddr,
    input                   [3:0]           i_arlen,
    input                   [2:0]           i_arsize,
    input                   [1:0]           i_arburst,
    input                                   i_arvalid,
    
    output   reg                            o_arready,
    
    /**************    Read Data Channel    ***************/
    input                                   i_rready,
    
    output   reg            [3:0]           o_rid,
    output   reg           [15:0]           o_rdata,
    output   reg                            o_rlast,
    output   reg                            o_rvalid,
    output   reg            [1:0]           o_rresp,
    
    /*****************     For Test    *********************/
    input                                   test_awready,
    input                                   test_wready,
    input                   [1:0]           test_bresp,
    input                                   test_arready,
    input                                   test_rvalid,
    input                   [1:0]           test_rresp
    );
    
    
    
    reg                     [7:0]           r_waddr;
    reg                     [3:0]           r_wburstn;
    reg                    [15:0]           r_wdata;
    reg                                     r_wlast;
    
    reg                     [7:0]           r_raddr;
    reg                     [3:0]           r_rburstn;
    reg                    [15:0]           r_rdata;
    reg                                     r_rlast;
        
    reg                                     r_waddr_on;
    reg                                     r_wdata_on;
    reg                     [4:0]           awlen_cnt;
    
    reg                                     r_raddr_on;
    reg                                     r_rdata_on;
    reg                     [4:0]           arlen_cnt;
    
    reg                    [15:0]           mem [0:15];
    
    /*--------------     handshake   -------------------*/
    wire      aw_handshake  =  i_awvalid && o_awready;
    wire       w_handshake  =  i_wvalid && o_wready;
    wire       b_handshake  =  i_bready && o_bvalid;
    wire      ar_handshake  =  i_arvalid && o_arready;
    wire       r_handshake  =  i_rready && o_rvalid;
    
    
    always @ (posedge i_clk or negedge i_resetn) begin          //for control slave
        if (!i_resetn) begin
            o_awready <= 1'd0;
            o_wready <= 1'd0;
            o_arready <= 1'd0;
        end
        else if (test_awready) begin
            o_awready <= 1'd1;
        end
        else if (test_wready) begin
            o_wready <= 1'd1;
        end
        else if (test_arready) begin
            o_arready <= 1'd1;
        end
        else begin
            o_awready <= 1'd0;
            o_wready <= 1'd0;
            o_arready <= 1'd0;
        end
    end
    
    
    always @ (posedge i_clk or negedge i_resetn) begin          // count write burst length
        if (!i_resetn) begin
            awlen_cnt <= 5'd0;
        end
        else if (awlen_cnt >= r_wburstn) begin
            awlen_cnt <= 5'd0;
        end
        else if (r_wlast) begin
            awlen_cnt <= 5'd0;
        end
        else if (r_waddr_on && r_wdata_on) begin
            awlen_cnt <= awlen_cnt + 5'd1;
        end
        else begin
            awlen_cnt <= awlen_cnt;
        end
    end
    
    always @ (posedge i_clk or negedge i_resetn) begin        //count read burst length
        if (!i_resetn) begin
            arlen_cnt <= 5'd0;
        end
        else if (arlen_cnt >= r_rburstn) begin
            arlen_cnt <= 5'd0;
        end
        else if (o_rlast) begin
            arlen_cnt <= 5'd0;
        end
        else if (r_handshake) begin
            arlen_cnt <= arlen_cnt + 5'd1;
        end
        else begin
            arlen_cnt <= arlen_cnt;
        end
    end
    
    
    /*----------------------   Write Address   --------------------------*/
    
    always @ (posedge i_clk or negedge i_resetn) begin              // write address 
        if (!i_resetn) begin
            r_waddr <= 8'd0;
        end
        else if(aw_handshake) begin
            r_waddr <= i_awaddr;
            r_waddr_on <= 1'd1;
        end
        else begin
            r_waddr <= r_waddr;
        end
    end
        
    
    always @ (posedge i_clk or negedge i_resetn) begin              // awlen
        if (!i_resetn) begin
            r_wburstn <= 8'd0;
        end
        else if(aw_handshake) begin
            r_wburstn <= i_awlen;
        end
        else begin
            r_wburstn <= r_wburstn;
        end
    end
    
    
    /*-----------------------    Write Data    ---------------------------*/
    
    always @ (posedge i_clk or negedge i_resetn) begin
        if (!i_resetn) begin
            r_wdata <= 16'd0;
        end
        else if (w_handshake) begin
            r_wdata <= i_wdata;
            r_wdata_on <= 1'd1;
        end
        else begin
            r_wdata <= r_wdata;
        end
    end
    
    always @ (posedge i_clk) begin
        if (r_waddr_on && r_wdata_on) begin
            mem[r_waddr + awlen_cnt] <= r_wdata;
            r_wdata_on <= 1'd0;
            if (i_wlast) begin
                r_waddr_on <= 1'd0;
            end
        end
    end
    
    
    always @ (posedge i_clk or negedge i_resetn) begin        //last data
        if (!i_resetn) begin
            r_wlast <= 1'd0;
        end
        else if (i_wlast && w_handshake) begin
            r_wlast <= i_wlast;
        end
        else begin
            r_wlast <= r_wlast;
        end
    end
    
    /*-----------------------    Write Response    ---------------------------*/
    
    always @ (posedge i_clk or negedge i_resetn) begin
        if (!i_resetn) begin
            //o_bresp <= 2'd0;
        end
        else if (r_wlast) begin
            case (test_bresp)
                2'b00: o_bresp = 2'b00;     //OKAY
                2'b01: o_bresp = 2'b01;     //EXOKAY
                2'b10: o_bresp = 2'b10;     //SLVERR
                2'b11: o_bresp = 2'b11;     //DECERR
                default: o_bresp = 2'd00;
            endcase
        end
        else begin
            o_bresp <= o_bresp;
        end
    end
    
    always @ (posedge i_clk or negedge i_resetn) begin
        if (!i_resetn) begin
            o_bvalid <= 1'd0;
        end
        else if (r_wlast) begin
            o_bvalid <= 1'd1;
        end
        else begin
            o_bvalid <= 1'd0;
        end
    end
    

/*-----------------------    Read Address    ---------------------------*/
    
    always @ (posedge i_clk or negedge i_resetn) begin
        if (!i_resetn) begin
            r_raddr <= 8'd0;
        end
        else if (ar_handshake) begin
            r_raddr <= i_araddr;
            r_raddr_on <= 1'd1;
        end
        else begin
            r_raddr <= r_raddr;
        end
    end    
    
    always @ (posedge i_clk or negedge i_resetn) begin
        if (!i_resetn) begin
            r_rburstn <= 4'd0;
        end
        else if (ar_handshake) begin
            r_rburstn <= i_arlen;
        end
        else begin
            r_rburstn <= r_rburstn;
        end
    end
    
    
/*-----------------------    Read Data    ---------------------------*/
    
    always @ (posedge i_clk or negedge i_resetn) begin
        if (!i_resetn) begin
            o_rvalid <= 1'd0;
        end
        else if (test_rvalid && r_raddr_on) begin
            o_rvalid = 1'd1;
        end
        else begin
            o_rvalid <= 1'd0;
        end
    end
        
    always @ (posedge i_clk) begin
        if (test_rvalid && r_raddr_on) begin
            o_rdata <= mem[r_raddr + arlen_cnt];
        end
    end
    
    always @ (posedge i_clk or negedge i_resetn) begin
        if (!i_resetn) begin
            o_rlast <= 1'd0;
        end
        else if (test_rvalid && r_raddr_on && (arlen_cnt == i_arlen-1)) begin
            o_rlast <= 1'd1;
            if (r_handshake) begin
                r_raddr_on <= 1'd0;
                o_rlast <= 1'd0;
            end
        end
    end
    
    always @ (posedge i_clk or negedge i_resetn) begin
        if (!i_resetn) begin
            //o_rresp <= 2'd0;
        end
        else if (o_rlast && r_handshake) begin
            o_rresp <= test_rresp;
        end
        else begin
            o_rresp <= o_rresp;
        end
    end

endmodule

 

Testbench

`timescale 1ns / 1ps

module tb_axi_slave();

    reg i_clk;
    reg i_resetn;
    
    /*************    Write Address Channel   ************/
    reg                   [3:0]           i_awid;
    reg                   [7:0]           i_awaddr;
    reg                   [3:0]           i_awlen;
    reg                   [2:0]           i_awsize;
    reg                   [1:0]           i_awburst;
    reg                                   i_awvalid;
    
    wire                                  o_awready;
    
    /**************    Write Data Channel    **************/
    reg                   [3:0]           i_wid;
    reg                  [15:0]           i_wdata;
    reg                                   i_wlast;
    reg                                   i_wvalid;
    
    wire                                  o_wready;
   
    /************    Write Response Channel    *************/
    reg                                   i_bready;
    
    wire                  [3:0]           o_bid;
    wire                  [1:0]           o_bresp;
    wire                                  o_bvalid;
    
    /*************    Read Address Channel    **************/
    reg                    [3:0]           i_arid;
    reg                    [7:0]           i_araddr;
    reg                    [3:0]           i_arlen;
    reg                    [2:0]           i_arsize;
    reg                    [1:0]           i_arburst;
    reg                                    i_arvalid;
    
    wire                                   o_arready;
    
    /**************    Read Data Channel    ***************/
    reg                                    i_rready;
    
    wire                   [3:0]           o_rid;
    wire                  [15:0]           o_rdata;
    wire                                   o_rlast;
    wire                                   o_rvalid;
    wire                   [1:0]           o_rresp;
    
    /*****************     For Test    *********************/
    reg                                    test_awready;
    reg                                    test_wready;
    reg                     [1:0]          test_bresp;
    reg                                    test_arready;
    reg                                    test_rvalid;
    reg                     [1:0]          test_rresp;

    
    axi_slave axi_slave (   .i_clk         (i_clk),
                            .i_resetn      (i_resetn),
                            
                            .i_awid        (i_awid),
                            .i_awaddr      (i_awaddr),
                            .i_awlen       (i_awlen),
                            .i_awsize      (i_awsize),
                            .i_awburst     (i_awburst),
                            .i_awvalid     (i_awvalid),
                            .o_awready     (o_awready),
                            
                            .i_wid         (i_wid),
                            .i_wdata       (i_wdata),
                            .i_wlast       (i_wlast),
                            .i_wvalid      (i_wvalid),
                            .o_wready      (o_wready),
                            
                            .i_bready      (i_bready),
                            .o_bid         (o_bid),
                            .o_bresp       (o_bresp),
                            .o_bvalid      (o_bvalid),
                            
                            .i_arid        (i_arid),
                            .i_araddr      (i_araddr),
                            .i_arlen       (i_arlen),
                            .i_arsize      (i_arsize),
                            .i_arburst     (i_arburst),
                            .i_arvalid     (i_arvalid),
                            .o_arready     (o_arready),
                            
                            .i_rready      (i_rready),
                            .o_rid         (o_rid),
                            .o_rdata       (o_rdata),
                            .o_rlast       (o_rlast),
                            .o_rvalid      (o_rvalid),
                            .o_rresp       (o_rresp),
                            
                            .test_awready  (test_awready),
                            .test_wready   (test_wready),
                            .test_bresp    (test_bresp),
                            .test_arready  (test_arready),
                            .test_rvalid   (test_rvalid),
                            .test_rresp    (test_rresp)
                         );
                            
                            
    always #5 i_clk = ~i_clk;
    initial begin
        i_clk = 1'd0; i_resetn = 1'd1;
        test_rresp = 2'd0;
        test_bresp = 2'd0;
        #1
        i_resetn = 1'd0;
        #1
        i_resetn = 1'd1;
        #10
        i_awaddr = 8'd8;
        i_awlen = 4'd4;
        i_awvalid = 1'd1;
        #10
        test_awready = 1'd1;
        #20
        test_awready = 1'd0;
        i_awvalid = 1'd0;
        #20
        i_wdata = 16'd37;
        i_wvalid = 1'd1;
        test_wready = 1'd1;
        #20
        test_wready = 1'd0;
        i_wvalid = 1'd0;
        #10
        i_wdata = 16'd38;
        i_wvalid = 1'd1;
        test_wready = 1'd1;
        #20
        test_wready = 1'd0;
        i_wvalid = 1'd0;
        #10
        i_wdata = 16'd39;
        i_wvalid = 1'd1;
        test_wready = 1'd1;
        #20
        test_wready = 1'd0;
        i_wvalid = 1'd0;
        #10
        i_wdata = 16'd40;
        i_wvalid = 1'd1;
        i_wlast = 1'd1;
        test_wready = 1'd1;
        #20
        test_wready = 1'd0;
        i_wvalid = 1'd0;
        i_wlast = 1'd0;
        #10
        i_bready = 1'd1;
        #30
        i_araddr = 8'd8;
        i_arlen = 4'd4;
        i_arvalid = 1'd1;
        #10
        test_arready = 1'd1;
        #20
        test_arready = 1'd0;
        i_arvalid = 1'd0;
        #20
        test_rvalid = 1'd1;
        #20
        i_rready = 1'd1;
        #10
        i_rready = 1'd0;
        #10
        i_rready = 1'd1;
        #10
        i_rready = 1'd0;
        #10
        i_rready = 1'd1;
        #10
        i_rready = 1'd0;
        #10
        i_rready = 1'd1;
        #10
        i_rready = 1'd0;
        #30
        $finish;
    end

endmodule

 

Simulation

Signal이 많아서 한눈에 보기 어려운 편 입니다.

 

이걸 timing diagram으로 간단하게 나타내면

  • Write Transaction

 

  • Read Transaction

 

결과가 이런 식으로 나왔다고 보시면 됩니다.

 


이번에 설계한 AXI Protocol은 burst mode만 지원합니다.

 

추후에 multiple transaction과 out-of-order 기능을 지원하는 AXI를 설계해서 공유하도록 하겠습니다.

 


 

댓글