Rocket-chip-l2bus

本文介绍的是rocket-chip l2_frontend_bus的作用。

在介绍l2_frontend_bus前,需要有的基础知识:AHB-Lite、AHB和AXI4协议的总线基础
具体的协议文档可以上ARM官网下载。在l2_frontend_bus功能说明中,不会对AXI4、AHB和TileLink协议进行讲解,默认大家都已经知道这些协议的核心内容。

rocket-chip在总线上可以生成三种协议,分别是AHB、AXI4和TileLink。
默认采用的是AXI4,AHB和TileLink的底层scala代码是有的,但需要自行修改连接关系,从而实现AHB和TileLink总线协议。

为了减少麻烦和避免一些不稳定的因素,所以我会直接采用默认的AXI4作为例子进行说明。

前提条件:

  1. memory和mmio通过地址来区分端口,我分配0x8000_0000-0x9000_0000为memory,0x6000_0000-0x7000_0000为而mmio,这些地址可以在生成rtl的时候修改。
  2. l2_frontend_bus没有地址。
  3. memory和mmio端口连接的是rocket-chip和memory、rocket-chip和外设、rocket-chip和registers,这里rocket-chip永远是master,而memory、外设和registers都系slave。
  4. l2_frontend_bus连接的是其他核和rocket-chip,dma和rocket-chip,这里rocket-chip永远是slave,其他核和dma是master。
  5. 在没有dcache flush和dcache invalid的功能前,l2_frontend_bus是保持系统数据一致性的总线接口。

关于3)、4)和5)点可能不太容易懂,在这里详细说明。
早期的rocket-chip是没有指令实现dcache flush和dcache invalid的功能,那时l2_frontend_bus是保持系统数据一致性的唯一方法。

系统数据一致性的问题涉及系统架构的内容。我在这里举两个个简单的例子做说明。

例子1前提条件:

  1. 假设系统中存在rocket-chip、dma,uart和memory。
  2. 假设存在指令实现dcache flush和dcache invalid的功能。

在上述两个条件下,想利用uart进行数据发送和接收,那么就要按以下步骤完成操作:

发送:

  1. rocket-chip先将串口发送的数据从memory中读入,在cpu中进行处理,此时数据已经在dcache中。
  2. 使用dcache flush指令,将dcache中处理过的串口数据刷回至memory中。
  3. 配置dma(搬运数据的地址和数据的长度)和uart寄存器(发送数据的长度和格式等)。
  4. 启动dma将memory中处理过的串口数据搬到uart模块中,并将数据通过uart模块发送出去。
  5. 完成发送后,uart产生中断信号告知rocket-chip,然后再继续后续的程序。

接收:

  1. 配置dma(搬运数据的地址和数据的长度)和uart寄存器(接收数据的长度和格式等)。
  2. 启动uart和dma,uart将收到的串口数据通过dma搬到memory中。
  3. 因为rocket-chip之前可能读过相关memory的值(存串口接收数据的相关地址),且没有flush掉,如果读过,那么cpu会认为在dcache中的数据是最新的,即使uart收到新的数据,也不会从memory中读取新的数据,还是会一直沿用dcache中已存的数据,因此数据的一致性就存在问题。
  4. 使用dcache invalid指令,将dcache中已存的旧数据无效掉。
  5. 根据dma产生的中断判断新的数据是否已经完成接收。
  6. 完成接收后,rocket-chip利用总线向memory读取最新的串口接收数据,如果没有dcache invalid的操作,rocket-chip是不会发起此次读操作的。

上述就是存在dcache flush和dcache invalid指令时,外设发送和接收数据保持数据一致性的大致过程。

例子2前提条件:

  1. 设系统中存在rocket-chip、dma,uart和memory。
  2. 假设没有指令实现dcache flush和dcache invalid的功能,只有l2_frontend_bus接口。

在上述两个条件下,想利用uart进行数据发送和接收,那么就要按以下步骤完成操作:

发送:

  1. rocket-chip先将串口发送的数据从memory中读入,在cpu中进行处理,此时数据已经在dcache中。
  2. 配置dma(搬运数据的地址和数据的长度)和uart寄存器(发送数据的长度和格式等)。
  3. 启动dma,dma从l2_frontend_bus接口中发起数据读请求,数据请求在rocket-chip内部完成仲裁后,如果请求的数据存在于dcache中,那么会将数据刷回memory,然后再将处理过的数据从memory中再次读取并交给l2_frontend_bus,最后再移交给dma;如果请求的数据不存在与dcache中,那么会从memory中直接读取数据,并交给l2_frontend_bus接口,最后移交给dma。
  4. dma将处理过的串口数据搬到uart模块中,并将数据通过uart模块发送出去。
  5. 完成发送后,uart产生中断信号告知rocket-chip,然后再继续后续的程序。

