深入理解AMBA总线(二)APB slave设计

简介: 深入理解AMBA总线(二)APB slave设计

上篇文章给大家介绍了APB协议相关的知识点,本篇文章通过一个实际的APB slave的设计帮助大家巩固对APB的掌握。

APB slave设计Spec

这个设计实例是在ARM公司的ARM DesignStart的CMSDK开发套件中扒出来的。其链接放在下方:

https://link.zhihu.com/?target=https%3A//developer.arm.com/documentation/ddi0479/d/apb-components/apb-example-slaves

其框图如上图所示,这里提一嘴,大家在做数字IC设计的时候,都应该像这样规划好各个模块的连接关系,确定好以后再写代码。该模块是一个基于APB协议完成寄存器配置或读取的设计实例。设计相对比较简单,但不失为一个很好的学习资料。

上面APB相关的信号都介绍过,这里不再重复介绍,其中的ECOREVNUM的意思是ECO revision number,如果没有用到ARM的ECO的话,将该信号固定为全0即可。

右边这个slave_reg实际上就对应我们自己设计的IP的寄存器配置部分。这一部分的接口是native interface,也就是没有考虑通用性的原生接口。想要通过APB总线对其进行配置,就需要通过slave_interface这个模块进行协议转换,进而完成APB协议的传输。我们经常能够看到的apb2reg模块,实际上也是做了这件事。该APB slave有以下的特点:

  • 不支持反压
  • 不支持错误传输
  • 支持4个RW类型的寄存器
  • 支持12个RO类型寄存器
  • 支持字节选通信号

APB slave设计代码

apb_slave_interface代码

下面是slave_interface的关键代码逻辑。我们对其进行分析:

  • 首先由于不支持反压和错误传输,因此将pready固定为1,pslverr固定为0。
  • APB传输进来的paddr可以直接赋给addr,作为读写的地址。
  • read_en需要在psel为1且pwrite为0的时候拉高。这实际上是希望在整个读传输过程中都让read_en信号有效。读者可能就想问了,读应该对应着两个阶段吗?不需要判断吗?实际上读的话,master那边自己控制好就行了,对于slave而言完全可以在第一拍和第二拍都把rdata提供好,这个是没有关系的
  • write_en则对应着setup phase,实际上在这个场景中修改write_en的逻辑让该信号对应着access phase也是可以的
  • 其它的信号直接赋值就可以,应该很好理解
// APB interface
assign   pready  = 1'b1; //always ready. Can be customized to support waitstate if required.
assign   pslverr = 1'b0; //always OKAY. Can be customized to support error response if required.
// register read and write signal
assign  addr = paddr;
assign  read_en  = psel & (~pwrite); // assert for whole apb read transfer
assign  write_en = psel & (~penable) & pwrite; // assert for 1st cycle of write transfer
        // It is also possible to change the design to perform the write in the 2nd
        // APB cycle.   E.g.
        //   assign write_en = psel & penable & pwrite;
        // However, if the design generate waitstate, this expression will result
        // in write_en being asserted for multiple cycles.
assign  byte_strobe = pstrb;
assign  wdata       = pwdata;
assign  prdata      = rdata;

apb_slave_reg代码

其关键逻辑如下所示,我们对其进行分析:

  • 首先由于分为RW寄存器和RO寄存器。这里确定写地址是否在规定区间,同时写使能是否有效,以及byte_strobe信号,来决定要不要写,写哪个字节。
  • 读的话就比较简单了,当读使能有效,根据地址信号决定rdata。实际上这就是个MUX选择逻辑。根据read_en加地址从多个寄存器的Q端选出某一个来。
