ysyx讲座笔记

SoC或CPU参考架构图

汇总:

img

img

Chisel 初步

AXI4 连接

image-20220621153910155

Chisel使用经验

  • Chisel开发环境
  • 用Chisel 写个简单的模块: Adder
  • 创建这个模块的端口类
  • 创建这个模块的伴生对象
  • 创建这个模块的 Chisel-Testers 2 TB
  • 继承这个模块的端口类
  • 高阶实例: Adder Tree (map, foreach, case, forkjoin]
  • 优化命令 (suggestName, dontTouch, dontCare)
  • 几个个人习惯

Chisel开发环境

image-20220621140101972

用Chisel 写个简单的模块: Adder

image-20220621140145632

改进为创建模块的端口类

image-20220621140210761

创建这个模块的伴生对象

image-20220621144638625

  • Adder
    • class
    • object(伴生对象)
      • 例化时直接进行连接,不用在手动进行连接

一个class与两个object.apply(一个使用bundle,一个端口单独拿出来)

image-20220621144735393

image-20220621144710515

调用方法

1
2
3
val adder = Adder(a=..., b=...)

val adder2 = Module(new Adder())

高级实例:adder tree 加法器树

image-20220621150402487

  • Vec(类似scala的seq)
    • 参数一:个数
    • 参数二:类型
  • Array.map(x => function(x))
    • array里面的元素,以x做迭代器,替代为=>后的函数表达式

image-20220621151428760

优化命令(很少用)

image-20220621151909478

个人习惯

image-20220621151955270

重构与注释

  • 经常refactor(代码重构)
  • 整理代码结构
  • 合并重复代码
  • 经常添加注释
  • Scaladoc(官方scala注释)

更多技巧

image-20220621153153323

补充

chisel在Nutshell中的使用

chisel在Nutshell中的使用

NutShell设计

image-20220621014257802

确定架构

image-20220621014431924

模块 分解

image-20220621014511324

  • 上面是一个单周期cpu
  • 下面是nutshell cpu

参数定义

image-20220621014852096

  • 实例化显式传参
  • 实例化时隐式传参(推荐)
    • 作用:在一个类中声明隐式参数,则该类内部所有实例化类都传入了改参数

顶层传入隐式参数

隐式参数的类的声明

  • 全局参数

image-20220621015019575

  • CDE(自己研究,较难)
  • Diplomacy(自己研究,较难)

接口定义

image-20220621015144329

模块组装

image-20220621015350856

时序电路的组装

image-20220621015433286

凡是两个流水级要连接,可以直接调用这个类

image-20220621015513522

模块开发

image-20220621015710711

  • chisel 仲裁器Arbiter和队列Queue(ready-valid接口)

https://blog.csdn.net/qq_39507748/article/details/118887707

  • 一个有初始值的二维寄存器示例

image-20220621015818662

  • chisel多使用for加速电路描述

image-20220621020002811

测试和调试

image-20220621020055118

NutShell中使用到的一些Chisel特性

image-20220621020445026

  • 可以看《scala函数式编程》
  • 一个生成掩码的函数

image-20220621020647595

设计MaskedRegMap来描述CSR

image-20220621020606688

  • 副作用:改一个csr对系统状态进行改变
  • reg addr:csr在手册中的地址
  • reg data source:实际硬件寄存器

image-20220621021153647

  • map(maskedregmap)
    • Mstaatus:reg addr,一个常量
    • mstatus:一个实例化的寄存器
    • “hfffffffffffffffffffff”.U一个掩码
    • mstatusUppdateSideEffect:一个函数
  • maskedregmap.generator(进行完整实现)

maskedregmap:

image-20220621023637785

总的MaskedRegMap一行

image-20220621024035126

AXI4设备中的代码复用

image-20220621024248398

  • 使用面向对象的思想进行复用

image-20220621024255563

UART就很简单,大量逻辑在AXI4Slave里面:

image-20220621024339370

image-20220621024403981

Chisel开发中的注意事项(坑)

注意事项

image-20220621024523039

  • 把scala看作宏一样的东西

image-20220621024610888

image-20220621024718656