接收:

  1. 配置dma(搬运数据的地址和数据的长度)和uart寄存器(接收数据的长度和格式等)。
  2. 启动uart和dma,uart将收到的串口数据通过dma搬到memory中。
  3. dma从l2_frontend_bus接口中发起数据写请求,数据写请求在rocket-chip内部完成仲裁后,如果有旧的数据存在于dcache中,那么会将旧的数据刷回memory,然后再将l2_frontend_bus新的数据写入到memory中;如果没有旧的数据存在与dcache中,那么会将l2_frontend_bus新的数据写入到memory中。
  4. 根据dma产生的中断判断新的数据是否已经完成写入。
  5. 完成接收后,rocket-chip利用总线向memory读取最新的串口接收数据。

上述就是利用l2_frontend_bus接口,外设发送和接收数据保持数据一致性的大致过程。
因此dcache flush指令、dcache invalid指令和l2_frontend_bus是保持系统数据一致性的方法。

测试代码如下:

#include "encoding.h"
#include "L1Dcache.h"

#define U32 *(volatile unsigned int *)
#define DEBUG_SIG   0x70000000
#define DEBUG_VAL   0x70000004

//--------------------------------------------------------------------------
// handle_trap function

void handle_trap()
{
    asm volatile ("nop");
    while(1);
}

//--------------------------------------------------------------------------
// Main

void main()
{
    unsigned int i;
    
    for(i=0;i<10;i++)
    {
        U32(0x60001000+4*i) = i+0x1234;
        U32(0x80001000+4*i) = i+0x10086;
    }  

    for(i=0;i<10;i++)
    {
        U32(0x80002000+4*i) = U32(0x80001000+4*i);
        U32(0x60002000+4*i) = U32(0x60001000+4*i);
    }

    for(i=0;i<10;i++)
    {
        U32(0x80002000+4*i) = U32(0x60001000+4*i);
        U32(0x60002000+4*i) = U32(0x80001000+4*i);
    }

    while(1) {asm volatile ("wfi");}
}

测试代码功能:

  1. 往0x60001000-0x60001040地址灌初始数据。
  2. 往0x80001000-0x80001040地址灌初始数据。
  3. 读0x80001000-0x80001040地址的值写入到0x80002000-0x80002040地址中,memory->memory。
  4. 读0x60001000-0x60001040地址的值写入到0x60002000-0x60002040地址中,mmio->mmio。
  5. 读0x60001000-0x60001040地址的值写入到0x80002000-0x80002040地址中,mmio->memory。
  6. 读0x80001000-0x80001040地址的值写入到0x60002000-0x60002040地址中,memory->mmio。
  7. 进行死循环,testbench中进行l2_frontend_bus->mmio和l2_frontend_bus->memory的操作。

下面进一步讨论rocket-chip三种接口的具体作用。

  1. 为了方便分析,所以增加了具体的对应编号。
  2. 源端是指数据的来源,可读可写;目标端是数据的去向,也是可读可写,但属性和源端相反,例如从0x8000_0000读数据,然后写入0x8000_1000中,那么源端就是memory,属性为read,目标端就是memory,属性为write,其他端口类似。
  3. memory port是以cache line的长度进行数据读写的;mmio是以单word的长度进行数据读写的。
  4. C、F和I是不能实现的,这里只是将它列出来,因为l2_frontend_bus永远不会成为目标端,所以在当目标端为l2_frontend_bus时,全部功能都是×的,这里上面已经说明,l2_frontend_bus另一端是作为master的,rocket-chip是作为slave的。
  5. 带有(*)的地方,非常特别,虽然写的方式是burst,但改变的数据却是某个具体地址的word,下面会用波形图进行说明。
  6. l2_frontend_bus能操作rocket-chip所有带物理地址的registers和memory,也就间接可以控制memory port和mmio port,同时PLIC的registers也能控制。
  7. E、G和H会在后面重点分析。

rocket-chip总线接口说明如下表:

编号源端Read BurstRead SingleWrite BurstWrite Single目标端Read BurstRead SingleWrite BurstWrite Single
Amemory××memory××
Bmemory××mmio××
Cmemory××l2_frontend_bus××××
Dmmio××mmio××
Emmio××memory×√(*)×
Fmmio××l2_frontend_bus××××
Gl2_frontend_busmmio
Hl2_frontend_busmemory××
Il2_frontend_bus××××l2_frontend_bus××××

