SoC入门:APBmaster设计主站设计原理与实践

电子说

1.3w人已加入

描述

大家不要以为APB的master和slave很简单,不需要了解。这是大错特错,为什么呢?

不过设计什么模块,你都要让它挂在标准总线上,比如你设计DMA,你就同时需要了解AMBA的master和slave设计。又比如你是设计算法计算模块,你的数据肯定要放到sram,你当然也要了解AMBA的master设计,将数据传输到crossbar上,进而放到指定memory。又比如SOC设计,肯定需要各种bridge,假设一个AHB2APB,你就同时需要了解AHB slave和APB master。

以APB为例,还是因为APB简单,但是我们可以从它学到设计的方法和思路。

既然是设计就需要spec和状态机。

设计spec如下

1、模块规划

状态机

模块diagram

2、接口描述

状态机

接口描述

3、时序描述

读时序

状态机

读时序

写时序

状态机

写时序

4、FSM

就是之前讲的APB协议状态机。如下图

状态机

APB FSM

模块规划有了,接口有了,时序有了,状态机有了,就可以开始设计coding了,代码如下:

 

 

module apb
#(
  parameter RD_FLAG        = 8'b0           ,
  parameter WR_FLAG        = 8'b1           ,
  parameter CMD_RW_WIDTH   = 8              ,
  parameter CMD_ADDR_WIDTH = 16             ,
  parameter CMD_DATA_WIDTH = 32             ,
  parameter CMD_WIDTH      = CMD_RW_WIDTH   + 
                             CMD_ADDR_WIDTH + 
                             CMD_DATA_WIDTH
)(
//-- clkrst signal
  input                           pclk_i       ,
  input                           prst_n_i     ,


//-- cmd_in
  input      [CMD_WIDTH-1:0]      cmd_i        ,
  input                           cmd_vld_i    ,
  output reg [CMD_DATA_WIDTH-1:0] cmd_rd_data_o,


//-- apb interface
  output reg [CMD_ADDR_WIDTH-1:0] paddr_o      ,
  output reg                      pwrite_o     ,
  output reg                      psel_o       ,
  output reg                      penable_o    ,
  output reg [CMD_DATA_WIDTH-1:0] pwdata_o     ,
  input      [CMD_DATA_WIDTH-1:0] prdata_i     ,
  input                           pready_i     ,
  input                           pslverr_i
);


//-- FSM state
parameter IDLE   = 3'b001;
parameter SETUP  = 3'b010;
parameter ACCESS = 3'b100;


//-- current state and next state
reg [2:0] cur_state;
reg [2:0] nxt_state;


//-- data buf
reg                      start_flag     ;
reg [CMD_WIDTH-1:0]      cmd_in_buf     ;
reg [CMD_DATA_WIDTH-1:0] cmd_rd_data_buf;




