Rocket-chip-RoCC(6)

下面是对CharacterCount模块的具体实现进行说明。

3、CharacterCount

类CharacterCountExample,需要输入opcodes参数,里面使用new创建CharacterCountExampleModuleImp的对象。声明atlNode为TLClientNode的类,用于TileLink协议,TLClientNode应该是作为master方的,用于发起数据的请求 。

class  CharacterCountExample(opcodes: OpcodeSet)(implicit p: Parameters) extends LazyRoCC(opcodes) {
  override lazy val module = new CharacterCountExampleModuleImp(this)
  override val atlNode = TLClientNode(Seq(TLClientPortParameters(Seq(TLClientParameters("CharacterCountRoCC")))))
}

CharacterCountExampleModuleImp类中混入LazyRoCCModuleImp、HasL1CacheParameters和HasCoreParameters。

  • 声明cacheParams值,如果存在tileParams.icache(存在icache),则cacheParams为1,不然为None(val cacheParams = tileParams.icache.get这句含义我也不太确定),但可以知道的是CharacterCountExampleModuleImp类的下文没有使用到cacheParams的值,所以暂时先这样理解。如果有明确知道的请告诉我,我再修改补充,谢谢。
  • 声明私有的值blockOffset,是blockOffBits的值,而追下去可以找到:
def blockOffBits = lgCacheBlockBytes
def lgCacheBlockBytes = log2Up(cacheBlockBytes)
def cacheBlockBytes = p(CacheBlockBytes)
class WithCacheBlockBytes(linesize: Int) extends Config((site, here, up) => {
  case CacheBlockBytes => linesize
})
  • 也就是blockOffset的值可以通过WithCacheBlockBytes()来设定的。
  • 声明私有的值beatOffset,是以2为底(cacheDataBits/8)的对数。
  • 声明needle为寄存器,位宽为8。
  • 声明addr为寄存器,位宽为coreMaxAddrBits。
  • 声明count为寄存器,位宽为xLen。
  • 声明resp_rd为寄存器,位宽和io.resp.bits.rd一致,应该是5。
  • 声明addr_block为addr的某些位,应该是addr[31:6]。
  • 声明offset为addr的某些位,应该是addr[5:0]。
  • 声明next_addr为(addr_block+1)的值左移6位。
  • 声明状态机的状态,分别为s_idle、s_acq、s_gnt、s_check和s_resp。
  • 声明state寄存器,用于存储状态机的状态,初始值为s_idle。
  • 声明tl_out值,将与core tilelink总线连接的信号atlNode.out(0)赋给它,这里的edgesOut我理解为边缘触发的意思。
  • 将tl_out.d.bits各信号(tilelink的d组信号,有多个信号并非只有一个信号)赋值给gnt,即gnt为d组信号的组合。
  • 声明recv_data为寄存器,位宽为cacheDataBits。
  • 声明recv_beat为寄存器,位宽是以2为底(cacheDataBeats+1)的对数,且初始值为0。
  • 声明一维数组data_bytes,深度为cacheDataBits/8,i为深度减1。当i=0时,data_bytes[0]= recv_data(7,0),当i=1时,data_bytes[1]= recv_data(15,8),当i=2时,data_bytes[2]= recv_data(23,16),当i=3时,data_bytes[3]= recv_data(31,24)。
  • 声明zero_match信号组,当data_bytes[0] = 0,则zero_match[0]为1,当data_bytes[1] = 0,则zero_match[1]为1,当data_bytes[2] = 0,则zero_match[2]为1,当data_bytes[3] = 0,则zero_match[3]为1。
    声明needle_match信号组,当data_bytes[0] = needle,则needle_match[0]为1,当data_bytes[1] = needle,则needle_match[1]为1,当data_bytes[2] = needle,则needle_match[2]为1,当data_bytes[3] = needle,则needle_match[3]为1。
  • 声明first_zero的值,对zero_match信号组进行优先级解码。即当{zero_match[0],zero_match[1],zero_match[2],zero_match[3]}={1’b1,1’bx,1’bx,1’bx}时,first_zero=0;当{zero_match[0],zero_match[1],zero_match[2],zero_match[3]}={1’b0,1’b1,1’bx,1’bx}时,first_zero=1;当{zero_match[0],zero_match[1],zero_match[2],zero_match[3]}={1’b0,1’b0,1’b1,1’bx}时,first_zero=2;当{zero_match[0],zero_match[1],zero_match[2],zero_match[3]}={1’b0,1’b0,1’b0,1’bx}时,first_zero=3。
  • 声明chars_found的值,此值通过一系列操作得到。needle_match.zipWithIndex.map {},做的就是将needle_match信号组按index展开,然后进行{}里面的操作。