// Address decoding for write operations
  assign wr_sel[0] = ((addr[(ADDRWIDTH-1):2]==10'b0000000000)&(write_en)) ? 1'b1: 1'b0;
  assign wr_sel[1] = ((addr[(ADDRWIDTH-1):2]==10'b0000000001)&(write_en)) ? 1'b1: 1'b0;
  assign wr_sel[2] = ((addr[(ADDRWIDTH-1):2]==10'b0000000010)&(write_en)) ? 1'b1: 1'b0;
  assign wr_sel[3] = ((addr[(ADDRWIDTH-1):2]==10'b0000000011)&(write_en)) ? 1'b1: 1'b0;
 // register write, byte enable
 // Data register: data0
  always @(posedge pclk or negedge presetn)
    begin
    if (~presetn)
      begin
        data0 <= {32{1'b0}}; // Reset data 0 to 0x00000000
      end
    else if (wr_sel[0])
      begin
        if (byte_strobe[0])
           data0[ 7: 0] <= wdata[ 7: 0];
        if (byte_strobe[1])
           data0[15: 8] <= wdata[15: 8];
        if (byte_strobe[2])
           data0[23:16] <= wdata[23:16];
        if (byte_strobe[3])
           data0[31:24] <= wdata[31:24];
      end
    end
 // register read
always @ (read_en or addr or data0 or data1 or data2 or data3 or ecorevnum)
 begin
   case (read_en)
     1'b1:
     begin
       if (addr[11:4] == 8'h00) begin
         case(addr[3:2])
           2'b00: rdata =  data0;
           2'b01: rdata =  data1;
           2'b10: rdata =  data2;
           2'b11: rdata =  data3;
           default: rdata = {32{1'bx}};
         endcase
       end
       else if (addr[11:6] == 6'h3F) begin
         case(addr[5:2])
          // Peripheral IDs and Component IDs.
          // AHB example slave has part number of 818
         4'b0100: rdata = ARM_CMSDK_APB4_EG_SLAVE_PID4; // 0xFD0 : PID 4
         4'b0101: rdata = ARM_CMSDK_APB4_EG_SLAVE_PID5; // 0xFD4 : PID 5
         4'b0110: rdata = ARM_CMSDK_APB4_EG_SLAVE_PID6; // 0xFD8 : PID 6
         4'b0111: rdata = ARM_CMSDK_APB4_EG_SLAVE_PID7; // 0xFDC : PID 7
         4'b1000: rdata = ARM_CMSDK_APB4_EG_SLAVE_PID0; // 0xFE0 : PID 0 APB Example slave part number[7:0]
         4'b1001: rdata = ARM_CMSDK_APB4_EG_SLAVE_PID1; // 0xFE4 : PID 1 [7:4] jep106_id_3_0. [3:0] part number [11:8]
         4'b1010: rdata = ARM_CMSDK_APB4_EG_SLAVE_PID2; // 0xFE8 : PID 2 [7:4] revision, [3] jedec_used. [2:0] jep106_id_6_4
         4'b1011: rdata ={ARM_CMSDK_APB4_EG_SLAVE_PID3[31:8], ecorevnum[3:0], 4'h0};
                                         // 0xFEC : PID 3 [7:4] ECO rev number, [3:0] modification number
         4'b1100: rdata = ARM_CMSDK_APB4_EG_SLAVE_CID0; // 0xFF0 : CID 0
         4'b1101: rdata = ARM_CMSDK_APB4_EG_SLAVE_CID1; // 0xFF4 : CID 1 PrimeCell class
         4'b1110: rdata = ARM_CMSDK_APB4_EG_SLAVE_CID2; // 0xFF8 : CID 2
         4'b1111: rdata = ARM_CMSDK_APB4_EG_SLAVE_CID3; // 0xFFC : CID 3
         // Note : Customer changing the design should modify
         // - jep106 value (www.jedec.org)
         // - part number (customer define)
         // - Optional revision and modification number (e.g. rXpY)
         4'b0000, 4'b0001,4'b0010,4'b0011: rdata = {32'h00000000}; // default
         default: rdata =  {32{1'bx}};  // x propagation
         endcase
       end
       else begin
         rdata = {32'h00000000}; // default
       end
   end
   1'b0:
     begin
       rdata =  {32{1'b0}};
     end
   default:
     begin
       rdata =  {32{1'bx}};
     end
   endcase
 end

完整的代码链接如下,需要大家阅读并理解这些代码。

https://link.zhihu.com/?target=https%3A//github.com/ForrestBlue/cortexm0ds/blob/master/logical/cmsdk_apb4_eg_slave/verilog/cmsdk_apb4_eg_slave_reg.v

APB slave mux设计

这里再给大家介绍一下APB slave mux的概念,如下图所示,基于APB slave mux我们可以快速地将多个apb slave连接在上面。在实际的设计当中都是采用这样的方式,连接多个slave的。一般我们管这种模块叫做interconnect,顾名思义,将不同的模块连接起来。而APB的interconnect只能连接一个master,因此继续管他叫interconnect感觉差了点意思。所以一般就叫它slave mux了。

其逻辑框图如上图所示,非常的简单啊。就是一个master和多个slave,通过这个额外的DECODE4bit进行16选1。

其关键代码如下所示

  • 根据PSEL是否有效以及DECODE4BIT的值,完成16选1,PSEL0~PSEL15有一个或者0个拉高。
  • PREADYm默认为1,当PSEL为1的时候,根据译码结果选择相应的PREADY信号(当端口没有使能的时候en[x] == 0, 对应的PREADYx信号不会被选择)。
  • PSLVERR和PRDATA,选中谁就取谁的。
assign PSEL0   = PSEL & dec[ 0] & en[ 0];
  assign PSEL1   = PSEL & dec[ 1] & en[ 1];
  assign PSEL2   = PSEL & dec[ 2] & en[ 2];
//省略3~15
  assign PREADY  = ~PSEL |
                   ( dec[ 0]  & (PREADY0  | ~en[ 0]) ) |
                   ( dec[ 1]  & (PREADY1  | ~en[ 1]) ) |
                   ( dec[ 2]  & (PREADY2  | ~en[ 2]) ) |
//省略3~15
  assign PSLVERR = ( PSEL0  & PSLVERR0  ) |
                   ( PSEL1  & PSLVERR1  ) |
                   ( PSEL2  & PSLVERR2  ) |
//省略3~15
  assign PRDATA  = ( {32{PSEL0 }} & PRDATA0  ) |
                   ( {32{PSEL1 }} & PRDATA1  ) |
                   ( {32{PSEL2 }} & PRDATA2  ) |
//省略3~15

这套代码的缺点或者说优点是,它是用组合逻辑做的,逻辑非常的简单。实际上就是多选1,一般来说APB的时钟频率很低,所以增加了一定的组合逻辑级数也不会出现时钟违例。这样做还可以节省一个时钟周期,大家也可以用时序逻辑去做,思路是类似的。还有一个问题就是这个模块没有PENABLE信号,这个其实挺致命,大家可以手动加上,跟PSEL的逻辑基本是一模一样的,非常简单。

完整的代码连接在下方,这一系列的代码其实都很适合学习。建议想做SoC的朋友都可以学一遍,其中的apb_gpio、apb_uart、apb_timer等都是SoC必备的模块,通过学习这些代码可以掌握真实的SoC中这些简单外设是如何工作起来的。并且这些ARM官方都是有对应文档的,结合文档学事半功倍,文档链接在这篇文章的最上方。

https://github.com/ForrestBlue/cortexm0ds/blob/master/logical/cmsdk_apb_slave_mux/verilog/cmsdk_apb_slave_mux.vgithub.com/ForrestBlue/cortexm0ds/blob/master/logical/cmsdk_apb_slave_mux/verilog/cmsdk_apb_slave_mux.v

欢迎和我一起学习AMBA总线,完整的专栏在这里:


AMBA学习www.zhihu.com/column/c_1663245806869


目录
相关文章
|
存储 网络性能优化 vr&ar
深入理解AMBA总线(十七)AXI是如何提高性能的
深入理解AMBA总线(十七)AXI是如何提高性能的
1983 1
|
安全 物联网 数据安全/隐私保护
深入理解AMBA总线协议(AXI总结篇)
深入理解AMBA总线协议(AXI总结篇)
1536 1
|
芯片
深入理解AMBA总线(一)APB总线入门(上)
深入理解AMBA总线(一)APB总线入门
1068 0
|
SoC
深入理解AMBA总线(十六)AXI设计的关键问题(二)
深入理解AMBA总线(十六)AXI设计的关键问题(二)
819 0
深入理解AMBA总线(十六)AXI设计的关键问题(二)
|
存储 安全
深入理解AMBA总线(四)AHB-lite总线
深入理解AMBA总线(四)AHB-lite总线
1105 0
|
存储 SoC
深入理解AMBA总线(十一)AXI协议导论
深入理解AMBA总线(十一)AXI协议导论
1684 0
|
SoC
深入理解AMBA总线(七)AHB设计要点和AHB2APB同步桥设计前言
深入理解AMBA总线(七)AHB设计要点和AHB2APB同步桥设计前言
490 0
深入理解AMBA总线(七)AHB设计要点和AHB2APB同步桥设计前言
|
异构计算 SoC
深入理解AMBA总线(三)APB interconnect的补充
深入理解AMBA总线(三)APB interconnect的补充
274 0
|
缓存 SoC
深入理解AMBA总线(八)AHB2APB同步桥设计
深入理解AMBA总线(八)AHB2APB同步桥设计
739 0
|
存储
深入理解AMBA总线(六)AHB-lite Slave响应和其它控制信号
深入理解AMBA总线(六)AHB-lite Slave响应和其它控制信号
798 0