image-20220621024815837

  • DontCare非常危险,流片前消灭掉

image-20220621025046638

提早发现错误

两种assert(俄则特)

image-20220621025153220

什么是中断和异常

参考内容

The RISC-V Instruction Set Manual Volume II: Privileged Architecture Document Version 20190608-Priv-MSU-Ratified

中断异常简介

  • 异常(狭义):一般指来源于核内部的异常
    • -如:指令错误、访存错误等
  • 中断:一般指来源于核外部的异常,属于广义的异常
    • 包括外部中断、计时器中断以及软件中断
  • 一个特别术语——trap: 由异常或中断引起的控制转移

什么是异常处理机制

  • 处理器内核在执行程序的过程中,突然发生异常中止当前执行的程序,转而去做别的事情
  • 当别的事情处理完后,回到之前的地方继续执行之前的程序

RISCV异常类型

image-20220618172834396

要运行interrupt test ( am-kernels/tests/am-tests )暂时只需要实现时钟中断(计时器中专)+环境调用异常

RISCV特权级别

在任何时候,RISC-V处理器一定是运行在一个特权级别当中的

image-20220618172922145

  • 暂时只需要实现M模式

注意事项

  • 中断嵌套
    • 处理器内核正在处理中断时,出现了新的中断请求
    • RISC-V在硬件层面不支持,需要软件层面的特殊处理
    • 所以暂时不需要考虎
  • 仲裁
    • 当同时出现多个中断的时候,需要进行仲裁
    • 出现在外部中断与内部中断、外部与外部中断之间
    • 所以暂时不需要考虑
  • 中断处理模式
    • 分为向量处理模式和非向量处理模式
    • 暂时只需要实现非向量处理模式

总结

需要实现M模式与非向量处理模式

实现环境调用异常

需要实现部分与环境调用异常有关的CSR

  • 控制状态寄存器 Control and Status Registers

image-20220618173210600

1. mstatus

  • Machine-mode Status Register
  • 跟院并控制处理器的当前运行状态
  • 可读/写

image-20220618173234806

  • mstatus-MIE
    • 启用/禁用全局中断
    • 当硬件线程在特权模式M下执行时
      • MIE=1:全局启用中断
      • MIE=0:全局禁用中断

image-20220618173321296

  • mstatus-MPIE & MPP

    • 为了中断结束后能恢复原本的MIE位和特权模式
    • MPIE:存储在trap 之前 MIE 位的值
    • MPP:存储在trap 之前的特权模式 (目前可以写死为3,M->3)

    image-20220618173417619

2. mtvec

  • Machine Trap-Vector Base-Address Register

  • trap 向量配置 (异常入口地址)

  • 可读/写
  • 两部分:base, mode
    • mode:direct(非向量处理模式)、vectored(向量处理模式)。我们只需要实现非向量处理模式,因此可以写死为00
    • base:direct时被设置为异常入口地址的2到最高位

image-20220618173517781
3. mepc

  • Machine Exception Program Counter
  • 遇到中断/异常的指令的pc
  • 可读/写

image-20220618173832464

4. mcause

  • Machine Cause Register
  • 指示导致 trap的事件
  • 可读/写
  • 如果 trap 是由中断引起的,中断位(Interrupt)则会置1,异常则为0
  • code:手册上有不同异常中断对应不同mcause值,在实现中断和异常的时候根据这个查询中断或异常的不同,trap时修改这个mcause,反映了中断异常的原因

image-20220618174211347

am中的注意事项:

image-20220618174657583

image-20220618181746220


定时器中断的产生需要三个条件:

  • mstatus全局中断为1(软件实现)
  • mie中MTIE为1(软件实现)
  • mtime>=timecmp

1.2.条件如下所示

image-20220618203125914

image-20220618203139034

image-20220618204046668


ecall:通过引发环境调用异常,来请求执行环境

hello_intr的内容

  • 编写异常处理代码
  • 设置异常处理入口地址
  • 调用ecal指令