case (matches, i) =>
      val idx = Cat(recv_beat - UInt(1), UInt(i, beatOffset))
      matches && idx >= offset && UInt(i) <= first_zero
  • 因为needle_match会展开为各个信号,所以这里的matches就是指对应展开的needle_match_X(X=0/1/2/3),i就是index,可以为0/1/2/3。即needle_match_0和i=0进行一次val idx = Cat(recv_beat - UInt(1), UInt(i, beatOffset))和matches && idx >= offset && UInt(i) <= first_zero操作,以此类推。idx为信号拼接,分别采用recv_beat - UInt(1)和UInt(i, beatOffset)拼成。UInt(x,y),x数值,y为位宽。后面一行是条件判断。
  • PopCount()操作是将括号内的结果逐一相加。
  • 那么这段操作的意思是:
  1. 取i=0,matches=needle_match_0,得到idx0={recv_beat - UInt(1), UInt(0)},然后判断needle_match_0 && (idx0 >= offset) && (0 <= first_zero)的结果,并存入tmp0中。
  2. 取i=1,matches=needle_match_1,得到idx1={recv_beat - UInt(1), UInt(1)},然后判断needle_match_1 && (idx1 >= offset) && (1 <= first_zero)的结果,并存入tmp1中。
  3. 取i=2,matches=needle_match_2,得到idx2={recv_beat - UInt(1), UInt(2)},然后判断needle_match_2 && (idx2 >= offset) && (2 <= first_zero)的结果,并存入tmp2中。
  4. 取i=3,matches=needle_match_3,得到idx3={recv_beat - UInt(1), UInt(3)},然后判断needle_match_3 && (idx3 >= offset) && (3 <= first_zero)的结果,并存入tmp3中。
  5. 最后利用PopCount()操作,将tmp0,tmp1,tmp2和tmp3加起来,得到一个3bits的数据,赋给chars_found[2:0]。
    声明zero_found,其值为zero_match_0 || zero_match_1 || zero_match_2 || zero_match_3。
  • 声明finished为单bit寄存器。
  • 当state为s_idle时,io.cmd.ready输出为1。
  • 当state为s_resp时,io.resp.valid输出为1。
  • io.resp.bits.rd等于 resp_rd,io.resp.bits.data等于count。
  • 当state为s_acq时,tl_out.a.valid输出为1。
  • tl_out.a.bits是a组多个信号的捆绑,使用edgesOut.Get(这里的Get是TileLink的一种操作方式,可以简单理解为数据获取,是通过A组信号进行的,与缓存操作没有关系,具体说明大家可以查看TileLink协议)对不同信号进行赋值。edgesOut.Get在tilelink/Edges.scala中定义,代码如下。
 // Accesses
  def Get(fromSource: UInt, toAddress: UInt, lgSize: UInt) = {
    require (manager.anySupportGet, s"TileLink: No managers visible from this edge support Gets, but one of these clients would try to request one: ${client.clients}")
    val legal = manager.supportsGetFast(toAddress, lgSize)
    val a = Wire(new TLBundleA(bundle))
    a.opcode  := TLMessages.Get
    a.param   := UInt(0)
    a.size    := lgSize
    a.source  := fromSource
    a.address := toAddress
    a.mask    := mask(toAddress, lgSize)
    a.data    := UInt(0)
    a.corrupt := Bool(false)
    (legal, a)
  }
  • 当stata为s_gnt时,tl_out.d.ready输出为1。

  • 当io.cmd.ready=1和io.cmd.vaild=1时,进行下面的非阻塞操作:

  • 将io.cmd.bits.rs1赋给addr。

  • 将io.cmd.bits.rs2赋给needle。

  • 将io.cmd.bits.inst.rd赋给resp_rd。

  • count信号清0,finished信号清0,状态跳转至s_acq态。

  • 当tl_out.a.ready=1和tl_out.a.valid=1时,状态跳转至s_gnt态。

  • 当tl_out.d.ready=1和tl_out.d.valid=1时,进行下面非阻塞操作:
    1)recv_beat自加1。
    2)recv_data等于gnt.data。
    3)状态跳转至s_check态。

  • 当状态为s_check时,进行以下非阻塞操作:
    1)若操作没有完成的话,count自加chars_found。
    2)若zero_found为1时,finished由0跳变为1。
    3)如果recv_beat等于cacheDataBeats时,addr跳变为next_addr,而state根据zero_found || finished的结果来选择,如果为1则state<= s_resp;为0则state<=s_acq。
    4)如果recv_beat不等于cacheDataBeats时,state<=s_gnt。

  • 当io.resp.ready=1和io.resp.valid=1时,state<=s_idle。

  • 当state 不等于s_idle时,io.busy都为1。

  • io.interrupt强接0,给优化处理掉。

  • io.mem.req.valid强接0,给优化处理掉。

  • 因为TileLink协议是有5组信号的,分别是a,b,c,d和e,不同组负责不同的功能,a具有类似于写的功能,d具有类似于读的功能,这里只是简化,a和d组还有更复杂的功能。这里tl_out.b.ready := Bool(true),tl_out.c.valid := Bool(false)和tl_out.e.valid := Bool(false),会将b,c和e组的信号优化掉,生成的RTL不会存在b,c和e组的信号。

