[Verilog] 실전 2 – APB interface design

그럼, 이제 본격적으로 APB interface를 설계해 봅시다. Interface의 개요와 검증을 위한 BFM은 이전 글을 참고해 주세요.

Register map

먼저 interface 설계를 위한 register map을 작성해야 합니다. 이는 interface에서 IP block에 어떤 신호를 주고받을지 정하는 겁니다. Register map은 IP를 설명하는 data sheet에 기록되고, IP를 운용하는 소프트웨어를 짜는 데도 필요하기 때문에 굉~~~장히 중요합니다.

글 설명 이미지, Register setting
Register map의 역할

그럼, register map을 아래와 같이 작성해 보겠습니다.

  • Address 0x0: register a
Signal nameR/WDefault valueBit
Reserved31:11
a_3RW0x010:8
Reserved7:6
a_2RW0x05:4
Reserved3:1
a_1RW0x00
register a
  • Address 0x4: register b
Signal nameR/WDefault valueBit
Reserved31:11
b_3RW0x010:8
Reserved7:6
b_2RW0x05:4
Reserved3:1
b_1RW0x00
register b

여기서 R/W는 Read/Write access이며 종류는 아래와 같습니다.

  • RW: readable and writable
  • RO: read-only
  • WO: write-only
  • W1C: write 1 clear

APB interface 설계

그럼, 본격적으로 interface를 설계해 볼까요? 우선 input output port를 설정해 봅시다.

module apb_if (
     //APB Interface
     input  wire        pclk
    ,input  wire        presetn
    ,input  wire        penable
    ,input  wire        psel
    ,input  wire [31:0] paddr
    ,input  wire        pwrite
    ,input  wire [31:0] pwdata
    ,output wire [31:0] prdata
 
     //Register Interface
    ,output wire        a_1
    ,output wire [ 1:0] a_2
    ,output wire [ 2:0] a_3
    ,output wire        b_1
    ,output wire [ 1:0] b_2
    ,output wire [ 2:0] b_3
);

Bus signal이 있고, register map에서 설정한 signal 들이 output으로 설정되어 IP block에 연결될 겁니다. 그리고 자료형 선언을 해야겠죠? 우선 write_en과 read_en을 설정했습니다.

자료형 선언

    //===================================================================
    // Internal Signals
    //===================================================================
    wire        write_en = psel & ~penable &   pwrite;
    wire        read_en  = psel & ~penable &  ~pwrite;

Enable signal 들은 psel이 high일 때, 그러니까 CPU가 이 IP를 선택할 때 켜집니다. 그리고 pwrite에 따라 write를 할지, read를 할지 정합니다.

APB write

