본문 바로가기
Digital Design/SoC

[AMBA] AXI Protocol 설계(Verilog) - ② Write Transaction

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

들어가기 앞서...

 

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

 

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

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

 

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

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

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

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

 

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


 

AXI Protocol의 Write Transaction은 세 가지 채널을 사용합니다.

 

  • Write Address Channel
  • Write Data Channel
  • Write Response Channel

세 채널은 독립적이지만 의존성은 있습니다.

 

Address나 Data 둘 중에 어떤 것이 먼저 도착하는지는 상관 없지만,

두 정보가 모두 있어야 write를 할 수 있고,

그 후에 response를 합니다.

 

오늘 설계한 모델은 burst를 지원하고 burst type은 INCR입니다.

 


Verilog Code

DUT

  • Test용
    always @ (posedge i_clk or negedge i_resetn) begin          //for control slave-write
        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

Testbench에서 slave의 ready 여부를 정해주었습니다.

위의 코드는 그 신호에 따라 slave의 output을 바꿔주는 block입니다.

 

 

  • Burst lenght counter
    always @ (posedge i_clk or negedge i_resetn) begin              // count burst length
        if (!i_resetn) begin
            awlen_cnt <= 4'd0;
        end
        else if (awlen_cnt >= r_wburstn) begin
            awlen_cnt <= 4'd0;
        end
        else if (r_wlast) begin
            awlen_cnt <= 4'd0;
        end
        else if (r_waddr_on && r_wdata_on) begin
            awlen_cnt <= awlen_cnt + 4'd1;
        end
        else begin
            awlen_cnt <= awlen_cnt;
        end
    end

AXI는 하나의 address만 전송하고 control 신호를 보고 자동으로 다음 address에 data를 R/W합니다.

몇 개의 Data를 쓸지 알아야하기 때문에 counter를 만들어줬습니다.

 

 

  • 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

AW channel을 통해 들어온 신호는 Handshake 구간에서 slave의 register에 저장됩니다.

 

 

  • 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 Data channel을 통해 들어온 data 역시 handshake 구간에서 slave의 register에 저장됩니다.

이때 r_wdata_on을 high로 만들어주는데요,

이 이유는 "data 값을 Master에서 받아왔다"는 것을 알리기 위함입니다.

r_awaddr_on도 마찬가지입니다.

 

두 번째 always block에서 (r_wdata_on && r_awdata_on)일때 write를 수행합니다.

address와 data 정보가 모두 들어왔을 때 write를 하기 위해서 조건을 달아준 것 입니다.

write를 하면서 r_wdata_on을 low로 내려줍니다.

그리고 i_wlast, 즉 마지막 data까지 모두 들어오면 write를 하고 r_awaddr_on까지 내려줍니다.

 

여기서 write를 하는 주소를 보면 mem[r_waddr + awlen_cnt] <= r_wdata; 입니다.

이유는 Burst type이 INCR이기 때문에 다음(+1) 주소에 write를 하기 위함입니다.

 

 

  • 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

마지막 data가 write되면 (when r_wlast == HIGH), response를 수행합니다.

o_bvalid를 HIGH로 올려주고 Master의 응답을 기다립니다.

 

 

Testbench

    always #5 i_clk = ~i_clk;
    initial begin
        i_clk = 1'd0; i_resetn = 1'd1;
        #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;
        #20
        
        $finish;
    end

하나하나 넣어주다보니 조금 지저분하긴 한데, 바로 시뮬레이션을 보겠습니다.

 

 

Simulation

하늘색 Handshake 구간에서 clk의 rising edge에 data가 write되었습니다.

i_awaddr = 8이고 i_awlen = 4 였는데

메모리를 보면 8, 9, 10, 11 번지에 data가 write된 것을 볼 수 있습니다.

 

data가 모두 write되고 o_bresp가 0, 즉 okay가 뜬 것을 볼 수 있습니다.

 


 

댓글