/*-----------------------------------------------
 --             update cmd_in_buf              --
-----------------------------------------------*/
always @ (posedge pclk_i or negedge prst_n_i) begin
  if (!prst_n_i) begin
    cmd_in_buf <= {(CMD_WIDTH){1'b0}};
  end
  else if (cmd_vld_i && pready_i) begin
    cmd_in_buf <= cmd_i;
  end
end


/*-----------------------------------------------
 --             start flag of transfer         --
-----------------------------------------------*/
always @ (posedge pclk_i or negedge prst_n_i) begin
  if (!prst_n_i) begin
    start_flag <= 1'b0;
  end
  else if (cmd_vld_i && pready_i) begin
    start_flag <= 1'b1;
  end
  else begin
    start_flag <= 1'b0;
  end
end


/*-----------------------------------------------
 --           update current state             --
-----------------------------------------------*/
always @ (posedge pclk_i or negedge prst_n_i) begin
  if (!prst_n_i) begin
    cur_state <= IDLE;
  end
  else begin
    cur_state <= nxt_state;
  end
end


/*-----------------------------------------------
 --               update next state            --
-----------------------------------------------*/
always @ (*) begin
  case(cur_state)
    IDLE  :if(start_flag)begin
             nxt_state = SETUP;
           end
           else begin
             nxt_state = IDLE;
           end


    SETUP :nxt_state = ACCESS;
          
    ACCESS:if (!pready_i)begin
             nxt_state = ACCESS;
           end
           else if(start_flag)begin
             nxt_state = SETUP;
           end
           else if(!cmd_vld_i && pready_i)begin
             nxt_state = IDLE;
           end
  endcase
end


/*-----------------------------------------------
 --         update signal of output            --
-----------------------------------------------*/
always @ (posedge pclk_i or negedge prst_n_i) begin
  if (!prst_n_i) begin
    pwrite_o  <= 1'b0;
    psel_o    <= 1'b0;
    penable_o <= 1'b0;
    paddr_o   <= {(CMD_ADDR_WIDTH){1'b0}};
    pwdata_o  <= {(CMD_DATA_WIDTH){1'b0}};
  end
  
  else if (nxt_state == IDLE) begin
    psel_o    <= 1'b0;
    penable_o <= 1'b0;
  end


  else if(nxt_state == SETUP)begin
    psel_o    <= 1'b1;
    penable_o <= 1'b0;
    paddr_o   <= cmd_in_buf[CMD_WIDTH-CMD_RW_WIDTH-1:CMD_DATA_WIDTH];
    //-- read
    if(cmd_in_buf[CMD_WIDTH-1:CMD_WIDTH-8] == RD_FLAG)begin
      pwrite_o <= 1'b0;
    end
    //-- write
    else begin
      pwrite_o  <= 1'b1;
      pwdata_o  <= cmd_in_buf[CMD_DATA_WIDTH-1:0];
    end
  end


  else if(nxt_state == ACCESS)begin
    penable_o <= 1'b1;
  end
end


/*-----------------------------------------------
 --            update cmd_rd_data_buf          --
-----------------------------------------------*/
always @ (posedge pclk_i or negedge prst_n_i) begin
  if (!prst_n_i) begin
    cmd_rd_data_buf <= {(CMD_DATA_WIDTH){1'b0}};
  end
  else if (pready_i && psel_o && penable_o) begin
    cmd_rd_data_buf <= prdata_i;
  end
end


/*-----------------------------------------------
 --            update cmd_rd_data_o            --
-----------------------------------------------*/
always @ (posedge pclk_i or negedge prst_n_i) begin
  if (!prst_n_i) begin
    cmd_rd_data_o <= {(CMD_DATA_WIDTH){1'b0}};
  end
  else begin
    cmd_rd_data_o <= cmd_rd_data_buf;
  end
end


endmodule

 

 

模块设计的比较简单,只是实现APB的基本功能。下面讲一下设计重点:

·一定要做好功课在开始coding。

·Flow control,APB的上级模块,需要给到流控信号,告知APB master什么时候开始传输,什么时候结束。

·FSM,必须完全遵循AMBA的datasheet。

·时序对齐,和FSM一样,接口时序要和APB协议对齐。

·重点中的重点,pready的反压一定要逐级反压,不能直接送到APB master的上次模块,这样会丢数据。

testbench如下

 

 

`timescale 1ns/1ns
module tb_apb;
  reg         pclk_i       ;
  reg         prst_n_i     ;
                          
  reg  [55:0] cmd_i        ;
  reg         cmd_vld_i    ;
  wire [31:0] cmd_rd_data_o;
                          
  wire [15:0] paddr_o      ;
  wire        pwrite_o     ;
  wire        psel_o       ;
  wire        penable_o    ;
  wire [31:0] pwdata_o     ;
  reg  [31:0] prdata_i     ;
  reg         pready_i     ;
  reg         pslverr_i    ;


initial begin
 // rst; 
  pclk_i   = 0;
  prst_n_i = 1;
  pslverr_i = 0;
  cmd_i = 56'b0;
  cmd_vld_i = 0;
  prdata_i = 32'b0;
  pready_i = 1;
  #20 prst_n_i = 0;
  #20 prst_n_i = 1;


 // cmd_in_wr(cmd_i,56'h01_FF_EE_DD_CC_BB_AA);
    cmd_i     = 56'h01_FF_EE_DD_CC_BB_AA;
    cmd_vld_i = 1   ;
    #20 cmd_vld_i = 0;
    #31 pready_i = 0;
    #80 pready_i = 1;


  #90;
  //cmd_in_rd(cmd_i,56'h00_AA_BB_CC_DD_EE_FF,prdata_i,32'h12_34_56_78);
    cmd_i = 56'h00_AA_BB_CC_DD_EE_FF;
    cmd_vld_i = 1;
    #20 cmd_vld_i = 0;
    #30 pready_i = 0;


    #60 pready_i = 1;
        prdata_i = 32'h12_34_56_78;


    cmd_i = 56'h00_AA_BB_CC_DD_EE_FF;
    cmd_vld_i = 1;
    #20 cmd_vld_i = 0;
    #30 pready_i = 0;


    #50 pready_i = 1;
        prdata_i = 32'h11_22_33_44;




end


always #10 pclk_i = ~pclk_i;


//-- RST
task rst;
  begin
    pclk_i   = 1;
    prst_n_i = 1;
    pslverr_i = 0;
    cmd_i = 56'b0;
    cmd_vld_i = 0;
    prdata_i = 32'b0;
    pready_i = 1;
    #20 prst_n_i = 0;
    #10 prst_n_i = 1;
    //cmd_i = 56'h01_FF_EE_DD_CC_BB_Ab;
  end
endtask


//-- write
task cmd_in_wr;
  output [55:0] cmd;
  input  [55:0] data;


  begin
    cmd     = data;
    cmd_vld_i = 1   ;
    #20 cmd_vld_i = 0;
    #20 pready_i = 0;
    #40 pready_i = 1;
  end
endtask


//-- read
task cmd_in_rd;
  output [55:0] cmd;
  input  [55:0] data ;
  output [31:0] prdata;
  input  [31:0] rd_data;


  begin
    cmd = data;
    cmd_vld_i = 1;
    #20 cmd_vld_i = 0;
    #20 pready_i = 0;
    #40 pready_i = 1;
        prdata = rd_data;
  end
endtask
initial begin
  #1000 $finish;
end
apb tb_apb(
            .pclk_i       (pclk_i       ),
            .prst_n_i     (prst_n_i     ),
            .cmd_i        (cmd_i        ),
            .cmd_vld_i    (cmd_vld_i    ),
            .cmd_rd_data_o(cmd_rd_data_o),
            .paddr_o      (paddr_o      ),
            .pwrite_o     (pwrite_o     ),
            .psel_o       (psel_o       ),
            .penable_o    (penable_o    ),
            .pwdata_o     (pwdata_o     ),
            .prdata_i     (prdata_i     ),
            .pready_i     (pready_i     ),
            .pslverr_i    (pslverr_i    )
          );




initial begin
  $fsdbDumpfile("apb.fsdb");
  $fsdbDumpvars            ;
  $fsdbDumpMDA             ;
end


endmodule

 

 

makefile如下:

 

 

LAB_DIR = /home/*/apb




DFILES = $(LAB_DIR)/*.v 


all:clean elab rung
elab:
  vcs -full64 -LDFLAGS -Wl,-no-as-needed -debug_acc+all -timescale=1ns/1ns 
  -fsdb -sverilog -l comp.log 
  ${DFILES}


run:
  ./simv -l run.log


rung:
  ./simv -gui -l run.log


verdi:
  verdi ${DFILES} 
  -ssf ./*.fsdb &


clean:
  rm -rf  AN.DB 
  rm -rf  DVEfiles 
  rm -rf  csrc 
  rm -rf  simv.* 
  rm -rf  *simv 
  rm -rf  inter.vpd 
  rm -rf  ucli.key 
  rm -rf  *.log 
  rm -rf  verdiLog 
  rm -rf  novas* 
  rm -rf  *.fsdb

 

 

下面是仿真结果

状态机

好了,今天讲的主要就这么多,这个是基础,但也是干货,对以后设计AHB,AXI乃至NOC都非常有帮助。

审核编辑:黄飞

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分