本文介绍的是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作为例子进行说明。
前提条件:
- memory和mmio通过地址来区分端口,我分配0x8000_0000-0x9000_0000为memory,0x6000_0000-0x7000_0000为而mmio,这些地址可以在生成rtl的时候修改。
- l2_frontend_bus没有地址。
- memory和mmio端口连接的是rocket-chip和memory、rocket-chip和外设、rocket-chip和registers,这里rocket-chip永远是master,而memory、外设和registers都系slave。
- l2_frontend_bus连接的是其他核和rocket-chip,dma和rocket-chip,这里rocket-chip永远是slave,其他核和dma是master。
- 在没有dcache flush和dcache invalid的功能前,l2_frontend_bus是保持系统数据一致性的总线接口。
关于3)、4)和5)点可能不太容易懂,在这里详细说明。
早期的rocket-chip是没有指令实现dcache flush和dcache invalid的功能,那时l2_frontend_bus是保持系统数据一致性的唯一方法。
系统数据一致性的问题涉及系统架构的内容。我在这里举两个个简单的例子做说明。
例子1前提条件:
- 假设系统中存在rocket-chip、dma,uart和memory。
- 假设存在指令实现dcache flush和dcache invalid的功能。
在上述两个条件下,想利用uart进行数据发送和接收,那么就要按以下步骤完成操作:
发送:
- rocket-chip先将串口发送的数据从memory中读入,在cpu中进行处理,此时数据已经在dcache中。
- 使用dcache flush指令,将dcache中处理过的串口数据刷回至memory中。
- 配置dma(搬运数据的地址和数据的长度)和uart寄存器(发送数据的长度和格式等)。
- 启动dma将memory中处理过的串口数据搬到uart模块中,并将数据通过uart模块发送出去。
- 完成发送后,uart产生中断信号告知rocket-chip,然后再继续后续的程序。
接收:
- 配置dma(搬运数据的地址和数据的长度)和uart寄存器(接收数据的长度和格式等)。
- 启动uart和dma,uart将收到的串口数据通过dma搬到memory中。
- 因为rocket-chip之前可能读过相关memory的值(存串口接收数据的相关地址),且没有flush掉,如果读过,那么cpu会认为在dcache中的数据是最新的,即使uart收到新的数据,也不会从memory中读取新的数据,还是会一直沿用dcache中已存的数据,因此数据的一致性就存在问题。
- 使用dcache invalid指令,将dcache中已存的旧数据无效掉。
- 根据dma产生的中断判断新的数据是否已经完成接收。
- 完成接收后,rocket-chip利用总线向memory读取最新的串口接收数据,如果没有dcache invalid的操作,rocket-chip是不会发起此次读操作的。
上述就是存在dcache flush和dcache invalid指令时,外设发送和接收数据保持数据一致性的大致过程。
例子2前提条件:
- 设系统中存在rocket-chip、dma,uart和memory。
- 假设没有指令实现dcache flush和dcache invalid的功能,只有l2_frontend_bus接口。
在上述两个条件下,想利用uart进行数据发送和接收,那么就要按以下步骤完成操作:
发送:
- rocket-chip先将串口发送的数据从memory中读入,在cpu中进行处理,此时数据已经在dcache中。
- 配置dma(搬运数据的地址和数据的长度)和uart寄存器(发送数据的长度和格式等)。
- 启动dma,dma从l2_frontend_bus接口中发起数据读请求,数据请求在rocket-chip内部完成仲裁后,如果请求的数据存在于dcache中,那么会将数据刷回memory,然后再将处理过的数据从memory中再次读取并交给l2_frontend_bus,最后再移交给dma;如果请求的数据不存在与dcache中,那么会从memory中直接读取数据,并交给l2_frontend_bus接口,最后移交给dma。
- dma将处理过的串口数据搬到uart模块中,并将数据通过uart模块发送出去。
- 完成发送后,uart产生中断信号告知rocket-chip,然后再继续后续的程序。
接收:
- 配置dma(搬运数据的地址和数据的长度)和uart寄存器(接收数据的长度和格式等)。
- 启动uart和dma,uart将收到的串口数据通过dma搬到memory中。
- dma从l2_frontend_bus接口中发起数据写请求,数据写请求在rocket-chip内部完成仲裁后,如果有旧的数据存在于dcache中,那么会将旧的数据刷回memory,然后再将l2_frontend_bus新的数据写入到memory中;如果没有旧的数据存在与dcache中,那么会将l2_frontend_bus新的数据写入到memory中。
- 根据dma产生的中断判断新的数据是否已经完成写入。
- 完成接收后,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) {

本文详细解读了rocket-chip中的L2_frontend_bus,介绍了其在总线协议转换、数据一致性维护(如DCACHE操作)以及与其他接口(如内存、MMIO、其他核和DMA)交互的角色。通过实例阐述了L2前端如何确保系统数据一致性,并提供了测试代码片段以佐证其功能。
最低0.47元/天 解锁文章
3388

被折叠的 条评论
为什么被折叠?



