咕咕咕

年底太忙, 后面直播频率降低

  • 还是想准备一些新内容之后再来讲
  • 在这之前, 大家可以先看五期的视频

引言

上次课内容: 一个简单的SoC

  • 只读存储器 - MROM -> flash
  • 输出设备- UART
  • 随机存储器 - SRAM

 

本次课内容:

  • 如何接入容量更大的随机存储器? - DRAM
  • 如何接入更多的输入输出设备? - GPIO, PS/2键盘, VGA

存储密度更大的随机存储器

回顾: 通过SRAM存储1 bit

  • 空闲: 字线加低电压时, N1和N2截止, 无读写操作
  • 读出: 向两根位线加高电压(接近逻辑1), 再向字线加高电压, 此时N1和N2导通, 根据存储的信息, 其中一根位线的电压轻微下降, 可通过放大电路检测并确定哪一根位线, 从而得知存储的信息是逻辑0还是逻辑1
  • 写入: 将两根字线分别设置成逻辑0和逻辑1, 再向字线加高电压, 效果类似SR锁存器的复位和置位

可将SRAM单元的行为抽象成一个锁存器(假设高电平有效)

  • SEL有效时, Q端读出单元中数据
  • SEL有效且WE有效时, 将D写入单元

通过DRAM存储1 bit

DRAM存储单元 = 1根晶体管 + 1个电容

  • 晶体管 = 读写开关
  • 电容 = 存储的信息
    • 电量 > 某阈值, 则存储1; 否则存储0
  • 易失性存储, 断电后信息丢失

 

使用电容存储信息需要解决的问题

  • 随着DRAM工艺的提升, 电容越来越小
    • 01之间的区别也很小, 很难直接检测
  • 电容本身会漏电
    • 如果什么都不做, 1就会变成0

解决读出问题 - 读出放大器

读出放大器的本质 = 交叉配对反相器

  • 利用负反馈特性, 将\(V_a\)\(V_b\)的差值放大
  • 只要设置\(V_a \neq V_b\), 读出放大器将进入稳态01

DRAM存储单元的激活(Activate)和读出

  1. 预充电状态: 字线未选通, 读出放大器未使能, 其两端加电压\(\frac{1}{2}V_{DD}\)
  2. 激活(操作1): 选通字线, 使晶体管导通, 电容与位线连通
  3. (假设DRAM存储单元中存1)电容中的电荷流向位线, 使其电压升高
  4. 激活(操作2): 使能读出放大器, 将两端的电压差放大
  5. 激活状态: 读出放大器进入稳定状态, 外部电路可从中读出1

解决漏电问题 - 定时刷新

在激活状态下, 电容仍然与位线连通

  • 在读出放大器的负反馈作用下, 电荷从位线流向电容, 相当于对其充电

 

为了解决电容的漏电问题, 需要添加负责定时刷新的电路逻辑, 对DRAM存储单元进行周期性的激活操作

  • 刷新周期 < \(T_{V_{DD}\to\frac{1}{2}V_{DD}}\)
    • 太稀少, 读出放大器将检测到0; 太频繁, 会降低读写效率
  • JEDEC标准规定, 刷新周期不小于64ms
  • 与SRAM相比, DRAM的刷新操作需要花费不少功耗

 

定时刷新的电路逻辑放在哪里?

  • 放在DRAM控制器 - DRAM控制器设计复杂, 但DRAM颗粒主频高
  • 放在DRAM颗粒内部 - DRAM控制器设计简单, 但DRAM颗粒主频低

PSRAM颗粒 = 支持定时刷新和地址译码的DRAM

型号为IS66WVS4M8ALL的PSRAM(Pseudo-SRAM)颗粒

  • 存储阵列 - 22根地址线, 4MB存储单元
  • 地址译码器 - 行地址X和列地址Y
  • 寄存器/锁存器 - 地址锁存器, 数据锁存器等
  • 控制逻辑 - 命令解析, 定时刷新等
  • 总线接口

 

 

