前言
复习一下研究生时候一个小的课设题,内容在《Verilog HDL高级数字设计(第二版)》“7.3 RSIC存储程序机的设计与综合”,目的是做一个精简指令集计算机。之前一直很是不能理解其中的设计思路与结构,最近这段时间在有了一些实践经验后有种豁然开朗、一拍即合的感觉,趁着还记得赶紧记下来。
完整目录:
概述
RSIC处理器的结构图我直接从书上拓下来下来了,如下图:
整个处理器电路分为三个通路:控制通路(Controller)、处理通路(Processor)和存储通路(Memory),运算器ALU包含在处理通路中。
整体系统的运转方式,在我理解,先由软件将要执行的指令和数据写入mem中,之后进入硬件工作流程:
1.取值:PC模块中记录着当前需要取得指令的存储地址;
2.译码:IR模块对指令进行译码,告知ALU模块需要对数据进行什么操作;
3.执行:ALU对立即数/通用寄存器值进行运算,输出运算结果至通用寄存器;按照需求更新PC/IR/Add_R/Reg_Z等模块;
4.写入:将执行处理结果写入mem;
5.回收:回收指令上报完成,继续将PC中指向的下一条指令读出至IR中,继续1~5的过程。
6.结束:直到遇到finish指令,结束硬件处理;
Processor
由于processor组件内部是最为复杂的,因此在看完概述后,我觉得有必要先展开下processor中的子模块,直接带着代码来展开可能更易于入门。
PC
程序计数器PC,用来存放着下一条待取指令的存储地址,其代码实现如下:
1. module program_count #( 2. `include "C:/Users/gaoji/Desktop/RISC_SPM/src/para_def.v" 3. )( 4. output reg [WORD_WD -1:0] count_out, 5. input [WORD_WD -1:0] count_in, 6. input load, incr, clk, rst_n 7. ); 8. 9. always @(posedge clk)begin 10. if(rst_n == 1'b0)begin 11. count_out <= {WORD_WD{1'b0}}; 12. end 13. else if(load == 1'b1)begin 14. count_out <= count_in; 15. end 16. else if(incr == 1'b1)begin 17. count_out <= count_out + 'h1; 18. end 19. else begin 20. count_out <= count_out; 21. end 22. end 23. 24. endmodule//: program_count
其主要输入输出信号为:
count_out:待取指令的存储地址,该地址最终作用于Memory,将指令取出经由总线传送至IR模块;
incr:指令自加1高有效,该信号为高是count_out自加1,因为一般取值就是顺序取值,controller控制这个信号让PC自加1一直向下执行就可以了;
load:为高时使count_out跳转至count_in,一般遇到跳转指令时会用到该信号;
count_in:配合load使用,需要进行取值跳转时使用;
OK,PC就是控制指令地址的模块,用来加载下一条指令到IR模块。
IR
指令寄存器IR,用来存放当前正在执行的指令,简单而言就是一个寄存器足以:
1. module reg_unit #( 2. `include "C:/Users/gaoji/Desktop/RISC_SPM/src/para_def.v" 3. )( 4. output reg [WORD_WD -1:0] data_out, 5. input [WORD_WD -1:0] data_in, 6. input load, clk, rst_n 7. ); 8. 9. always @(posedge clk)begin 10. if(rst_n == 1'b0)begin 11. data_out <= {WORD_WD{1'b0}}; 12. end 13. else if(load == 1'b1)begin 14. if(DISPLAY_EN == 1) $display("%0t, %m, data_out update to 'b%8b('h%2h-'d%0d)", $realtime, data_in, data_in, data_in); 15. data_out <= data_in; 16. end 17. else begin 18. data_out <= data_out; 19. end 20. end 21. 22. endmodule//: reg_unit
load:在load信号有效时,data_in会被写入都寄存器中,load由controller控制;
data_in:总线上的待写入数据,一般为PC->读取mem->读至总线->IR的数据流;
data_out:当前指令,因为指令的固定位是运算操作指示,因此该信号同时送至两个方向,方向一是给controller取解析指令,方向二是给ALU告诉他当前进行什么运算;
可见,IR就是用来暂存当前的指令,controller和ALU都是从IR获取当前要进行的操作的。
R0/R1/R2/R3
四个通用寄存器,用来在任何时间地点把需要用的值存在里面;
比如说我需要mem[100]的值+mem[200]值,那么我受制于总线和指令解析,我只能先把mem[100]的值取出来先放到R0去,然后再取mem[200]的值,在ALU中做加法然后通过总线写到R1,最后再讲R1的值通过总线写入到mem中;
因此这几个通用寄存器就是各种按需做数据倒腾的,代码其实就用上面的reg_unit就可以了。
Mux_1/Bus_1
进行数据交流的模块是无限的,而数据总线是有限的,因此需要对数据进行选择上总线;
Mux_1对接着Bus_1,根据controller的指示,选择将R0/R1/R2/R3/PC的值写到Bus_1,Bus_1的下游再根据需求从Bus_1上获取数据,举几个例子:
指令需要R0去做加法,那么Mux_1选择R0,ALU从Bus_1上取数据;
指令需要将R1存入mem中,那么Mux_1选择R1,mem从Bus_1上取数据;
需要进行取值,那么Mux_1选择PC,mem从Bus_1上取数据;
Mux如何选择,下游谁需要这个数据,一切尽在Controller控制之下,因此Mux_1的代码如下:
1. module mux_5sel1 #( 2. `include "C:/Users/gaoji/Desktop/RISC_SPM/src/para_def.v" 3. )( 4. output reg [WORD_WD -1:0] mux_out, 5. input [WORD_WD -1:0] mux_in0, mux_in1, mux_in2, mux_in3, mux_in4, 6. input [SEL1_WD -1:0] sel 7. ); 8. 9. always @(*)begin 10. case(sel) 11. 'h0: mux_out = mux_in0; 12. 'h1: mux_out = mux_in1; 13. 'h2: mux_out = mux_in2; 14. 'h3: mux_out = mux_in3; 15. 'h4: mux_out = mux_in4; 16. default: mux_out = {WORD_WD{1'bx}}; 17. endcase 18. end 19. 20. endmodule//: mux_5sel1