Menu Close

RISC-V 总线和流水线(2)FII RISC-V CPU总线设计

1. 学习总线设计

学习设计总线是本文的学习目的,在学习时遵循从简单到复杂的设计。本文将分析目前已有的总线来说明为什么需要设计特有的FII RISC-V BUS。首先,设计这个总线的目的是在CPU内部传输数据。

1.1. 复杂但功能强大的AXI总线

AXI(Advanced eXtensible Interface)总线是ARM公司的AMBA(Advanced Microcontroller Bus Architecture)部分协议。其特点是总线接口多,协议全面,操作复杂,且可以实现灵活控制。

上述的AXI总线的优点另一方面来看也是缺点。针对一个专为嵌入式 IoT设计的CPU而言,无论是使用还是调试时,AXI过于庞大和复杂。完全支持像AXI4这样的总线会消耗太多资源,耗电。

1.2. 简单易控制的SPI总线

SPI总线是由1979年Motorola公司发明的同步串行总线,常用于SD(secure digital) card和LCD(liquid crystal display)显示屏通信中。SPI总线的硬件实现也很简单,通讯线比较少,一般只有4条,但不使用SPI作为总线的原因是,在CPU内部,数据传输一般都是并行的,而SPI是串行总线,如果使用SPI总线作为RISC-V内部总线,需要多次串并,并串的转换。

 

通过上述的两个具有代表性的总线可以看出,现有的总线要么复杂难以控制,要么过于简单,不适用于CPU内部传输。因此,在综合考虑了性能,功耗,资源的平衡后,我们选择实现一个小而精的总线。

 

2. FII RISC-V 总线设计

 

相关参考文章:

RISC-V教学教案

 

以下代码模块显示了FII RISC-V总线设计。 具体细节在代码模块中详细注释说明。

//RISCV 总线设计:RIB (riscv internal bus) 总线定义

32bit: rib_addr,     // 地址总线

 

32bit: rib_dout,     // 数据总线

32bit: rib_din,        // 数据总线

 

1bit: rib_valid,          // 控制总线

2bit: rib_ready,     // 控制总线

32bit: rib_we,        // 控制总线

1bit: rib_rd,            // 控制总线

1bit: rib_op,            // 控制总线

 

//RISCV master 总线设计 (cpu 等):verilog代码
output [31:0]   o_rib_maddr,    // master 发送, 32位 地址
output [31:0]   o_rib_mdout,    // master 发送, 32位 数据   ( 写操作 )
input  [31:0]   i_rib_mdin,     // master 接收, 32位 数据   ( 读操作 )
output          o_rib_mvalid,   // master 发出, 用于表示 有 操作(读/写)发生
input  [1:0]    i_rib_mready,   // master 接收, bit[1]= 总线错误, bit[0] 外设准备好
output [3:0]    o_rib_mwe,      // master 发送,写操作信号,每一位表示一个 (单沿)
output          o_rib_mrd,      // master 发送,读操作信号,(单沿)
output          o_rib_mop,      // master 发送,表示每次操作(单沿)

//RISCV slave 总线设计 (外设 等):verilog 代码
input  [31:0]   i_rib_saddr,    // slave 接收, 32位 地址
input  [31:0]   i_rib_sdin,     // slave 接收, 32位 数据   ( 写操作 )
output [31:0]   o_rib_sdout,    // slave 发出, 32位 数据   ( 读操作 )
input           i_rib_svalid,   // slave 接收, 用于表示 有 操作(读/写)发生
output [1:0]    o_rib_sready,   // slave 发出, bit[1]= 总线错误, bit[0] 外设准备好
input  [3:0]    i_rib_swe,      // slave 接收,写操作信号,每一位表示一个 (单沿)
input           i_rib_srd,      // slave 接收,读操作信号,(单沿)
input           i_rib_sop,      // slave 接收,表示每次操作(单沿)

 

3.读写操作分析

接下来,对单独的读写操作进行分析,最简单的单时钟周期总线读操作时序图如图1所示。注意:

  • mvalid,mready[0]同时产生
  • 读信号,地址,读数据和mop操作信号都是单周期

在图中,灰色波形表示无效区域。 蓝色波形表示输出(相对于CPU),绿色波形表示输入(相对于CPU)。

%title插图%num

图1 单时钟周期读操作时序图

 