다음으로 Address 관련된 signal입니다.

    //===================================================================
    // Address Decode 
    //===================================================================
    wire        a = (paddr == 32'h0);
    wire        b = (paddr == 32'h4);
         
    //===================================================================
    // Write Enable
    //===================================================================
    wire        we_a = write_en & a;
    wire        we_b = write_en & b;

Register map에서 설정한 address를 반영했습니다.

다음은 실제 register signal입니다. 작성된 register map과 비교해 보시길 바랍니다.

    //===================================================================
    // Register Setting
    //===================================================================
    reg         a_one;
    always @(posedge pclk or negedge presetn) begin
        if(!presetn)   a_one <= 1'b0;
        else if (we_a) a_one <= pwdata[0];
    end
 
    reg  [ 1:0] a_two;
    always @(posedge pclk or negedge presetn) begin
        if(!presetn)   a_two <= 1'b0;
        else if (we_a) a_two <= pwdata[5:4];
    end
 
    reg  [ 2:0] a_three;
    always @(posedge pclk or negedge presetn) begin
        if(!presetn)   a_three <= 1'b0;
        else if (we_a) a_three <= pwdata[10:8];
    end
 
    reg         b_one;
    always @(posedge pclk or negedge presetn) begin
        if(!presetn)   b_one <= 1'b0;
        else if (we_b) b_one <= pwdata[0];
    end
 
    reg  [ 1:0] b_two;
    always @(posedge pclk or negedge presetn) begin
        if(!presetn)   b_two <= 1'b0;
        else if (we_b) b_two <= pwdata[5:4];
    end
 
    reg  [ 2:0] b_three;
    always @(posedge pclk or negedge presetn) begin
        if(!presetn)   b_three <= 1'b0;
        else if (we_b) b_three <= pwdata[10:8];
    end

CPU가 register a(0x0)에 write를 하면 we_a가 켜지면서 pwdata의 특정 bit 들이 a_one, a_two, a_three에 입력됩니다. Register b(0x4)도 마찬가지로 입력됩니다. 그럼, 이 register file을 output에 연결해야겠죠?

    //===================================================================
    // Output Assign
    //===================================================================
    assign a_1 = a_one  ;
    assign a_2 = a_two  ;
    assign a_3 = a_three;
    assign b_1 = b_one  ;
    assign b_2 = b_two  ;
    assign b_3 = b_three;

APB Read

이제 RD(Read Data) 값을 설정해 봅시다.

    //===================================================================
    // Local Parameters
    //===================================================================
    localparam INVALID_DATA = 32'hDEAD_DEAD;
    
    //===================================================================
    // Read Decode
    //===================================================================
    reg [31:0] RD;
    always @(*) begin
        if(read_en) begin
            case (paddr)
                32'h0   : RD = {21'h0, a_three, 2'h0, a_two, 3'h0, a_one};
                32'h4   : RD = {21'h0, b_three, 2'h0, b_two, 3'h0, b_one};
                default : RD = INVALID_DATA;
            endcase
        end
        else RD = INVALID_DATA;
    end
    
    assign  prdata = RD;

그럼, testbench를 작성하고 simulation 결과를 통해 register setting이 제대로 되는지 확인해 봅시다.