使用方式与SRAM很类似: 只需给出地址, 数据和读写命令, 即可访问颗粒中的数据, 无需关心存储阵列的物理组织结构

PSRAM颗粒的总线接口

PSRAM通常提供两种接口, 可按需选择

  • 并行PSRAM - 地址线和数据线全都通过引脚连出芯片
  • 串行PSRAM - 通过串行总线协议使用少数几根信号线进行通信

SPI总线协议的升级版 - 更高效的传输

基础SPI协议是全双工的 - master和slave可同时分别通过MOSIMISO发送消息

  • 但通常master先发送命令, slave处理后才回复结果
    • 只需要半双工的信道即可

 

升级版2.0 - Dual SPI, 半双工的SPI

  • MOSIMISO同时用于一个方向的传输
  • 一个SCK时钟内可单向传输2 bit
  • MOSI/MISO -> SIO0(Serial I/O 0)/SIO1

不同的传输方式

为了与基础SPI协议的传输方式区分, slave通常提供不同的命令

  • 通常用三元组(命令传输位宽-地址传输位宽-数据传输位宽)表示一种传输方式

型号为W25Q128JV的flash颗粒提供多种读命令, 以读32位数据为例:

命令 协议 最大频率(MHz) 三元组 命令周期 地址周期 读延迟 数据周期 总周期 总时间(us)
03h 基础SPI 50 (1-1-1) 8 24 0 32 64 1.28
0Bh 基础SPI 133 (1-1-1) 8 24 8 32 72 0.54
3Bh Dual SPI 133 (1-1-2) 8 24 8 16 56 0.42
BBh Dual SPI 133 (1-2-2) 8 12 4 16 40 0.30

无论地址和数据部分按多少位进行传输, 命令部分都按1 bit传输

  • 因为slave在解析出命令之后, 才知道后续的地址和数据应该使用何种协议进行传输
  • 因此命令部分仍然按照基础SPI协议逐位传输

不同的传输方式(2)

升级版3.0 - Quad SPI, 简称QSPI, 新增SIO2SIO3

  • 一个SCK时钟内可单向传输4 bit

 

命令 协议 最大频率(MHz) 三元组 命令周期 地址周期 读延迟 数据周期 总周期 总时间(us)
6Bh QSPI 133 (1-1-4) 8 24 8 8 48 0.36
EBh QSPI 133 (1-4-4) 8 6 6 8 28 0.21

 

升级版4.0 - QPI, 支持命令按4 bit传输, 即(4-4-4)

  • 为兼容基础SPI的master, slave上电时处于基础SPI模式
  • slave需提供一个切换到QPI模式的命令, 切换后才能使用QPI协议通信
  • 型号为W25Q128JV的flash颗粒不支持QPI协议

访问PSRAM颗粒中的数据

 

根据手册给PSRAM颗粒发送正确的指令序列

  • 8位指令EBh表示通过QSPI传输方式读数据

访问PSRAM颗粒中的数据(2)

以读32位数据为例:

  • SPI模式
命令 协议 最大频率(MHz) 三元组 命令周期 地址周期 读延迟 数据周期 总周期 总时间(us)
03h 基础SPI 33 (1-1-1) 8 24 0 32 64 1.94
0Bh 基础SPI 104 (1-1-1) 8 24 8 32 72 0.69
EBh QSPI 104 (1-4-4) 8 6 6 8 28 0.27

 

  • QPI模式
命令 协议 最大频率(MHz) 三元组 命令周期 地址周期 读延迟 数据周期 总周期 总时间(us)
03h QSPI 84 (4-4-4) 2 6 4 8 20 0.24
0Bh QSPI 84 (4-4-4) 2 6 4 8 20 0.24
EBh QSPI 104 (4-4-4) 2 6 6 8 22 0.21

PSRAM控制器

  • 通过SPI协议向PSRAM颗粒发送正确的指令序列
    • ysyxSoC/perip/psram/efabless/EF_PSRAM_CTRL.v

 