硬件工作(调用ecall后,cpu执行ecall指令的过程)

  • CPU捕获异常

  • 保存/更新CSRs

    • mepc

      • 异常:当前的指令的PC值(我们刚刚执行的是ecall,因此保存的是ecall指令的值)

      • mcause

        • 记录当前的异常原因: 十进制11(ecall的异常原因是d11,因此这里需要记录为d11)
    • mstatus

      • MPIE设为MIE的值0(即禁用环境异常)
  • MIE设为0跳转到mtvec的异常入口地址

进行异常处理——软件

  • 保存上下文:保存CPU的寄存器到内存中
  • 跳转到异常处理程序进行异常处理 (ecall特有的会在软件中设置mepc为pc+4)
    • 为什么是pc+4,因为pc是ecall,回卡死在ecall
    • 为什么am里面使用原件修改为pc+4,而不直接使用硬件,因为RISCV规定发生异常时mepc必须是异常处的pc,所以我们使用ecall软件中断的时候必须手动设置mepc为pc+4不然就卡死在ecall了
  • 恢复上下文: 从内存中恢复CPU的寄存器
  • PS:RISCV是软件进行上下文管理的,RISC-V架构的处理器不实现硬件上的上下文保存/恢复,需要在软件上实现上下文的切换机制

退出异常(mret)

  • 最后软件调用mret指令恢复异常,mret作用:

    • 恢复mstatus

      • MIE从MPIE中恢复,MPIE设置为1
    • 跳转到mepc中的地址

实现定时器中断

需要实现部分与实现定时器中断有关的CSR

1. mip

  • Machine interrupt-pending register
  • 包含等待中断的信息
  • 可读/写*
    • 为什么有星号:因为mip并不是所有位都即可也可写,但是哪些不可以即可读也可写的位与计时器中断关系不大

image-20220618182439087

2. mie

  • Machine interrupt-enable register
  • 包含各种中断的启用位
  • 可读/写
  • 我们需要实现的定时器中断MTIE:M模式计时器中断启用位

image-20220618182706509

3. mtime(Clint)

  • Machine time reqister
  • 64位内存映射的M模式实时计数器(对于rv32也是64位的)
  • 本质上不是CSR,可读/写
  • 属于Clint (Core-Local Interruptor, 核心局部外设),本质上是核心外设,通过MMIO实现的
  • 恒定的频率增加(与mycycle不同,mycycle与外接时钟频率有关)

4. mtimecmp(Clint)

  • Machine time compare register
  • 64位内存映射的 M模式计时器比较寄存器
  • 不是CSR
  • 可读/写
  • 当mtime >= mtimecmp 时,并且产生计时器中断mip的MTIP位置1

image-20220618202840804

前面的步骤与环境调用异常类似

进入中断-硬件

  • CPU捕获计时器中断
  • 保存/更新CSRs
    • mepc
      • 中断:下一条指令的PC值(与环境调用异常中硬件保存的PC不同,这里是pc+4,但是这里的pc+4指的是发生中断时还未执行的那一条指令,即译码时期的指令),其实和环境调用异常中的ecall在硬件实现上一样,这里的下一条指的就是中断发生时正在执行的指令
    • mcause
      • 记录当前的异常原因:0x8000000000000007
    • mstatus
      • MPIE设为MIE的值,MIE设为0
  • 跳转到mtvec的异常入口地址

进行中断处理-软件

  • 保存上下文: 保存CPU.的寄存器到内存中
  • 跳转到中断处理程序进行中断处理 (mtimecmp)
  • 恢复上下文: 从内存中恢复CPU 的寄存器

退出异常(mret)

  • 最后软件调用mret指令恢复异常,mret作用:

    • 恢复mstatus
      • MIE从MPIE中恢复,MPIE设置为1
  • 跳转到mepc中的地址

补充CLINT(Core Local Interruptor)

https://blog.csdn.net/qianniuwei321/article/details/123250492

中断全流程

小结:发现环境调用异常与定时器中断在很多时候是相通的,中断异常的实现如下

image-20220618203956923

  • 硬件除法异常或中断
  • 硬件保存/更新 CSRs
  • 硬件跳转到mtvec
  • 软件…
  • mret

其他注意事项

备注: csr指令中的5位立即数不需要符号位扩展。https://www.cnblogs.com/mikewolf2002/p/11196680.html

