0 核心思想
根据指令功能,分析出需求,从而得出需要的部件、控制信号以及其他设计。
1. 针对的指令
取指阶段,针对所有指令,任何指令都需要进行取指。
2 功能(需求)分析
CPU的内部采用的是字节编址,每次取指之后,都进行PC + 4
。
需要执行的操作是
- 取指令:M[PC]
- 更新PC:PC <- PC + 4
3 需要的部件
- PC寄存器
- 加法器
- 指令存储器ROM
4 控制线(控制信号)
4.1 自身必须
- clk:时钟信号(用于同步)
- reset:复位信号(清零 clear)
问题:PC更新,是上升沿还是下降沿?
答:这取决于设计。
4.2 来源外部
无
5 数据线
- 数据线存储的就是ROM的地址
- 由于MIPS内部都是32位运算,所以PC寄存器必须是32位
- 外部的ROM不一定是32位,完全可以小于32位,只需要截断PC高位即可
特别注意: 此处我们PC使用的是字节编址的地址值,而ROM使用的是字编址的地址值,一个地址代表了一条32位指令,因此,除了PC高位截断,最低两位也要截断,根据字节对齐原则,PC
的最低两位必然是00
。
6 地址线
无
7 注意点
- 字节编址的PC值与字编址的ROM地址值要对应(去掉最低2位)
- 对于数据输入端,未来可能有多个来源,还会增加控制信号以及多个可选择的数据,以后再增加即可。
- 注意模块的独立性!
8 错误的代码:模块功能混杂了
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2020/11/12 20:31:59
// Design Name:
// Module Name: pc_1
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//module pc_1(input clk,input rst_n,input [31:0] pcNew, // The PC value from outsideoutput [31:0] pcOld);reg [31:0] pc = 0;
assign pcOld = pc;
assign pcNew = pcOld;// Update PC register
always @(posedge clk)
beginif(rst_n == 1) // Xilinx 官方推荐:reset 高电平有效beginpc <= 0;endelsebeginpc <= pcNew + 4;end
endendmodule
来看看这个好像没什么错误的代码……
看起来,没什么错误呀!
但是,我们仿真一看:
module tb_pc_1;// pc_1 Parameters
parameter PERIOD = 10;// pc_1 Inputs
reg clk = 0 ;
reg rst_n = 1 ;
reg [31:0] pcNew = 0 ;// pc_1 Outputs
wire [31:0] pcOld ;initial
beginforever #(PERIOD/2) clk=~clk;
endinitial
begin#(PERIOD*2) rst_n = 0;
endpc_1 u_pc_1 (.clk ( clk ),.rst_n ( rst_n ),.pcNew ( pcNew [31:0] ),.pcOld ( pcOld [31:0] )
);endmodule
尴尬了,那么问题在哪里?
关注x,这说明,可能
- 没有输入这个信号,这显然不可能
- 信号冲突了,0和1一起输入,导致变成
x
这里,我们看看发生了什么。
蓝色框出来的是内部,蓝色外部是外部信号。
很明显……我们的+4
应该在外部执行,而不是内部……内部的4和外部的0撞一起了。
总结: 模块独立性非常重要,外部就是外部,内部就是内部,功能不要混杂在一起……
9 正确的代码
pc.v
module pc_1(input clk,input rst_n,input [31:0] pcNew, // The PC value from outside.output [31:0] pcOld);reg [31:0] pc = 0;
assign pcOld = pc;// Update PC register
always @(posedge clk)
beginif(rst_n == 1) // Xilinx 官方推荐:reset 高电平有效beginpc <= 0;endelsebeginpc <= pcNew + 4;end
endendmodule
RTL优化结果
测试的时候注意将pcNew和pcOld接一起。
tb_pc.v
module tb_pc_1;// pc_1 Parameters
parameter PERIOD = 10;// pc_1 Inputs
reg clk = 0 ;
reg rst_n = 1 ;
// reg [31:0] pcNew = 0 ;// pc_1 Outputs
wire [31:0] pcOld ;initial
beginforever #(PERIOD/2) clk=~clk;
endinitial
begin#(PERIOD*2) rst_n = 0;
endpc_1 u_pc_1 (.clk ( clk ),.rst_n ( rst_n ),.pcNew ( pcOld [31:0] ),.pcOld ( pcOld [31:0] )
);
这次就对啦!
10 后续的改进预告
后续,PC的值,可能不止是+4
还可能来自于j类指令或者b类,因此,真正输入的pc的pcNew,需要从不同的值中选出一个,需要多个跳转值的输入,需要正确的选择信号,这些都是pc模块可以增加的东西,后续有需要的时候再增加。
我们期待的是
- pc寄存器本身是独立的
- pc的输入是可选的
- 二者是分开的,先用其他方式选择好输入的数据,再输入到pc中更新,再输出,这几部分是相对独立的,尽管它们在一个模块中
图示如下:
红框内部是内部结构,外部是外部输入数据,包括
- 4个可能的pc输入情况
- 1个选择信号
- 1个pc输出信号