基于AHB总线的master读写设计(Verilog)

注:本文为原创,转载请注明出处。

一、AHB总线学习

   1. AHB总线结构

         

 

    如图所示,AHB总线系统利用中央多路选择机制实现主机与从机的互联问题。从图中可以看出,AHB总线结构主要可分为三部分:主机、从机、控制部分。控制部分由仲裁器、数据多路选择、地址和数据多路选择及地址译码器组成。主机首先需要向仲裁器提出使用总线的请求hbusreq信号,仲裁器通过仲裁(多主机使用总线的优先级)授权(hgrant)给某一主机(注意:一个周期内只能有一个主机接入总线),此时,主机就可以开始进行AHB传输了。主机首先发出地址和控制信号。这些信号主要提供地址信息、传输方向、带宽及burst类型(burst传输并非本文重点,故不作讨论)。由于AHB总线统一给每个从机分配地址,译码器可以根据主机发出的地址选择哪个主机与从机进行互联。

2、AHB总线基本传输

        AHB总线的一次传输主要由两部分组成:地址段(开始传输的第一个周期)和数据段(传输开始后的周期)。在hclk上升沿来临时,获得授权的主机驱动地址和控制信号到AHB总线上,在hclk下一周期的上升沿时,slave开始采样地址和控制信息。获取地址和控制信息的slave会返回hresp(回应信号)给master,而在hclk的第三个时钟上升沿hresp被master采样,与此同时,master与slave间完成数据的第一次读写操作。

      在进行数据传输时,若从机没有准备好接收下一个数据iketongg将hready信号拉低来插入一个空闲周期,等下一周期hready重新为高时再接收数据。主机在当前周期发送完部分数据,而在下一周期没有准备好发后面的数据,可通过加入BUSY状态来延缓传输。

二、基于AHB总线的读写设计

   1、输入输出接口

          在设计某个模块时,首先需要理清它有哪些输入输出,从而对设计进行一个整体了解。由于本文的读写模块设计属于比较基础的AHB传输,不涉及突发传输、锁定传输和从机的分块传输。本设计的输入有:hclk_i、irst_n、hgrant_i、hrdata_i、hready_i,输出有:hwdata_o、htrans_o、hwrite_o、haddr_o、hbusreq_o。

   1、状态机设计

          状态机的设计比较重要,本设计的主状态机是:空闲状态、读状态、写状态,从状态机分为读状态机(rd_fsm_r)和写状态机(wr_fsm_r),读状态机和写状态机的状态转移图如图所示。

 根据AHB总线地址段和数据段的特性,可将其分为:空闲状态、请求总线状态、地址段状态、读/写数据状态和读/写最后一个字节状态。注意:在状态机中,何时有效很重要,从图中可以看出,各状态的触发条件都有hready_i信号(由于hready_i信号是一直在变化的,可能前一个状态hready_i信号为高,但后一个状态会变低,不能使用软件思维去思考。),其次,何时开始读/写数据,何时数据读/写完成,这都是由计数器计数来决定的

 

  2、设计时序图

         读写过程比较类似,时序图如图所示:

          

 从图中可以看出,地址与数据并非在同一周期(AHB总线的特性)。当前周期的地址,存储的数据在下一周期才会出现。这种地址和数据交叠出现使总线能进行高性能操作的同时,给从机也提供了足够的时间来响应传输。

 

   3、基本代码

      (1)状态机逻辑