流水线

image-20220625203118393

  • 请参考这个符号位扩展的位置

image-20220625203134077

image-20220625203327974

  • 命名规范
    • i/o 输入输出
    • IDRN隶属模块
    • xxxxx具体功能

image-20220625203457203

动态分支预测

image-20220625204843292

2bit分支预测器与3/4/5/6…性能差不多,资料网上多就不赘述了

使用动态分支预测后的图

image-20220625205035384

image-20220625205143384

静态分支预测:永远猜他跳/不跳

AXI

握手

master发送给slave数据

  • 有效信号valid:一方想发送
  • 就绪信号ready:另一方能接受
  • valid && ready:数据成进行一次传输
    • 不重不漏

image-20220616200533214

异步总线,每一路都有握手

  • 读请求信号
    • RAVALID ->
    • RAREADY <-
    • RADDR ->
  • 读响应信号
    • RDVALID <-
    • RDREADY ->
    • RDATA
  • 写请求信号
    • WAVALID(wen) ->
    • WRAREADY <-
    • WRADDR ->
    • WDATA ->
    • WMASK ->
  • 写响应信号
    • WDVALID <-
    • WDREADY ->

AXI-Lite

AXI4-Lite 是一种异步存储器总线协议

  • 五个通道

    • 读地址 AR、读数据 R

      • 与之前的异步总线完全一致
    • 写地址 AW、写数据 W

      • 将原先的写请求通道一分为二
    • 写响应

  • 时钟信号 ACLK 上升沿触发

  • 同步复位 ARESETn 低电平有效

AXI读写事务transcation

image-20220616201717788

AXI4-Lite 读地址 AR

  • 读地址握手信号
    • ARVALID->
    • ARREADY <-
  • 读地址
    • ARADDR ->

image-20220616223703387

AXI4-Lite 读数据 R

  • 读地址握手信号
    • RVALID->
    • RREADY <-
  • 读地址
    • RDATA ->

image-20220616223728109

AXI4-Lite 写地址 AW

  • 写地址握手信号
    • AMVALID ->
    • AWREADY <-
  • 写地址
    • AWADDR ->

image-20220616223854710

AXI4-Lite 写数据 W

  • 写数据握手信号
    • WVALID ->
    • WREADY <-
  • 写数据
    • WDATA ->
  • 写掩码
    • WSTRB ->

STRB(Strobe)选通的意思

image-20220616223944488

AXI4-Lite 写响应 B

写响应握手信号

  • BVALID <-
  • BREADY->

image-20220616224145444

AXI4-Lite注意事项

  • 读事务和写事务分别均为顺序响应(也就是第一个地址、第二个地址、第三个….)
  • 写地址通道和写数据通道可以不同时有效
  • 为了避免总线死锁,规定每个通道内
    • valid 置有效不能依赖于 ready 是否有效
    • ready 置有效可以依赖于valid 是否有效

AXI4-Lite信号一览

image-20220616224501296

AXI4与AXI4-Lite的互联

AXI4协议和AXI4-Lite是可以互联的,唯一需要注意的是当主机是AXI4且从机是AXI4-Lite时,从机的AXI4-Lite协议需增加AXI ID信号以匹配主机的AXI ID

img

从AXI4-Lite到AXI4

如果我想从内存读一整页 (4K) 的内容

  • 一次读 8 Bytes,则需要发送 512 次读请求
  • -有没有更好的办法呢

什么是突发传输(Brust)

  • 一段时间内,连续地传输多个地址相邻或相同的数据

突发传输burst

突发传输事务与普通传输的对比:

image-20220616224654697

  • 注意burst突发和transaction事务所代表的范围

突发传输三要素(x为W或R,代表读或写)

  • 突发宽度 AxSIZE
  • 突发类型 AxBURST
  • 突发长度 AxLEN

突发宽度 AxSIZE

  • 每个 transfer/beat 的数据宽度
  • 必须小于 transaction 中一主从数据总线的宽度

image-20220617010130923