class CharacterCountExampleModuleImp(outer: CharacterCountExample)(implicit p: Parameters) extends LazyRoCCModuleImp(outer)
  with HasCoreParameters
  with HasL1CacheParameters {
  val cacheParams = tileParams.icache.get

  private val blockOffset = blockOffBits
  private val beatOffset = log2Up(cacheDataBits/8)

  val needle = Reg(UInt(width = 8))
  val addr = Reg(UInt(width = coreMaxAddrBits))
  val count = Reg(UInt(width = xLen))
  val resp_rd = Reg(io.resp.bits.rd)

  val addr_block = addr(coreMaxAddrBits - 1, blockOffset)
  val offset = addr(blockOffset - 1, 0)
  val next_addr = (addr_block + UInt(1)) << UInt(blockOffset)

  val s_idle :: s_acq :: s_gnt :: s_check :: s_resp :: Nil = Enum(Bits(), 5)
  val state = Reg(init = s_idle)

  val (tl_out, edgesOut) = outer.atlNode.out(0)
  val gnt = tl_out.d.bits
  val recv_data = Reg(UInt(width = cacheDataBits))
  val recv_beat = Reg(UInt(width = log2Up(cacheDataBeats+1)), init = UInt(0))

  val data_bytes = Vec.tabulate(cacheDataBits/8) { i => recv_data(8 * (i + 1) - 1, 8 * i) }
  val zero_match = data_bytes.map(_ === UInt(0))
  val needle_match = data_bytes.map(_ === needle)
  val first_zero = PriorityEncoder(zero_match)

  val chars_found = PopCount(needle_match.zipWithIndex.map {
    case (matches, i) =>
      val idx = Cat(recv_beat - UInt(1), UInt(i, beatOffset))
      matches && idx >= offset && UInt(i) <= first_zero
  })
  val zero_found = zero_match.reduce(_ || _)
  val finished = Reg(Bool())

  io.cmd.ready := (state === s_idle)
  io.resp.valid := (state === s_resp)
  io.resp.bits.rd := resp_rd
  io.resp.bits.data := count
  tl_out.a.valid := (state === s_acq)
  tl_out.a.bits := edgesOut.Get(
                       fromSource = UInt(0),
                       toAddress = addr_block << blockOffset,
                       lgSize = UInt(lgCacheBlockBytes))._2
  tl_out.d.ready := (state === s_gnt)

  when (io.cmd.fire()) {
    addr := io.cmd.bits.rs1
    needle := io.cmd.bits.rs2
    resp_rd := io.cmd.bits.inst.rd
    count := UInt(0)
    finished := Bool(false)
    state := s_acq
  }

  when (tl_out.a.fire()) { state := s_gnt }

  when (tl_out.d.fire()) {
    recv_beat := recv_beat + UInt(1)
    recv_data := gnt.data
    state := s_check
  }

  when (state === s_check) {
    when (!finished) {
      count := count + chars_found
    }
    when (zero_found) { finished := Bool(true) }
    when (recv_beat === UInt(cacheDataBeats)) {
      addr := next_addr
      state := Mux(zero_found || finished, s_resp, s_acq)
    } .otherwise {
      state := s_gnt
    }
  }

  when (io.resp.fire()) { state := s_idle }

  io.busy := (state =/= s_idle)
  io.interrupt := Bool(false)
  io.mem.req.valid := Bool(false)
  // Tie off unused channels
  tl_out.b.ready := Bool(true)
  tl_out.c.valid := Bool(false)
  tl_out.e.valid := Bool(false)
}