下面对上表作一个简单分析。

  • 由ABDE可知,如果memroy和mmio是由rocket-chip自由去控制的(非l2_frontend_bus控制),那么memory的读写都是按burst来进行的,mmio的读写都是按single来进行的。
  • E中memory的写是按burst进行,但修改的数据则按具体地址进行修改。如下图。在这里插入图片描述由于mmio的单次操作和memory的单次操作间隔太长了,所以不好截图,只能截取上面的这图。上图中,黄色是mmio的单次操作,是ar操作,读取0x60001004的值,为0x1235。红色为memory的单次操作,是aw操作,写到0x80002004的位置,红色箭头是为了显示数据更改的过程。从上图可以看到,mmio都是以单word长度进行读写的,而memory是以cache line长度进行读写的,所以memory中写的数据是一连串的,它们的地址分别是0x80002000,0x80002004,0x80002008……。因为此次单次操作是读0x60001004的值,写到0x80002004中,所以只有0x80002004地址的数据被修改了,因此写的一连串数据中只有第二个被修改,第一个是之前写0x80002000时被修改的,第三个以后的还没有被修改,所以保留之前的数值,可以看到第三个数据原值为0x10088。
  • 通过l2_frontend_bus控制mmio接口时,mmio是支持burst和single操作的,和rocket-chip控制时不一样。波形如下图。
    在这里插入图片描述
    黄色箭头:l2_frontend_bus single write -> mmio。
    橙色箭头:l2_frontend_bus burst write -> mmio。
    蓝色箭头:l2_frontend_bus single read -> mmio。
    红色箭头:l2_frontend_bus burst read -> mmio。
    白色箭头:burst和single操作的切换信号。
    进行的是先写后读的操作,写和读的地址是一一对应的,所以数据的一致性能证明读写操作是否正确。写的是一个随机数,比对读出后的结果是正确的。但需要注意的是,虽然执行的是burst操作,但并不是完全连续的burst操作,是分开了几拍进行的,由l2_frontend_bus_axi4_0_w_ready和l2_frontedn_bus_axi4_0_r_valid可以看到,这是由TileLink2AXI4转换代码决定的。
  • 通过l2_frontend_bus控制memory接口时,memory只支持single操作的,和rocket-chip控制时不一样。波形如下图。这个结果和我的预期也不同,我预期的是同时支持burst和single操作。
    在这里插入图片描述
    红色箭头:l2_frontend_bus single write -> memory。
    黄色箭头:l2_frontend_bus burst write -> memory。
    蓝色箭头:l2_frontend_bus single read -> memory。
    紫色箭头:l2_frontend_bus burst read -> memory。
    和第3点一样,通过数据的一致性来证明读写操作是否正确。同理,通过l2_frontend_bus_axi4_0_w_ready和l2_frontedn_bus_axi4_0_r_valid可以看到,memory port那端每次都是进行single操作的,即使l2_frontend_bus这端采用的是burst操作。

关于rocket-chip三种接口的一些结论:

  1. rocket-chip使用memory端口是burst操作的,以cache line长度为单位,建议连接memory(rom/sram)。
  2. rocket-chip使用mmio端口是single操作的,以word为单位,建议连接外设/寄存器。
  3. l2_frontend_bus能操作rocket-chip所有带物理地址的空间,包括全部registers和memory,也就间接可以控制memory port和mmio port,还有cpu内部的registers,建议连接保持数据一致性的模块/debug模块(因为可以访问全物理地址,所以debug功能是很好的选择)。
  4. l2_frontend_bus的作用是保持系统的数据一致性,功能类似于dcache flush指令和dcache invalid指令。使用情况需要根据系统的总架构而定。
  5. l2_frontend_bus的效率性能需要额外做实验来确定,这是另一个需要研究的方向。
  6. l2_frontend_bus的word/half word/byte的性能分析需要额外做实验来确定,这是另一个需要研究的方向。