突发类型AxBURST 有三种

  • FIXED(固定)
    • 所有的数据都使用AxADDR,通常用于FIFO,串口等,即每一次读/写数据都是AxADDR
  • INCR
    • 第一个数据的地址为 AxADDR
    • 后一个数据在前一个数据的基础上递增“突发宽度
    • 常用于RAM等
  • WRAP

image-20220617010550340

image-20220617012108640

突发长度 AxLEN

burst 中 transfer/beat的个数 = AXLEN + 1

  • AxLEN 位宽为8 位
    • 对于INCR类型的突发传输,AxLEN 可取0~255
    • 对于FIXED类型的突发传输,AxLEN 可取0~15

AXI4中的xID信号

五级流水线CPU 中,取指与访存都会发出读请求

  • 取指和访存分别发出读请求,然后读数据返回
  • 取指和访存都以为是自己的读数据
  • 怎么解决这一问题呢

为通道新增ID信号,不同流水级可用不同ID

  • master 发出的请求便用什么 ID,slave 返回的响应就使用什么ID
  • master根据响应的 ID 判断是哪一流水级的响应

image-20220617012500211

为什么没有WID写数据ID(AXI3中有,AXI4中不再使用)

答:

AXI4单主机传输事务规定

  • 写数据顺序与写事务的顺序一致
    • 这就使得我们不需要 WID信号
  • ID相同的事务,从机需要按照主机发出事务的顺序执行,并按序返回数据或者回复信号
  • ID不同的事务,之间不存在顺序约束,从机可以按照任意的顺序返回数据或回复
  • 读写事务之间不存在顺序约束
  • 或者,我们也可以干脆把IF级和MEM 级当作是两个 master 设备!
    • IF级和 MEM 级都能读
    • 只有MEM 级才能写

AXI4 多主机传输事务

如何支持多主机?实现一个仲裁器

  • N-to-1 Interconnect
  • 协议规定,不同主机间的事务是独立的,没有顺序约束

仲裁器如何将读数据和写响应返回给正确的主机?(两种方案)

  • 方案1(简单粗暴):各主机保证自己的 ID与其他主机不同
  • 方案2(推荐):仲裁器对不同主机请求的 ID 进行调整,使得即使不同主机发出的请求ID相同,从机看到的ID不同
    • 仲裁器接收主机事务后,在原有事务 ID附加一个表示来源端口的前缓
    • 仲裁器接收从机事务后,根据事务ID前缀判断应当返还给哪一个主机
    • 返还给主机前,仲裁器需要将 ID 前缀去掉
      • 协议建议主机端ID 不超过4位
      • 协议建议仲裁器为ID 添加的前缀不超过4 位

image-20220617023021809

AXI4仲裁器对写事务保序

  • 写数据顺序与写事务顺序一致(即lock住了,其他master来了先存起来)
    • 可以看作是之前单主机版本的约定的一个扩展
  • 一个事务内的写数据间,不能插入其他事务的数据(AXI4新增,AXI3支持但可以实现的设备较少且难度较大,因此在AXI4中删除)
  • 这些抓定讲一步保障了即使在多主机的情况下,我们也依然不需要WID
    • 如果没有保序会搞乱

窄传输

当本次传输的突发宽度 (AxSIZE)小于通道本身的宽度(假设为 size) 时,这次传输被称为窄传输

  • 数据在通道上传输的位置必须与 (数据的地址) % size 保持一致

AX4规定:

  • master 必须保证 WSTRB只在对应字节有效时为高

示例1

示例2

非对齐传输

起始地址 AxADDR 与突发宽度 AxSIZE 不对齐

对于非对齐传输,master可以把它变成对齐的:

  • 在首个 transfer 填充无效数据至对齐
  • 将起始地址调整为对齐的地址

然后,问题就退化为了窄传输/正常传输

特别提示

  • master 必须保证WSTRB 只在对应字节有效时为高

示例

transaction的回复信号

读数据和写响应通道分别有一个回复信号 xRESP

  • 写事务的回复信号针对整个事务
  • 读事务的回复信号针对每个 transfer

写事务与读事务

image-20220617024400211

  • OKAY
  • SLVERR从机问题
  • DECERR解码错误(一般是不存在的地址)

总结

这里只介绍了一些最主要的信号和规范,若需了解更多,可以自行查阅手册