数值示例如下:对于地址32’h9000_00xx,读取数据(mdin)32’h1234_5678。

 

mrd  = 1,        32'hxxxx_xx78,maddr = 32'h9000_0000,  lb(u)
mrd  = 1,        32'hxxxx_56xx,maddr = 32'h9000_0001,  lb(u)
mrd  = 1,        32'hxx34_xxxx,maddr = 32'h9000_0002,  lb(u)
mrd  = 1,        32'h12xx_xxxx,maddr = 32'h9000_0003,  lb(u)

mrd  = 1,        32'hxxxx_5678, maddr = 32'h9000_0000, lh(u)
mrd  = 1,        32'h1234_xxxx, maddr = 32'h9000_0002, lh(u)

mrd  = 1,        32'h1234_5678,maddr = 32'h9000_0000,  lw

 

单时钟周期总线写操作时序图如图2所示。注意,与单时钟周期总线读操作类似:

  • mvalid,mready[0]同时产生
  • 写信号,地址,写数据和mop操作信号均为单个时钟周期操作

 

%title插图%num

图2 单时钟周期写操作时序图

 

与读取数据示例类似,写入数据示例如下:对于地址32’h9000_00xx,写数据(mdout)32’h1234_5678。

 

mwe = 4'b0001,        32'hxxxx_xx78,maddr= 32'h9000_0000, sb
mwe = 4'b0010,        32'hxxxx_56xx,maddr= 32'h9000_0001, sb
mwe = 4'b0100,        32'hxx34_xxxx,maddr= 32'h9000_0002, sb
mwe = 4'b1000,        32'h12xx_xxxx,maddr= 32'h9000_0003, sb

mwe = 4'b0011,        32'hxxxx_5678,maddr= 32'h9000_0000, sh
mwe = 4'b1100,        32'h1234_xxxx,maddr= 32'h9000_0002, sh

mwe = 4'b1111,        32'h1234_5678,maddr= 32'h9000_0000, sw

 

单时钟周期总线连续读操作,其时序图如图3所示。与单时钟周期读取操作相同:

  • mvalid,mready[0]同时产生
  • 读信号,地址,读数据,mop操作信号均为单时钟周期操作信号

%title插图%num

图3 单时钟周期连续读操作时序图

 

单时钟周期总线连续写操作,其时序图如图4所示:

  • mvalid,mready[0]同时产生
  • 写信号,地址,写数据,mop操作信号均为单时钟周期操作信号

%title插图%num

图4 单时钟周期连续写操作时序图

 

多个时钟周期总线读操作的时序图如图5所示。多个周期意味着ready无法立即响应valid,即外围设备的速度很慢。

  • 如果mvaild为高,则仅在准备好读数据后,将mready [0]拉高
  • 读信号,地址是多个时钟周期操作信号,读数据,mop操作信号均为单时钟周期操作信号

%title插图%num

图5 多个时钟周期读操作时序图

 

多个时钟周期总线写操作的时序图如图6所示。

  • 如果mvaild为高,则仅在准备好写数据后,将mready [0]拉高
  • 写信号,地址和写数据是多个时钟周期操作信号,mop操作信号为单时钟周期

%title插图%num

图6 多个时钟周期写操作时序图

 

多个时钟周期总线连续读操作的时序图如图7所示。

  • 如果mvaild为高,则仅在准备好读数据后,将mready [0]拉高
  • 读信号,地址是多个时钟周期操作信号,读数据,mop操作信号均为单时钟周期操作信号

%title插图%num

图7 多个时钟周期连续读操作时序图

 

多个时钟周期总线连续写操作的时序图如图8所示。

  • 如果mvaild为高,则仅在准备好写数据后,将mready [0]拉高
  • 写信号,地址和写数据是多个时钟周期操作信号,mop操作信号为单时钟周期

%title插图%num

图8 多个时钟周期连续写操作时序图

 

相应的代码模块如下所示,具体细节在代码模块中详细注释说明。

localparam DEV_NUM = 10;



wire [DEV_NUM - 1:0] s_cs;//外围设备片选 