最后附上部分testbench的代码,需要说明的是,此部分代码只为了证明l2_frontend_bus的功能,是不全面的AXI4协议,并不能作为完整的AXI4接口代码来使用。同时因为没有进行过完全充分的验证,所以不排除有bug的存在。

  /**************************/
  /*      Front Port        */
  /**************************/
  reg           mult; 

  reg           aw_valid;
  reg   [31:0]  aw_addr;
  reg           w_valid;
  reg   [63:0]  w_data;
  reg           w_last;

  reg           ar_valid;
  reg   [31:0]  ar_addr;

  reg           l2bus_start = 1'b0;
  reg   [10:0]  cnt;
  
  initial #(7000) l2bus_start = 1'b1;

  always @ (posedge clock or negedge reset) begin
      if(reset == 1'b1) begin
          cnt <= 11'd0;
      end else if(cnt == 11'h6FF) begin
          cnt <= 11'd0;
          $finish(2);
      end else if (l2bus_start &&
                   (l2_frontend_bus_axi4_0_aw_ready == 1'b1 ||
                    l2_frontend_bus_axi4_0_w_ready == 1'b1)
                  ) begin
          cnt <= cnt + 1'b1;
      end
  end
  
  always @ (posedge clock or negedge reset) begin
      if(reset == 1'b1) begin
          mult <= 1'b0;
      end else if(cnt[7] == 1'b0 && cnt[10] != 1'b1) begin
          mult <= 1'b0; 
      end else if(cnt[7] == 1'b1 && cnt[10] != 1'b1) begin
          mult <= 1'b1; 
      end
  end

  always @ (posedge clock or negedge reset) begin
      if(reset == 1'b1) begin
          aw_valid <= 1'b0;
      end else if(aw_valid == 1'b1) begin
          aw_valid <= 1'b0;
      end else if(cnt[4:0] == 5'b10000 && 
                  cnt[8]   == 1'b0 &&
                  cnt[10]  != 1'b1 &&
                  mult     == 1'b0 &&
                  l2_frontend_bus_axi4_0_aw_ready == 1'b1) begin
          aw_valid <= 1'b1; 
      end else if(cnt[4:0] == 5'b01111 && 
                  cnt[8]   == 1'b0 &&
                  cnt[10]  != 1'b1 &&
                  mult     == 1'b1 &&
                  l2_frontend_bus_axi4_0_aw_ready == 1'b1) begin
          aw_valid <= 1'b1; 
      end
  end

  always @ (posedge clock or negedge reset) begin
      if(reset == 1'b1) begin
          w_valid <= 1'b0;
      end else if(w_valid == 1'b1 && mult == 1'b1 && cnt[3:0] == 4'b1111 &&
                  l2_frontend_bus_axi4_0_w_ready == 1'b1) begin
          w_valid <= 1'b0;
      end else if(w_valid == 1'b1 && mult == 1'b0) begin
          w_valid <= 1'b0;
      end else if(cnt[4:0] == 5'b10000 && 
                  cnt[8]   == 1'b0 &&
                  cnt[10]  != 1'b1 &&
                  mult     == 1'b0 &&
                  l2_frontend_bus_axi4_0_w_ready == 1'b1) begin
          w_valid <= 1'b1; 
      end else if(cnt[4:0] == 5'b01111 && 
                  cnt[8]   == 1'b0 &&
                  cnt[10]  != 1'b1 &&
                  mult     == 1'b1 &&
                  l2_frontend_bus_axi4_0_w_ready == 1'b1) begin
          w_valid <= 1'b1; 
      end
  end

  always @ (posedge clock or negedge reset) begin
      if(reset == 1'b1) begin
          aw_addr <= 32'h60000000;
      end else if(cnt[9:0] == 10'h200) begin
          aw_addr <= 32'h80000000;
      end else if(aw_valid && mult == 1'b0) begin
          aw_addr <= aw_addr + 3'h4;
      end else if(aw_valid && mult == 1'b1) begin
          aw_addr <= aw_addr + 8'h40;
      end
  end

  always @ (posedge clock or negedge reset) begin
      if(reset == 1'b1) begin
          w_data <= 64'h0;
      end else if(cnt[8:7] == 2'b00 && 
                  cnt[4:0] == 5'b10000 && 
                  cnt[10]  != 1'b1 &&
                  l2_frontend_bus_axi4_0_w_ready == 1'b1) begin
          w_data <= {2{$random()}};
      end else if(cnt[8:7] == 2'b01 && 
                  cnt[4]   == 1'b1 && 
                  cnt[10]  != 1'b1 &&
                  l2_frontend_bus_axi4_0_w_ready == 1'b1) begin
          w_data <= {2{$random()}};
      end
  end

  always @ (posedge clock or negedge reset) begin
      if(reset == 1'b1) begin
          w_last <= 1'b0;
      end else if(w_last == 1'b1 && mult == 1'b1 && cnt[3:0] == 4'b1111 &&
                  l2_frontend_bus_axi4_0_w_ready == 1'b1) begin
          w_last <= 1'b0;
      end else if(w_last == 1'b1 && mult == 1'b0) begin
          w_last <= 1'b0;
      end else if(cnt[3:0] == 4'b1110 && 
                  cnt[8:7] == 2'b01 &&
                  cnt[10]  != 1'b1 &&
                  w_valid  == 1'b1 &&
                  l2_frontend_bus_axi4_0_w_ready == 1'b1) begin
          w_last <= 1'b1; 
      end else if(cnt[4:0] == 5'b10000 && 
                  cnt[8:7] == 2'b00 &&
                  cnt[10]  != 1'b1 &&
                  l2_frontend_bus_axi4_0_w_ready == 1'b1) begin
          w_last <= 1'b1; 
      end
  end

  always @ (posedge clock or negedge reset) begin
      if(reset == 1'b1) begin
          ar_valid <= 1'b0;
      end else if(ar_valid == 1'b1) begin
          ar_valid <= 1'b0;
      end else if(cnt[4:0] == 5'b10000 && 
                  cnt[8]   == 1'b1 &&
                  mult     == 1'b0 &&
                  l2_frontend_bus_axi4_0_ar_ready == 1'b1) begin
          ar_valid <= 1'b1; 
      end else if(cnt[5:0] == 6'b100000 && 
                  cnt[8]   == 1'b1 &&
                  mult     == 1'b1 &&
                  l2_frontend_bus_axi4_0_ar_ready == 1'b1) begin
          ar_valid <= 1'b1; 
      end     
  end

  always @ (posedge clock or negedge reset) begin
      if(reset == 1'b1) begin
          ar_addr <= 32'h60000000;
      end else if(cnt[9:0] == 10'h200) begin
          ar_addr <= 32'h80000000;
      end else if(ar_valid && mult == 1'b0) begin
          ar_addr <= ar_addr + 3'h4;
      end else if(ar_valid && mult == 1'b1) begin
          ar_addr <= ar_addr + 8'h40;
      end
  end

  assign l2_frontend_bus_axi4_0_aw_valid      = aw_valid; 		
  assign l2_frontend_bus_axi4_0_aw_bits_id    = mult ? 8'd0 : 8'd0; 	
  assign l2_frontend_bus_axi4_0_aw_bits_addr  = aw_addr; 	
  assign l2_frontend_bus_axi4_0_aw_bits_len   = mult ? 8'hF : 8'd0; 	
  assign l2_frontend_bus_axi4_0_aw_bits_size  = mult ? 3'd2 : 3'd2; 	
  assign l2_frontend_bus_axi4_0_aw_bits_burst = mult ? 2'd1 : 2'd1; 	
  assign l2_frontend_bus_axi4_0_aw_bits_lock  = mult ? 1'd0 : 1'd0; 	
  assign l2_frontend_bus_axi4_0_aw_bits_cache = mult ? 4'd0 : 4'd0; 	
  assign l2_frontend_bus_axi4_0_aw_bits_prot  = mult ? 3'd1 : 3'd1; 	
  assign l2_frontend_bus_axi4_0_aw_bits_qos   = mult ? 4'd0 : 4'd0; 	

  assign l2_frontend_bus_axi4_0_w_valid       = w_valid; 	
  assign l2_frontend_bus_axi4_0_w_bits_data   = w_data; 	
  assign l2_frontend_bus_axi4_0_w_bits_strb   = mult ? 8'hFF: 8'hFF; 	
  assign l2_frontend_bus_axi4_0_w_bits_last   = w_last; 	

  assign l2_frontend_bus_axi4_0_ar_valid      = ar_valid; 	
  assign l2_frontend_bus_axi4_0_ar_bits_id    = mult ? 8'd0 : 4'd2; 	
  assign l2_frontend_bus_axi4_0_ar_bits_addr  = ar_addr;
  assign l2_frontend_bus_axi4_0_ar_bits_len   = mult ? 8'hF : 8'd0; 	
  assign l2_frontend_bus_axi4_0_ar_bits_size  = mult ? 3'd2 : 3'd2; 	
  assign l2_frontend_bus_axi4_0_ar_bits_burst = mult ? 2'd1 : 2'd1; 	
  assign l2_frontend_bus_axi4_0_ar_bits_lock  = mult ? 1'd0 : 1'd0; 	
  assign l2_frontend_bus_axi4_0_ar_bits_cache = mult ? 4'd0 : 4'd0; 	
  assign l2_frontend_bus_axi4_0_ar_bits_prot  = mult ? 3'd1 : 3'd1; 	
  assign l2_frontend_bus_axi4_0_ar_bits_qos   = mult ? 4'd0 : 4'd0; 	
                                                                                
  assign l2_frontend_bus_axi4_0_r_ready       = 1'b1; 		

  assign l2_frontend_bus_axi4_0_b_ready       = 1'b1; 		
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值