数据存储器
构建数据存储器
在前几个小节中, 我们构建了有处理器内核和指令存储器的 SoC, 它能执行指令和利用内部的寄存器存储数据. 使用寄存器存储数据显然是不现实的, 因此, 在一个能够执行复杂程序的系统中, 必须拥有数据存储器.
数据存储器在SoC中扮演什么角色?
数据存储器不仅用于数据的运算, 还在程序跳转、异常处理的过程中发挥重要作用. 数据存储器特定的一段地址空间会被编译器分配为栈存储空间, 在程序跳转和异常处理的过程中, 会通过堆栈和出栈的方式保存和恢复跳转前的执行状况. 这里介绍 Cortex-M0 处理器对异常的处理过程:
- 异常发生后, 寄存器组中的 8 个寄存器, 即 R0-R3, R12, 链接寄存器 (R14/LR), 返回地址寄存器 (R15/PC) 以及程序状态寄存器 (xPSR) 会被自动压入当前的栈空间里面, 以确保服务程序执行完成后还能继续返回当前程序.
- 链接寄存器 (LR/R14) 会被自动更新为异常返回时的特殊值 (EXC_RETURN, 该值的使用可以保证返回地址的选择性, 如选择 MSP 还是 PSP), 然后 PC 寄存器的值更新为对应的异常向量地址, 然后开始执行异常服务程序.
- 在异常服务程序执行结束以后, 会利用 LR 寄存器中的特殊值, 触发异常的返回机制. 处理器还会查看当前是否有其他异常需要处理, 如果没有, 处理器就会恢复之前存储在栈空间的寄存器, 继续执行异常之前的程序.
添加数据存储器的理论根据
- 我们这里添加的数据存储器相当于是 SoC 的外设, 添加外设就要涉及到 Memory Map 与总线扩展的相关知识了. 如果你对相关知识有所遗忘, 可以回看2.1.3 地址空间 - SoC 的"门牌号系统".
修改硬件代码添加数据存储器
参考实验二添加 RAMCODE 步骤. 在顶层模块中已经添加好 RAMDATA 总线接口以及对应的 Block RAM , 需要将 RAMDATA 总线接口接入总线扩展模块中预留的 P1 接口.
第一步, 在 "GPIO/rtl/AHBlite_Decoder.v" 中的 RAMDATA (Port 1) 端口参数将其使能.
/*RAMDATA enable parameter*/
parameter Port1_en = 0,
/************************/
改为
/*RAMDATA enable parameter*/
parameter Port1_en = 1,
/************************/
第二步, 根据存储系统章节所述的 memory map 推荐地址分配, 我们在地址 0x20000000 分配了一个 512KB 的RAMDATA.所以 RAMDATA 的总线编码为 0x20000000-0x2000ffff , 因为对于一次总线操作, 只要地址总线的高 16 位为 0x2000 , 则 Decoder 认为这是一次对数据存储器的操作, 进而生成数据存储器总线选择信号. 在译码部分插入 RAMDATA 的译码器代码.
//0x20000000-0x2000ffff
/*Insert RAMDATA decoder code there*/
assign P1_HSEL = 1’b0;
/***********************************/
改为
//0x20000000-0x2000ffff
/*Insert RAMDATA decoder code there*/
assign P1_HSEL = (HADDR[31:16] == 16'h2000) ? Port1_en : 1'b0;
/***********************************/
第三步, 我们借助于 readmemh 函数, 将 keil 编译生成的机器码, 初始化到程序 RAM 中. 即在 "Block_RAM.v" 文件中修改初始化函数中的文件路径, 使得编译器能够找到 code.hex 文件.
initial begin
$readmemh("F:/CortexM0_SoC/Task3/GPIO/keil/code.hex", mem);
end
至此, 我们就完成了 RAMDATA 部分的修改.