module ahb_test(hbusreq_o,haddr_o,htrans_o,hwdata_o,hwrite_o,
                           hclk_i,irst_n,hgrant_i,hready_i,hrdata_i,we_i,re_i);

   input  hclk_i,irst_n,we_i,re_i,hgrant_i,hready_i;
   input  [31:0] hrdata_i;
   output hbusreq_o,hwrite_o;
   output [31:0] hwdata_o;
   output [1:0] htrans_o;
   output [31:0] haddr_o;

   reg [1:0] main_fsm_r;
   reg [2:0] rd_fsm_r;
   reg [2:0] wr_fsm_r;
   reg  [31:0 haddr_r;
   reg [2:0] rd_cnt_r;
   reg [2:0] wr_cnt_r;

    parameter  data_size = 4; //读写4个字节数据
    parameter rd_base_addr = 'h1A00;
    parameter  wr_base_addr = 'h1B00;

//the status of main fsm
    parameter    S0 = 'd0;
    parameter    S1 = 'd1;
    parameter    S2 = 'd2;

//the status of read fsm
    parameter  RD_IDLE = 3'b000;
    parameter  RD_BUSREQ = 3'b001;
    parameter  RD_ADDR = 3'b010;
    parameter  RD_RD = 3'b011;
    parameter  RD_LRD = 3'b100;

     wire   fsm_rd_idle = rd_fsm_r == RD_IDLE;
     wire   fsm_rd_busreq = rd_fsm_r == RD_BUSREQ;
     wire   fsm_rd_addr =  rd_fsm_r ==RD_ADDR;
     wire   fsm_rd_rd =  rd_fsm_r == RD_RD;
     wire   fsm_rd_lrd = rd_fsm_r === RD_LRD;
     wire    rd_last_data = rd_cnt_r == data_size - 1'd1;

//the status of write fsm
    parameter  WR_IDLE = 3'b000;
    parameter  WR_BUSREQ = 3'b001;
    parameter  WR_ADDR = 3'b010;
    parameter  WR_WD = 3'b011;
    parameter  WR_LWD = 3'b100;

     wire   fsm_wr_idle = wr_fsm_r == WR_IDLE;
     wire   fsm_wr_busreq = wr_fsm_r == WR_BUSREQ;
     wire   fsm_wr_addr =  wr_fsm_r ==WR_ADDR;
     wire   fsm_wr_wd =  wr_fsm_r == WR_WD;
     wire   fsm_wr_lwd = wr_fsm_r === WR_LWD;
     wire    wr_last_data = wr_cnt_r == data_size - 1'd1;
 
//Main  FSM  
       wire rd_done;
       wire wr_done;
       reg we_r,re_r;
       reg  [1:0] main_fsm_r;
   
       always @(posedge hclk_i)
             if(~irst_n)
                main_fsm_r   <=S0;
              else
                 case(main_fsm_r)
                     S0: if(we_r | re_r)
                         main_fsm_r    <= S1;
                     S1: if(rd_done)
                          main_fsm_r   <=S2;
                     S2: if(wr_done)
                          main_fsm_r   <=S0;
                    default:
                          main_fsm_r   <= S0;
                  endcase

//Sub Read FSM
       always @(posedge hclk_i)
             if(~irst_n)
                rd_fsm_r   <= RD_IDLE;
             else
                 case(rd_fsm_r)
                    RD_IDLE : if((we_r | re_r) | (rd_done))
                              rd_fsm_r    <= RD_BUSREQ;
                    RD_BUSREQ : if(hgrant_i & hready_i)
                              rd_fsm_r    <= RD_ADDR;
                     RD_ADDR : if(hready_i)
                              rd_fsm_r    <= RD_RD;
                     RD_RD : if(rd_cnt_r == data_size-2 & hready_i)
                              rd_fsm_r    <= RD_LRD;
                     RD_LRD : if(hready_i & rd_last_data)
                              rd_fsm_r    <= RD_IDLE;
                       default:
                                rd_fsm_r    <= RD_IDLE;
                    endcase

//Sub Write FSM
       always @(posedge hclk_i)
             if(~irst_n)
                wr_fsm_r   <= WR_IDLE;
             else
                 case(wr_fsm_r)
                    WR_IDLE : if(rd_done)
                              wr_fsm_r    <= WR_BUSREQ;
                    WR_BUSREQ : if(hgrant_i & hready_i)
                              wr_fsm_r    <= WR_ADDR;
                     WR_ADDR : if(hready_i)
                              wr_fsm_r    <= WR_WD;
                     WR_WD : if(wr_cnt_r == data_size-2 & hready_i)
                              wr_fsm_r    <= WR_LWD;
                     WR_LWD : if(hready_i & wr_last_data)
                              wr_fsm_r    <= WR_IDLE;
                       default:
                                wr_fsm_r    <= WR_IDLE;
                    endcase

(2)寄存器逻辑

//we_r
always @(posedge hclk_i)
    if(~irst_n | we_r)
       we_r    <= 1'b0;
    else(we_i)
        we_r    <=1'b1;

//re_r
always @(posedge hclk_i)
    if(~irst_n | re_r)
       re_r    <= 1'b0;
    else(re_i)
        re_r    <=1'b1;

assign rd_done = main_fsm_r == S1 & hready_i & rd_last_data;

assign wr_done = main_fsm_r == S2 & hready_i & wr_last_data;

assign hwrite_o = (main_fsm_r == S2) ? 'd1 : 'd0;

assign  hbusreq_o = (fsm_rd_busreq || fsm_wr_busreq) ? 'd1 : 'd0;

//rd_done_r
always @(posedge hclk_i)
    if(~irst_n || rd_done_r)
        rd_done_r    <= 'd0;
    else if(rd_done)
         rd_done_r    <= 'd1;

//wr_done_r
always @(posedge hclk_i)
    if(~irst_n || wr_done_r)
        wr_done_r    <= 'd0;
    else if(wr_done)
         wr_done_r    <= 'd1;

assign  htrans_o = (fsm_rd_addr || fsm_wr_addr) ? 2'b10 : 2'b11;
wire  addr_add_en = (main_fsm_r == S1 || main_fsm_r == S2) && 
                   (fsm_rd_addr || fsm_rd_rd || fsm_wr_addr || fsm_wr_wd);

//haddr_r
always @(posedge hclk_i)
     if(~irst_n)
          haddr_r    <= 32'd0;
    else if(main_fsm_r == S1 & fsm_rd_busreq & hready_i)
           haddr_r    <= rd_base_addr;
    else if(main_fsm_r == S2 & fsm_wr_busreq & hready_i)
           haddr_r    <= wr_base_addr;
    else if(addr_add_en)
           haddr_r    <= haddr_r + 32'd4;


//rd_cnt_r
always @(posedge hclk_i)
     if (~irst_n)
          rd_cnt_r    <= 3'd0;
     else if (hready_i & fsm_rd_addr)
          rd_cnt_r    <= 3'd0;
     else if (hready_i & fsm_rd_rd)
          rd_cnt_r    <= rd_cnt_r + 1'd1;
      else if (hready_i & rd_last_data)
          rd_cnt_r    <= 3'd0;

//wr_cnt_r
always @(posedge hclk_i)
     if (~irst_n)
          wr_cnt_r    <= 3'd0;
     else if (hready_i & fsm_wr_addr)
          wr_cnt_r    <= 3'd0;
     else if (hready_i & fsm_wr_wd)
          wr_cnt_r    <= wr_cnt_r + 1'd1;
      else if (hready_i & wr_last_data)
          wr_cnt_r    <= 3'd0;

reg  [31:0] rd_data_r [ 0 : data_size-1];
//rd_data_r
always @(posedge hclk_i)
      if(~irst_n)
        {rd_data_r[0],rd_data_r[1],rd_data_r[2],rd_data_r[3]}  <= 32'd0;
      else if(main_fsm_r == S1 & (fsm_rd_rd || fsm_rd_lrd) & hready_i)
             rd_data_r    <= hrdata_i;

assign  hwdata_o = (main_fsm_r == S2 & (fsm_wr_wd || fsm_wr_lwd) & hready_i) ? rd_data_r[wr_cnt_r] : 32'b0;
assign  haddr_o = haddr_r;

endmodule

 至此,本文基于AHB总线的master读写设计就完成了。在设计过程中,重要的是画出状态机,并理解每个状态的逻辑及状态与状态间跳转的触发条件。需要理解阻塞赋值和非阻塞赋值。在这里说一下我对阻塞赋值和非阻塞赋值的理解:

(1)非阻塞赋值(需要使用寄存器将值存储起来,使用always块赋值):当前周期时钟上升沿时存储值,下一周期时钟上升沿才会进行赋值操作。(和下一周期的时序也有关系)。使用非阻塞赋值,各个赋值语句在块结束后(下一周期)同步赋值。

 

(2)阻塞赋值(组合逻辑):当前周期时钟上升沿赋值生效,不存储值。使用assign能实时给wire型信号赋值。(在当前周期完成操作)。阻塞语句是顺序执行的。在当前周期,前一个赋值语句执行完才能执行下一个赋值语句,即前一赋值语句的结果能影响下一赋值语句的值。

阻塞赋值的操作符用”=“表示,阻塞赋值时先计算等号右边的值,这时的赋值语句不允许任何别的Verilog语句干扰,直到右边的赋值完成时,把等号右边的值赋值给等号左边,才允许别的赋值语句执行。所以,所谓阻塞赋值是在同一个always块中,其后面的语句从概念上是在前一句赋值语句结束后再开始赋值的。

非阻塞赋值操作符用”<=“表示,非阻塞赋值时,计算"<="右边表达式,赋值操作结束时刻才更新”<=“左边的值。在计算非阻塞赋值的RHS表达式和更新LHS期间,其他verilog语句都能同时计算RHS(右侧)表达式和更新LHS(左侧)值。非阻塞赋值允许其他的Verilog语句同时进行。  (引自 夏宇闻 《Verilog 数字系统设计教程【第2版】》)

                                                                                                                                                         本图引自  https://zhuanlan.zhihu.com/p/57973779

模块输入输出一般都是wire型,内部逻辑可以是wire也可以是reg,一般先对一些内部逻辑信号进行各种操作,最后再将其赋值给输出信号。如本文assign haddr_o = haddr_r;中间对haddr_r进行操作,最后将其赋值给haddr_o。

 

 

   

 

posted @ 2019-10-21 17:18  刘羽冰  阅读(10511)  评论(1编辑  收藏  举报