前言
最近研读《自己动手写CPU》,发现很多模块的加入,使得整个工程变得非常奇怪,让人不能理解为什么这样设计,现在我将重新构建整个工程,实现更加快速理解,原书籍使用Verilog代码编写,为了避免抄袭,我使用VHDL重新构建代码。部分模块当前不需要使用,我将会将之去除, 在后续文章中加入
一、OpenMIPS教学版介绍
本书中需要一个或操作,采用五级级流水。
总结说明下
- 五级整数流水线,分别是:取指、译码、执行、访存、回写。
- 哈佛结构,分开的指令、数据接口。
根据百度百科介绍:中央处理器首先到程序指令存储器中读取程序指令内容,解码后得到数据地址,再到相应的数据存储器中读取数据,并进行下一步的操作(通常是执行)。程序指令存储和数据存储分开,可以使指令和数据有不同的数据宽度。
指令集位于CPU内部,而数据集处于外部为ram接口
- 32个32位整数寄存器。
- 大端模式。
大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。
小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。 - 大多数指令可以在一个时钟周期内完成。
二、流程介绍
1、取指
CPU工作模式,当开始后会不断从程序存储其中取出指令,其中指令地址由一个加法器进行累加,直到遇到停机指令
程序计数器
程序计数器PC中的数值,用来指示当前指令在主存中的位置。当一条指令被取出后,PC中的数值将根据指令字长度而自动递增。若为单字长指令,则PC+1,若为双字长指令,则PC+2,依此类推。OpenMIPS指令集为4个字节
process(clk,rst)
begin
if(rst = '1')then --//cpu全局复位信号
PC <= (others => '0');
elsif(clk'event and clk = '1') then
if(rom_ec_i = '1')then --//停机指令,当遇到停机指令时停止
PC <= PC + x"4";
else
PC <= (others => '0');--//程序开始地址
end if;
end if;
end process;
rom_addr_o <= PC;
程序存储器
程序为CPU循环运行程序,固定在rom中不需要进行修改。
subtype word_t is std_logic_vector(7 downto 0);--//8位存储器
type memory_t is array(2**8-1 downto 0) of word_t;
signal rom_data : memory_t := init_rom;--//rom_data中存放的是运行程序的二进制码
rom_data_o <= x"00000000" when (rom_ec_i = ’0‘)else (rom_data(to_integer(rom_addr_i)) &
rom_data(to_integer(rom_addr_i + x'1')) &
rom_data(to_integer(rom_addr_i + x'2')) &
rom_data(to_integer(rom_addr_i + x'3')) );
取指仅有地址累加时使用一个周期,而读取指令集时使用组合逻辑不需要时钟
2、译码
指令拆解
op <= rom_data_in(31 downto 26); --//指令码
reg_addr_o <= rom_data_in(25 downto 21); --//读取通用寄存器的地址
ram_wd_o <= rom_data_in(20 downto 16); --//结果保存地址
imm(15 downto 0) <= rom_data_in(15 downto 0); --//立即数参与计算的值
imm(31 downto 16) <= (others => rom_data_in(15));
with op select
aluop_o <= "00100101" when "001101" ; --//对运算类型进行赋值
"00000000" when "000000" ; --//默认状态
with op select
reg1_read_o <= "1" when "001101" ; --//对寄存器1是否读取有效
"0" when "000000" ; --//默认状态
with op select
reg2_read_o <= "0" when "001101" ; --//对寄存器2是否读取有效
"0" when "000000" ; --//默认状态
with op select
ram_we_o <= "1" when "001101" ; --//指令是否有效
"0" when "000000" ; --//默认状态
建立RAM存储器
--创建ram存储列表
subtype word_t is std_logic_vector(31 downto 0);--//8位存储器
type memory_t is array(31 downto 0) of word_t;
signal ram_data : memory_t := (others => (others => '0'));--共32个32位寄存器
--组合逻辑读取1
process(reg1_read_i & ram_we_i)
case reg1_read_i & ram_we_i is
when "00" => reg1_data_o <= x"0000000";
when "01" => reg1_data_o <= x"0000000";
when "10" => reg1_data_o <= ram_data(to_intager(reg_addr_i));
when "11" => reg1_data_o <= ram_wdata_i;
when others => reg1_data_o <= x"0000000";
end case
end process
--组合逻辑读取2
process(reg2_read_i & ram_we_i)
case reg2_read_i & ram_we_i is
when "00" => reg2_data_o <= x"0000000";
when "01" => reg2_data_o <= x"0000000";
when "10" => reg2_data_o <= ram_data(to_intager(reg_addr_i));
when "11" => reg2_data_o <= ram_wdata_i;
when others => reg1_data_o <= x"0000000";
end case
end process
寄存配置参数
process(clk,rst) -- 写入数据
begin
if(rst = '1')then --//cpu全局复位信号
ex_aluop <= "00000000";
ex_wd_o <= "000000"; --写入地址
ex_we_o <= ‘1’ --写入使能
elsif(clk'event and clk = '1')then
ex_aluop <= aluop_i;
ex_wd_o <= ram_wd_i;
ex_we_o <= ram_we_i --写入使能
end if;
end process;
process(clk,rst) -- 写入数据
begin
if(rst = '1')then --//cpu全局复位信号
ex_reg1 <= x"00000000";
ex_reg2 <= x"00000000";
elsif(clk'event and clk = '1')then
case reg1_read_i & reg2_read_i is
when "00" => ex_reg1 <= imm;ex_reg2 <= imm;
when "01" => ex_reg1 <= imm;ex_reg2 <= reg2_data_i;
when "10" => ex_reg1 <= reg1_data_i;ex_reg2 <= imm;
when "11" => ex_reg1 <= reg1_data_i;ex_reg2 <= reg1_data_i;
when others => NULL;
end case;
end if;
end process;
3、执行
执行需要一个周期,直接使用时序逻辑
with ex_aluop select
result <= ex_reg1 | ex_reg2; when "00100101";
result <= x'0000000'; when others;
process(clk,rst) -- 写入数据
begin
if(rst = '1')then --//cpu全局复位信号
mem_data <= x"00000000";
mem_wd_o <= "000000";
mem_we_o <= '0';
elsif(clk'event and clk = '1')then
mem_data <= result;
mem_wd_o <= ex_wd_i;
mem_we_o <= ex_we_i;
end if
end process
4、访存
根据指令需要,有可能要访问主存,读取操作数,这样就进入了访存取数(Memory,MEM)阶段。
此阶段的任务是:根据指令地址码,得到操作数在主存中的地址,并从主存中读取该操作数用于运算。
程序存放地址
一个或操作不需要访存,但是5级流水会有这个操作,此时打一拍
process(clk,rst) -- 写入数据
begin
if(rst = '1')then --//cpu全局复位信号
mem_wb_data <= x"00000000";
mem_wb_wd_o <= "000000";
mem_wb_we_o <= '0';
elsif(clk'event and clk = '1')then
mem_wb_data <= mem_data;
mem_wb_wd_o <= mem_wd_o;
mem_wb_we_o <= mem_we_o;
end if
end process
5、回写
process(clk,rst) -- 写入数据
begin
if(rst = '1')then --//cpu全局复位信号
ram_data <= (others => (others => '0'));
elsif(clk'event and clk = '1') then
if(ram_we_i = '1')then --//停机指令,当遇到停机指令时停止
case ram_addr_i is
when "00000" => ram_data(0) <= x"00000000"; --//默认0寄存器为0
when others => ram_data(to_intager(ram_addr_i)) => ram_data <= ram_wdata_i;
end case;
else
ram_data <= ram_data;
end if;
end if;
end process;
总结
本篇简单的讲解了5级流水,冯诺依曼处理器执行方式,有问题,请在下方评论区指出,本篇代码都在文本上进行编写,可能存在语法错误,可以自行修改,也可以再下方留言