上层软件不关心SPI的通信过程, 需要控制器提供SPI传输的功能抽象

  • 支持XIP模式, 否则CPU无法用访存指令访问PSRAM, 软件难以将其作为内存使用
    • 由于PSRAM控制器只连接PSRAM颗粒, 是唯一的slave, 因此实现比SPI master的XIP模式更简单

 

  • PSRAM控制器的总线连接
    • ysyxSoC/perip/psram/efabless/EF_PSRAM_CTRL_wb.v - wishbone协议
    • ysyxSoC/perip/psram/psram_top_apb.v - 我们将它封装成APB协议

应用程序访问PSRAM的过程

  • C代码 - a = *(int *)(PSRAM_BASE + 4)
  • RISC-V指令 - lw a0, 4(a1)
  • LSU - 发起AXI-Lite总线请求
    • araddr = 0x8000_0004, …
  • 总线 - 将请求转发到PSRAM控制器
    • AXI-Xbar -> AXI-APB -> APB-Xbar -> APB-wishbone -> 控制器
  • PSRAM控制器 - 接收总线请求后, 从araddr解析出PSRAM的地址4
  • SPI master - 将地址4作为PSRAM读命令的参数, 通过QSPI发送

应用程序从PSRAM中读出数据的过程(2)

  • PSRAM颗粒 - 监听SIO, 得到master发送的命令psram_cmd
  • 命令解析 - 从psram_cmd中解析出命令EBh和地址0x4, 向存储阵列发送激活命令
  • 地址译码器 - 将地址0x4译码成选择信号, 并驱动存储阵列的字线
  • 存储阵列 - 根据字线选出存储单元
  • 存储单元 - 将存储的数据读到读出放大器, 根据读出放大器的状态检测存储单元中的值

 

  • 存储的数据 -> PSRAM颗粒的SIO -> PSRAM控制器的SIO -> wishbone.wb_dat_o信号 -> APB.prdata信号 -> AXI-Lite.rdata信号 -> a0寄存器 -> a变量

运行更大的程序

运行更大的程序

型号为IS66WVS4M8ALL的PSRAM颗粒可以提供4MB的存储空间

  • 比8KB的SRAM大很多
  • 将数据段和堆区分配到PSRAM, 可以支持更大程序的运行

 

但大程序的运行时间较长

分析: 数据段和堆区分配到PSRAM, 但取指仍然需要访问flash

  • flash的SPI master只支持(1-1-1)传输模式, 需要花费64个SCK时钟
  • SPI master通过分频方式生成SCK, 即使采用效率最高的二分频, 也需要花费128个CPU时钟
  • 还有XIP状态机的控制开销

结果: 从flash中取出1条指令, 前后需要花费约150个CPU时钟

 

如何优化?

将代码段分配到PSRAM

既然PSRAM容量大, 而且支持(1-4-4)甚至(4-4-4)的传输方式, 为什么不将代码段也分配到PSRAM中呢?

PSRAM的本质是一种DRAM, 是易失性存储, 因此也需要从flash中加载

  • 加载也需要花费时间, 值得吗?

 

程序中的大部分代码都需要重复执行: 循环, 函数调用

  • 假设一段代码需要执行\(n\)次, 考虑其取指所需的SCK周期数
    • 若直接在flash中执行, 则\(f_{flash}(n) = nT_{flash} = 64n\)
    • 若加载到PSRAM中执行, 则\(f_{psram}(n) = T_{flash} + T^{write}_{psram} + nT^{read}_{psram} \le 92 + 28n\)
  • 只要代码执行超过1次, 就值得将其加载到PSRAM中执行

 

由谁来加载 - 当然是bootloader了

进一步优化加载时间 - bootloader的多级加载

若程序较大, bootloader加载程序的过程也会较长

  • bootloader本身在flash中执行

类似地, 能否将 “bootloader加载程序”的代码先加载到访问效率更高的存储器中, 然后再执行 “bootloader加载程序”的功能?

  • 这就是bootloader的多级加载!

 