charactercount模块的实际功能是从cache中查找特定字符的数量。

  1. 指令中rs1的32位作为查找的起始地址。
  2. 指令中rs2的低8位,作为特定字符的输入。
  3. 当从cache中返回的数据存在8’h00,且状态机处于s_check时,计算结束,等待recv_beat等于16返回结果。
  4. 从cache中进行查找,因为配置的cache为4096Bytes,而sets为64,所以一个set里面有64Bytes,当recv_beat计数满足cacheDataBeats(因为总线是32bits,所以一次读4Bytes,64Bytes的数据需要读16次,即recv_beat需要等于16)时,返回结果。
  5. 满足计算结束的条件有三点,第一点返回的64Bytes的数据中存在8’h00;第二点状态机存在于s_check;第三点recv_beat需要等于16。
  6. 当64Bytes的数据中第一次检测到8’h00的存在,则不再对特定字符进行计数,即使后面还有也不再计数,默认8’h00为字符串的结束标志。
    注意:如果不满足5)中所述的3点,charactercount模块会一直请求数据,一直处于s_gnt状态,同时拉死CPU,CPU不能再运行后面的指令操作。

仿真过程说明。

仿真代码如下:

#include "encoding.h"
#include "L1Dcache.h"
#include "xcustom.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 rs1,rs2,rd1;
    unsigned int i;
    unsigned int test_num;
    
    {
        rs1 = 0x80000000;
        rs2 = 0x1a;
        for(i=0;i<32;i++) U32(0x80000000+i*4) = i + 1;
        ROCC_INSTRUCTION(2, rd1, rs1, rs2, 0); 
        for(i=0;i<32;i++) U32(0x80000000+i*4) = 0x1a3456f1 + i;
        ROCC_INSTRUCTION(2, rd1, rs1, rs2, 0); 
        for(i=0;i<16;i++) U32(0x80000000+i*4) = 0x1a3456f1 + i;
        U32(0x8000003C) = 0x001a2345;
        ROCC_INSTRUCTION(2, rd1, rs1, rs2, 0); 
        for(i=0;i<1000;i++) U32(0x70000000+i*4) = i;
    }

    U32(DEBUG_SIG) = 0xFF;
    while(1);
}

代码步骤:

  1. 配置rs1为0x80000000,rs2为0x1a,即搜索基地址为0x80000000,搜索字符串为0x1a。
  2. 往0x80000000-0x8000007C地址写入0x1-0x31。
  3. 执行custom3。
  4. 往0x80000000-0x8000007C地址写入0x1a3456f1-0x0x1a345700。
  5. 执行custom3。
  6. 往0x80000000-0x80000078地址写入0x1a3456f1-0x0x1a3456ff。
  7. 往0x8000007C写入0x001a2345。
  8. 执行custom3。
  9. 延迟一段时间,往0x70000000+i*4地址写一堆没有用的数据。
  10. 最后往DEBUG_SIG地址发送0xFF结束仿真。

功能说明:

  1. 第一次往0x80000000-0x8000007C地址写入0x1-0x31,因为0x80000000存的数据为0x00000001,所以0x01会与0x1a进行比较,但不符合,所以count没有增加,第二个byte为0x00,所以zero_found置1,不再统计0x1a的个数。
  2. 第二次往0x80000000-0x8000007C地址写入0x1a3456f1-0x0x1a345700,因为每个32-bit数据都包含了0x1a,这里有16个0x1a,但最终统计的个数只会是15个,因为最后一个32-bit数据为0x0x1a345700,从最低8位开始计数,最低8位为0x00,所以在这里zero_found已经会被置1,因此不会再对后面的数据进行统计。
  3. 第三次往0x80000000-0x80000078地址写入0x1a3456f1-0x0x1a3456ff,往0x8000007C写入0x001a2345,这里看最后一个32-bit数据,因为是0x0x001a2345,所以第三byte的0x1a也会被统计,最后一byte才是0x00,所以最终统计的数量为16个。

仿真波形:
白色箭头:为匹配到字符串0x00,所以zero_found置1。
红色箭头:为需要匹配的字符串,rs2的低8位,为0x1a。
蓝色箭头:从memory中读取的数据,将32-bit数据拆分为4byte进行匹配,基地址为0x80000000。
绿色箭头:由于cache sizes和sets的配置,所以每次memory请求数据都是16个32-bit。
在这里插入图片描述
下图是为了说明功能2和3的差别,白色箭头返回的计数结果为15(0x0f),而蓝色箭头返回的计数结果为16(0x10),具体的原因在上面已经说明清楚。
在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值