assign s_cs[0] = ( i_rib_maddr[31:12] == DBG_BASEADDR[31:12] )  ? 1'b1 : 1'b0;//jtag调试
assign s_cs[1] = ( i_rib_maddr[31:16] == PLIC_BASEADDR[31:16] ) ? 1'b1 : 1'b0;//外部中断
assign s_cs[2] = ( i_rib_maddr[31:16] == CPU_BASEADDR[31:16] )  ? 1'b1 : 1'b0;//CPU
assign s_cs[3] = ( i_rib_maddr[31:16] == MEM_BASEADDR[31:16] )  ? 1'b1 : 1'b0;//内存
assign s_cs[4] = ( i_rib_maddr[31:16] == TMR_BASEADDR[31:16] )  ? 1'b1 : 1'b0;//计时器中断
assign s_cs[5] = ( i_rib_maddr[31:16] == GPIO_BASEADDR[31:16] ) ? 1'b1 : 1'b0;//GPIO
assign s_cs[6] = ( i_rib_maddr[31:16] == UART_BASEADDR[31:16] ) ? 1'b1 : 1'b0;//UART

//有一些未使用的选项,之后可以进行扩展

assign s_cs[7] = 1'b0;
assign s_cs[8] = 1'b0;
assign s_cs[9] = 1'b0;

//===============================================================================

always @ ( * )
if(!rst_n)
begin

    o_rib_saddr  = 0;
    o_rib_sdin   = 0;
    o_rib_svalid = 0;
    o_rib_swe    = 0;
    o_rib_srd    = 0;
    o_rib_sop    = 0;

end
else
begin

    //主机发送的信息,每个外围设备都可以接收(广播)
    o_rib_saddr  = i_rib_maddr;
    o_rib_sdin   = i_rib_mdout;
    o_rib_svalid = i_rib_mvalid;
    o_rib_swe    = i_rib_mwe;
    o_rib_srd    = i_rib_mrd;
    o_rib_sop    = i_rib_mop;

end

//===============================================================================

wire bus_err_ack = (i_rib_maddr == i_PC) ? 1'b1 : 1'b0;

always @ ( * )
begin

    //外围设备不能同时发送信息
    //如果当前片选被拉高,则相应的外设将返回数据和ready
    //与主机信息传输一起组成一个总线分配器/多路复用器

    case (s_cs)
    10'b00_0000_0001:   // DBG_BASEADDR
    begin

        o_rib_mdin      = i0_rib_sdout;
        o_rib_mready    = i0_rib_sready; 

    end

    10'b00_0000_0010:   // PLIC_BASEADDR
    begin

        o_rib_mdin      = i1_rib_sdout;
        o_rib_mready    = i1_rib_sready; 

    end

    10'b00_0000_0100:   // CPU_BASEADDR
    begin

        o_rib_mdin      = i2_rib_sdout;
        o_rib_mready    = i2_rib_sready; 

    end

    10'b00_0000_1000:   // MEM_BASEADDR
    begin

        o_rib_mdin      = i3_rib_sdout;
        o_rib_mready    = i3_rib_sready; 

    end

    10'b00_0001_0000:   // TMR_BASEADDR
    begin

        o_rib_mdin      = i4_rib_sdout;
        o_rib_mready    = i4_rib_sready; 

    end

    10'b00_0010_0000:   // GPIO_BASEADDR
    begin

        o_rib_mdin      = i5_rib_sdout;
        o_rib_mready    = i5_rib_sready; 

    end

    10'b00_0100_0000:   // UART_BASEADDR
    begin

        o_rib_mdin      = i6_rib_sdout;
        o_rib_mready    = i6_rib_sready; 

    end

    10'b00_1000_0000:
    begin

        o_rib_mdin      = i7_rib_sdout;
        o_rib_mready    = i7_rib_sready; 

    end

    10'b01_0000_0000:
    begin

        o_rib_mdin      = i8_rib_sdout;
        o_rib_mready    = i8_rib_sready; 

    end

    10'b10_0000_0000:
    begin

        o_rib_mdin      = i9_rib_sdout;
        o_rib_mready    = i9_rib_sready; 

    end

    default:
    begin

        o_rib_mdin      = 0;
        o_rib_mready    = {1'b1, bus_err_ack};

    end

    endcase

end

 

Posted in FPGA, RISC-V, RISC-V, RISC-V 教案, 应用开发, 开发板, 教材与教案, 文章
0 0 投票数
Article Rating
订阅评论
提醒
guest
0 评论
内联反馈
查看所有评论

相关链接