输入输出

那么,MMIO 在硬件上应该由谁来实现呢

  • 访问设备由访存指令实现
  • 访存需要经过总线
  • 总线负责把不同地址的访问请求分发给不同的设备
  • 1-to-N Interconnect 负责分发

1-to-N AXI4 Interconnect(单主机多从机,我们在设计CPU时一般参考这个就行)

  • 也称为 decoder/router
  • 根据 AxADDR将相应的请求转发到不同的从机

image-20220617025054411

CPU 中的可能情况

  • 多主机:IF,MEM
  • 多从机:DRAM,UART,CLINT等

image-20220617025121564

NEMU的MMIO

nemu 的设备及其内存映射

  • 串口: 0xA10003F8,1字节
  • 时钟: 0xA1000048,2 字节
  • 键盘:0xA1000060,4字节

各设备寄存器的用法请自行查阅手册和阅读代码

一个有关interconnect的问题

axi4-interconnect

问题:axi interconnect 外围子系统包含一个 AXI crossbar,每个 S/M 端口带有couplers 。这些对不是标准的 AXI 对 IP,而是直接将 S_AXI 连接到 M_AXI 的自定义 IP。我猜这些是虚拟 IP,如果我不必在我的设计中实现耦合器,则不需要。我想得到一些关于设计的确认和见解。链接

回答:Each sub modules in the axi_interconnect are generated automatically by the tools per MI/SI protocols. Couples are needed for auto generation. If no signals conversion are needed, it’s only a dummy as you have seen. This doesn’t matter. If you really don’t want to see it(I cannot image the reason), don’t use axi_interconnect you can manually add each AXI infrastructure cores like Crossbar, converter, etc.

image-20220620161127202

image-20220620161053683

image-20220620015020896

  • CPU访问FPGA外设:crossbar->chiplink->FPGA
  • CPU使用UART:crossbar->UART
  • CPU上有两个AXI4接口,一个master一个slave,slave与dma接,没有则悬空

RAM替换

image-20220620015511158

RAM/与cache

image-20220620015744997

  • 注意:数据输出会延后一个周期

设备和中断的扩展

image-20220620015810536

时钟复位模块(RCG)

image-20220620015925031

  • 为什么要做一个时钟复位?
    • 如果不做,异步的时候会出现亚稳态问题
  • RCG模块:异步复位,同步撤离
  • sys_clk 外部晶振输入
  • pll_cfg pll配置信号
  • clk_sel mux选择项,防止pll挂掉,从而提供外部晶振
  • sys_rst 外部复位信号
  • 左边两个D触发器作用:打两拍,实现同步释放
  • Counter:做延迟,pll一开始不稳定

异步复位寄存器说明

image-20220620020444092

外设介绍

1 UART

image-20220620020533266

2 SPI

image-20220620020633761

总线介绍

image-20220620020728986

取指通路(红色线)

image-20220620021001218

访存通路(红色线)

image-20220620021201665

DMA通路

  • 网卡
    • Network接口处需要有一个中断

image-20220620021246033

地址分配

image-20220620021401313

  • reserve(红色部分)意思是保留

第一个程序的加载

image-20220620023401557

  • 四种加载方式:
      • cpu在执行第一条指令前使用verilator等仿真工具,将测试程序加载到memory中,并设置合适的pc起始地址。这样cpu就会根据起始地址,逐条指令执行
    • flash存放一条跳转地址,跳转指令作用是跳转到memory中的测试程序的起始地址
      • cpu在执行第一条指令前使用verilator等仿真工具,将测试程序加载到memory中,并设置起始地址为flash中的跳转指令的地址
    • 在flash中放一个加载程序loader,cpu起始地址设置为loader起始地址
    • 直接在flash中运行测试程序,但是flash中的xip地址空间智能读不能写,有些数据读写操作不能进行,因此需要对程序进行特殊处理
      • 我们提供一XIP flash模式的rtthread
      • image-20220620151146458

cache一致性问题

image-20220620151310776

一个可以仿真的soc

image-20220620151351070

MIPS(龙芯杯接入)

image-20220620151531430


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!