//top.v
`timescale 1ns/10ps

module top();

    parameter period_pclk = 10;

    reg         pclk;
    reg         presetn;

    wire        psel;
    wire        penable;
    wire [31:0] paddr;
    wire        pwrite;
    wire [31:0] pwdata;
    wire        pready;
    wire [31:0] prdata;
    wire        pslverr;

    wire        a_1;
    wire [ 1:0] a_2;
    wire [ 2:0] a_3;
    wire        b_1;
    wire [ 1:0] b_2;
    wire [ 2:0] b_3;

    //clk
    always #(period_pclk*0.5) pclk = ~pclk;

    assign pready  = 1'b1;
    assign pslverr = 1'b0;

    //instance
    apb_bfm u_apb (
         .pclk    (pclk    )
        ,.presetn (presetn ) 
        ,.psel    (psel    ) 
        ,.penable (penable ) 
        ,.paddr   (paddr   ) 
        ,.pwrite  (pwrite  ) 
        ,.pwdata  (pwdata  ) 
        ,.pready  (pready  ) 
        ,.prdata  (prdata  ) 
        ,.pslverr (pslverr ) 
    );

    apb_if u_apb_if (
         .pclk    (pclk    )
        ,.presetn (presetn ) 
        ,.psel    (psel    ) 
        ,.penable (penable ) 
        ,.paddr   (paddr   ) 
        ,.pwrite  (pwrite  ) 
        ,.pwdata  (pwdata  ) 
        ,.prdata  (prdata  ) 

        ,.a_1     (a_1     )
        ,.a_2     (a_2     )
        ,.a_3     (a_3     )
        ,.b_1     (b_1     )
        ,.b_2     (b_2     )
        ,.b_3     (b_3     )
    );

    initial begin
        pclk    = 1'b0;
        presetn = 1'b0;

        #(period_pclk);
        presetn = 1'b1;

        //Register a write
        #(10*period_pclk);
        u_apb.apb_write(32'h0,32'haaa);

        #(10*period_pclk);
        u_apb.apb_write(32'h0,32'h555);

        //Register b write
        #(10*period_pclk);
        u_apb.apb_write(32'h4,32'haaa);

        #(10*period_pclk);
        u_apb.apb_write(32'h4,32'h555);

        #100 $finish;
    end

    initial begin
        $dumpfile ("test.vcd");
        $dumpvars();
    end

endmodule

//apb_if.v
module apb_if (
     //APB Interface
     input  wire        pclk
    ,input  wire        presetn
    ,input  wire        penable
    ,input  wire        psel
    ,input  wire [31:0] paddr
    ,input  wire        pwrite
    ,input  wire [31:0] pwdata
    ,output wire [31:0] prdata
 
     //Register Interface
    ,output wire        a_1
    ,output wire [ 1:0] a_2
    ,output wire [ 2:0] a_3
    ,output wire        b_1
    ,output wire [ 1:0] b_2
    ,output wire [ 2:0] b_3
);

    //===================================================================
    // Local Parameters
    //===================================================================
    localparam INVALID_DATA = 32'hDEAD_DEAD;
    
    //===================================================================
    // Internal Signals
    //===================================================================
    wire        write_en = psel & ~penable &   pwrite;
    wire        read_en  = psel & ~penable &  ~pwrite;
    reg  [31:0] RD ;
      
    //===================================================================
    // Address Decode 
    //===================================================================
    wire        a = (paddr == 32'h0);
    wire        b = (paddr == 32'h4);
     
    //===================================================================
    // Write Enable
    //===================================================================
    wire        we_a = write_en & a;
    wire        we_b = write_en & b;
    
    //===================================================================
    // Register Files 
    //===================================================================
    reg         a_one;
    always @(posedge pclk or negedge presetn) begin
        if(!presetn)   a_one <= 1'b0;
        else if (we_a) a_one <= pwdata[0];
    end
 
    reg  [ 1:0] a_two;
    always @(posedge pclk or negedge presetn) begin
        if(!presetn)   a_two <= 1'b0;
        else if (we_a) a_two <= pwdata[5:4];
    end
 
    reg  [ 2:0] a_three;
    always @(posedge pclk or negedge presetn) begin
        if(!presetn)   a_three <= 1'b0;
        else if (we_a) a_three <= pwdata[10:8];
    end
 
    reg         b_one;
    always @(posedge pclk or negedge presetn) begin
        if(!presetn)   b_one <= 1'b0;
        else if (we_b) b_one <= pwdata[0];
    end
 
    reg  [ 1:0] b_two;
    always @(posedge pclk or negedge presetn) begin
        if(!presetn)   b_two <= 1'b0;
        else if (we_b) b_two <= pwdata[5:4];
    end
 
    reg  [ 2:0] b_three;
    always @(posedge pclk or negedge presetn) begin
        if(!presetn)   b_three <= 1'b0;
        else if (we_b) b_three <= pwdata[10:8];
    end
 
    //===================================================================
    // Read Decode
    //===================================================================
    always @(*) begin
        if(read_en) begin
            case (paddr)
                32'h0   : RD = {21'h0, a_three, 2'h0, a_two, 3'h0, a_one};
                32'h4   : RD = {21'h0, b_three, 2'h0, b_two, 3'h0, b_one};
                default : RD = INVALID_DATA;
            endcase
        end
        else RD = INVALID_DATA;
    end
    
    assign  prdata = RD;
    
    //===================================================================
    // Output Assign
    //===================================================================
    assign a_1 = a_one  ;
    assign a_2 = a_two  ;
    assign a_3 = a_three;
    assign b_1 = b_one  ;
    assign b_2 = b_two  ;
    assign b_3 = b_three;

endmodule

Register a(address 0x0)에 write transfer를 함으로써 a_1, a_2, a_3 signal을 컨트롤하고 register b(address 0x4)에 write transfer를 함으로써 b_1, b_2, b_3 signal을 컨트롤할 수 있습니다.

Register a (0x0)
Register a (0x0)
Register b (0x4)
Register b (0x4)

이 signal 들이 IP의 block에 input 되어 IP control이 가능한 것입니다. CPU가 AMBA bus를 통해 IP를 컨트롤하는 것, 이것이 register setting입니다.

이로써 본격적으로 APB interface를 사용한 IP를 설계할 수 있게 되었습니다.

참고: ARM® AMBA APB Protocol Specification

유사한 게시물