下面是对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中定义,代码如下。
 // Accessesdef 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.Geta.param   := UInt(0)a.size    := lgSizea.source  := fromSourcea.address := toAddressa.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 HasCoreParameterswith HasL1CacheParameters {val cacheParams = tileParams.icache.getprivate val blockOffset = blockOffBitsprivate 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.bitsval 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_rdio.resp.bits.data := counttl_out.a.valid := (state === s_acq)tl_out.a.bits := edgesOut.Get(fromSource = UInt(0),toAddress = addr_block << blockOffset,lgSize = UInt(lgCacheBlockBytes))._2tl_out.d.ready := (state === s_gnt)when (io.cmd.fire()) {addr := io.cmd.bits.rs1needle := io.cmd.bits.rs2resp_rd := io.cmd.bits.inst.rdcount := 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.datastate := 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_addrstate := 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 channelstl_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 functionvoid handle_trap()
{asm volatile ("nop");while(1);
}//--------------------------------------------------------------------------
// Mainvoid 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),具体的原因在上面已经说明清楚。

Rocket-chip-RoCC(6)相关推荐

  1. Rocket Chip 介绍

    Rocket  Chip 是基于 Chisel 开发的一款开源的 SoC 生成器,具有可综合的RTL.通过配置可以生成两种基于 RISC-V 指令集的通用处理器就.Rocket-Chip 中有两种处理 ...

  2. chisel(Rocket Chip)中如何参数化芯片系统

    2021.9.5 有些地方添加了一点自己的理解!!! 0 绪论 前面已经介绍了chisel的初级和高级参数化. 如何把这些东西有效的在系统中组织起来呢?如何在系统中快捷的使用他们?这篇文章主要解决这个 ...

  3. 在“芯片庭院”培育一颗多核异构 RISC-V SOC种子

    1 文章导览 本文是简要性的导览chipyard官方手册内容,以及安装开发环境需要注意的的一些地方,最后运行几个简单的官方Demo,希望能对RISC-V有兴趣的小伙伴有所启发帮助,官方网址为https ...

  4. RISC-V BOOM核学习

    来源:RISC-V BOOM documentation RISC-V BOOM核学习 The Berkeley Out-of-Oder Machine INTRODUCTION RV64GC Fig ...

  5. chipyard学习笔记

    目录 一 . 进入chipyard后的环境变量设置 3 二 . 6. Customization 自定义SoC学习: 3 Chipyard document 6.1 : 3 Chipyard docu ...

  6. 芯片开发语言:Verilog 在左,Chisel 在右

    来源 | 老石谈芯 在最近召开的RISC-V中国峰会上,中科院计算所的包云岗研究员团队正式发布了名为"香山"的开源高性能处RISC-V处理器.前不久我有幸和包老师就这个事情做了一次 ...

  7. 片上总线协议学习(1)——SiFive的TileLink与ARM系列总线的概述与对比

    link 片上总线协议学习(1)--SiFive的TileLink与ARM系列总线的概述与对比 finally 27 人赞同了该文章 一.背景介绍 随着超大规模集成电路的迅速发展,半导体工业进入深亚微 ...

  8. 降低芯片设计创新门槛——从互联网成功经验看开源芯片生态发展

    降低芯片设计创新门槛 --从互联网成功经验看开源芯片生态发展 中国科学院计算技术研究所研究员  包云岗 2018年,中国企业遭遇芯片禁售令而陷入困境,中国半导体产业的现状再次引起各界广泛关注.如何尽快 ...

  9. 开源RISC-V 项目Freedom在Arty-7-100T开发板上的实现

    开源RISC-V 项目Freedom在Arty-7-100T开发板上的实现 1.获取Freedom项目源码 Freedom项目开源的地址为https://github.com/sifive/freed ...

  10. ARM + RISC-V双核锁步DCLS Lockstep技术总结

    基于<A Loosely-Coupled Arm and RISC-V Locksteping Technology>文章总结 一.lockstep技术分类 文中将lockstep技术总结 ...

最新文章

  1. 深度学习如何选择模型
  2. 国家计算机科学进展,高等学校计算机一级学科教学改革与科学办学最新进展.pdf...
  3. 【坑】记录型信号量/AND信号量/管程解决生产者-消费者问题
  4. linux命令-- pstack命令(跟踪进程栈)
  5. zigbee 协议栈数据类型及转换
  6. 多线程池、饱和策略详解
  7. 渐进式图片加载 progressive-image
  8. 【渝粤题库】国家开放大学2021春3894理工英语1题目
  9. mysql5.6编译
  10. Android-入门学习笔记-JSON 解析
  11. Overview of HEVC之5 帧间预测
  12. 一年级大括号问题专项训练_新人教版一年级数学上册5.9解决问题(减法)微课视频辅导|课后练习...
  13. tplink 无线打印服务器,tplink打印服务器设置
  14. Vue学习(一)从 mvx模式 到 mvvm模式
  15. 爬虫-爬取中国诗歌网中中国好诗栏目 - 统计词汇出现频数 - 副本
  16. 一维优化 方法c语言,第三章一维优化方法
  17. 小程序开发工具_小程序开发工具都有哪些?
  18. IT职场人生系列之二十四:程序员如何增加收入
  19. NBUT 1181 Big Mouth of Abyss - Kog'Maw(删k位留最大最小数)
  20. CRC循环冗余校验是如何纠错的

热门文章

  1. 深入浅出Javascript的正则表达式
  2. 关于CSP选择C语言问题老是编译错误该怎么办???
  3. Materials - 编写高性能冰材质
  4. 联想sr650安装centos_一次联想ThinkServer RD650 510i配置及系统安装过程
  5. 深入理解System.arraycopy内部原理
  6. 读研三年后工作没了 2023计算机考研是不是没用了
  7. ZYNQ Linux+裸核
  8. 数据库如何把正数变成负数
  9. 人最大的敌人就是自己
  10. jdbc,叫的很顺口,但是你真的知道这个是什么意思吗?