二级bootloader = FSBL(first stage bootloader) + SSBL(second ~)

  • 系统上电时, FSBL, SSBL和需要运行的程序都位于flash中
  • 首先执行FSBL, 将SSBL从flash加载到PSRAM, 然后跳转到SSBL执行
  • 然后执行SSBL, 将程序从flash加载到PSRAM, 然后跳转到程序并执行

 

SSBL的代码很小, 可将其加载到SRAM中执行, 进一步提升执行效率

  • SRAM的读延迟是1周期

进一步提升DRAM的访问效率

并行的DRAM颗粒总线接口

  • 串行总线的一个周期只能传输少量数据
  • 采用并行总线可以显著提升信息传输的效率

 

采用并行总线接口的主流DRAM颗粒 - SDRAM(Synchronous DRAM)

  • 颗粒的时钟信号由控制器直接驱动, 无需像SPI那样通过分频产生时钟
  • 第一代在1992年发售, 属于SDR SDRAM(Single Data Rate SDRAM)
    • 一个时钟传输一次数据
  • 1997年发售DDR SDRAM(Double Data Rate SDRAM)
    • 能分别在时钟的上升沿和下降沿传输数据, 提升传输带宽
  • 此后依次出现DDR2~DDR5, 通过不同技术提升传输带宽

 

还有异步DRAM, 总线信号中没有时钟, 但目前基本上被SDRAM替代

  • 今天说DRAM, 通常指代SDRAM

SDRAM颗粒的总线接口

型号为MT48LC16M16A2的SDR SDRAM颗粒有39根引脚

  • CLK, CKE - 时钟信号和时钟使能信号
  • CS#, WE#, CAS#, #RAS - 命令信号
  • BA[1:0] - 存储体地址
  • A[12:0] - 地址
  • DQ[15:0] - 数据
    • 一个周期能传输16位数据
  • DQM[1:0] - 数据掩码

SDRAM颗粒的内部结构

和PSRAM不同, SDRAM总线中包含存储体地址BA

  • 这要求SDRAM控制器了解颗粒中存储阵列的内部组织结构

 

SDRAM颗粒的存储阵列是一个多维结构

typedef bit word[16]; word MT48LC16M16A2[4][8192][512];
//       0        1                      4    3    2
// sizeof(MT48LC16M16A2) = 4 * 8192 * 512 * 16 bit = 256Mb = 32MB
  1. 一个DRAM存储单元由1根晶体管和1个电容组成, 用于存储1 bit
  2. 一个存储字(memory word)由16个DRAM存储单元组成
  3. 一行(row)由512个存储字组成, 每个存储字位于一列(column)
  4. 一个存储体(memory bank)由8192行组成
  5. 一个颗粒(chip)由4个存储体组成

要访问一个存储字, 需要给出存储体地址(bank address), 行地址(row address)和列地址(column address)

SDR SDRAM颗粒的访问命令

不同版本的SDRAM标准, 颗粒的命令稍有不同, 要RTFM

CS# RAS# CAS# WE# 命令名称 命令含义
1 X X X COMMAND INHIBIT 无命令
0 1 1 1 NO OPERATION NOP
0 0 1 1 ACTIVE 激活目标存储体的一行
0 1 0 1 READ 读出目标存储体的一列
0 1 0 0 WRITE 写入目标存储体的一列
0 1 1 0 BURST TERMINATE 停止当前的突发传输
0 0 1 0 PRECHARGE 关闭存储体的已激活行(预充电)
0 0 0 1 AUTO REFRESH 刷新
0 0 0 0 LOAD MODE REGISTER 设置Mode寄存器

 

和PSRAM不同, 刷新作为命令提供给控制器

  • SDRAM控制器需要及时向SDRAM颗粒发送刷新命令

突发传输

突发传输 = 一次事务(transaction)中包含多次连续的数据传输

  • 一次数据传输称为一个 “节拍”(beat)

 

从发出READ命令, 到通过DQ总线返回数据, 存在一定的读延迟

  • 称为CAS latency(有的教材翻译为CAS潜伏期)
  • 对于型号为MT48LC16M16A2的SDR SDRAM颗粒, 根据工作频率不同, CAS latency可设为1~3周期

假设CAS latency = 2, 读出连续的8字节

  • 方案1 - 拆分成多个请求, 共需12个周期
  • 方案2 - 采用突发传输, 共需6个周期
// normal                                                // burst
  1   1   1   1   1   1   1   1   1   1   1   1            1   1   1   1   1   1
|---|---|---|---|---|---|---|---|---|---|---|---|        |---|---|---|---|---|---|
  ^       |   ^       |   ^       |   ^       |            ^       |   |   |   |
  |       v   |       v   |       v   |       v            |       v   v   v   v
 READ   data READ   data READ   data READ   data          READ    1st 2nd 3rd 4th

SDR SDRAM颗粒的读写命令序列

逻辑上需要\(\log_2(32\mathrm{MB} / 16\mathrm{b}) = 24\)根地址线, 但地址端口只有13位, 因此地址需要分时传输

SDRAM控制器

上层软件不关心控制器和颗粒之间的通信过程, 需要控制器提供相应的功能抽象

  • 地址转换 - 将系统总线请求的地址转换成访问SDRAM颗粒所需的存储体地址, 行地址和列地址
  • 命令生成 - 根据SDR SDRAM标准向SDRAM颗粒发送正确的命令序列
  • 定时刷新 - 周期性向SDRAM颗粒发送刷新命令, 以保持SDRAM存储阵列中数据的正确性
  • ysyxSoC/perip/sdram/core_sdram_axi4/ sdram_axi_core.v - RTFSC

 

  • SDRAM控制器的总线连接
    • ysyxSoC/perip/sdram/sdram_top_apb.v - 我们将它封装成APB协议

应用程序访问SDRAM的过程

  • C代码 - a = *(int *)(SDRAM_BASE + 4)
  • RISC-V指令 - lw a0, 4(a1)
  • LSU - 发起AXI-Lite总线请求
    • araddr = 0xa000_0004, …
  • 总线 - 将请求转发到SDRAM控制器
    • AXI-Xbar -> AXI-APB -> APB-Xbar -> SDRAM控制器
  • SDRAM控制器 - 接收总线请求后, 从araddr解析出SDRAM的地址4, 生成命令序列向SDRAM颗粒发送

应用程序从SDRAM中读出数据的过程(2)

  • SDRAM颗粒 - 接收来自SDRAM控制器的命令序列
  • 地址译码器 - 将地址0x4译码成选择信号
  • 激活命令 - 激活目标存储体的一行, 通过译码出的选择信号驱动存储阵列的字线, 对应行的存储单元的数据转移到读出放大器中
  • 读命令 - 从读出放大器中选出列地址所在的存储字

 

  • 存储的数据 -> SDRAM颗粒的DQ -> SDRAM控制器的DQ -> APB.prdata信号 -> AXI-Lite.rdata信号 -> a0寄存器 -> a变量

从DRAM存储单元到内存条

DRAM颗粒的内部结构

  1. 1根晶体管 + 1个电容 = 1个DRAM存储单元

  1. w个存储单元 = 1个存储字 = 1列

  1. c个存储字 = 1行

DRAM颗粒的内部结构(2)

  1. r行 = 1个存储体

  1. b个存储体 = 1个颗粒芯片

从DRAM颗粒到内存条

在电路中, 通常大 = 慢

如今的内存条动辄就4GB, 如何兼顾容量和速度?

  • 通过小模块(颗粒)组合扩展而成
  • 集成在一个标准尺寸规格的电路板上
  • 对外提供DIMM接口, 使其可插入主板的内存槽中

 

如何扩展?

 

回顾: 存储器逻辑上是个二维矩阵 - 每行存储一个字, 行号 = 地址

两个维度的扩展:

  • 位扩展 - 增加字的位宽, 让一个地址存储更多位的信息
  • 字扩展 - 增加字的数量, 扩展地址的范围

从DRAM颗粒到内存条(2)

  1. 位扩展: C个颗粒芯片 = 1个rank(面)

  • 思想: 同时访问不同颗粒中相同的地址的数据
  • 既提升存储器容量, 还提升访问带宽
  • 代价: 要求数据引脚线性增长

从DRAM颗粒到内存条(3)

  1. 字扩展: R个rank = 1个channel
  • 通常内存条一侧的颗粒组成1个rank

  • 思想: 将不同的地址分布到不同的颗粒上
    • 通过对部分地址信号的译码, 生成不同rank的片选信号
  • 地址引脚数量的增长与存储容量呈log关系
  • 缺点: 无法直接提升访存带宽
    • 给定一个地址, 只有一个rank能工作

支持多通道(channel)的主板

  1. n个channel = 1个SoC

SoC中集成多个内存控制器, 可独立工作, 分别连接一个通道的内存条

  • 主板上不同颜色的插槽表示不同的通道
  • SoC引脚数量线性增长

接入更多外设

IOE在SoC上的实现

  • GPIO - 本质上是一根连接芯片内外的导线
    • 需要GPIO控制器来提供设备寄存器的抽象
    • 直接让寄存器中的1 bit与GPIO连接即可
  • UART - 分TXRX两根导线
    • 已集成UART16550控制器
    • 为了看到导线传输的效果, 需要一个串口终端
  • PS/2键盘 - 分PS2_CLKPS2_DATA两根导线
    • 需要PS/2控制器来提供设备寄存器的抽象, 让软件从设备寄存器中读出按键信息
  • VGA - I/O接口包含RGB信号, 水平同步信号HS, 垂直同步信号VS, 有效信号VALID
    • 需要VGA控制器, 一方面向软件提供帧缓冲的访问, 另一方面将帧缓冲中的像素信息转换成VGA I/O接口信号

NVBoard + ysyxSoC + NPC + RISC-V + AM + RT-Thread + FCEUX = 计算机系统!

片间总线协议

将总线延伸到片外

之前介绍的设备控制器都位于SoC中, 需要占用一定的流片面积

  • 若设备控制器较复杂(如现代DDR控制器), 将花费不少流片成本

 

一个想法: 将总线延伸到芯片外部

  • 两个芯片可以在遵循同一套通信协议的情况下互相通信
  • 一个芯片就可以访问其他芯片成品上的设备
    • 后者一方面不占用流片面积, 从而节省流片成本
    • 另一方面也可以降低验证的复杂度和流片风险

引脚和总线信号的传输

但这个方案需要考虑引脚的数量

  • 考虑地址位宽和数据位宽均为32位的AXI总线
    • araddr, awaddr, rdatawdata已经占用128个引脚
    • 加上各种控制信号, 总计约150个引脚
  • 若数据位宽是64位, 则总计需要占用200个以上的引脚
  • 需要采用较昂贵的封装方案

 

减少引脚数量 -> 对引脚进行分时复用(和SDRAM分时传输地址类似)

  • 每次传输AXI请求的一部分, 通过多个周期来传输一个完整的AXI请求
  • 例如, 若只用32个引脚, 可约定:
    • T0时刻传输写地址/T1时刻传输写数据/T2时刻传输其他控制信号
    • 对端芯片按照约定, 将这3个周期从32个引脚上收到的信息重新组合成一个AXI的写请求

总结

SoC = 硬件 + 软件

  • 不仅要了解硬件模块的底层细节
  • 还需要知道如何让软件使用这些硬件模块

 

  • DRAM是主流的存储技术
    • 从存储单元到颗粒
      • 并行总线端口, 突发传输
    • 从颗粒到内存条
      • 现代DRAM存储系统是个高维结构

 

  • NVBoard + ysyxSoC + NPC + AM + RT-Thread + FCEUX = 计算机系统!