CPU设计实战-最小SOC的实现

一 顶层模块的实现

顶层模块用于对之前文章里介绍的五级流水线的各个模块进行例化,也就是连线,那么顶层模块的输入输出接口如何呢?

首先输入要有时钟复位信号,还要有一个来接收指令存储器里的数据记为rom_data_i

输出因为要去读取指令存储器中的数据,所以要输出读地址以及一个使能信号。

具体实现就参照我们上一节所做好的数据通路进行连线,连接和数据通路图如下:CPU设计实战-第一条指令ori的实现即最简单的五级流水线的实现


module my_mips(

    input clk,
    input rst,

    input [31:0] rom_data_i,
    
    output [4:0] rom_addr_o,
    output rom_ce_o

    );

wire [31:0] pc;

//if/id输出与id模块输入模块的连接
wire [31:0] id_pc_i;
wire [31:0] id_inst_i;
//ID模块的输出与ID/EX输入的连接
wire [7:0]  id_aluop_o ;
wire [2:0]  id_alusel_o;
wire [31:0] id_reg1_o  ;
wire [31:0] id_reg2_o  ;
wire [4:0]  id_wd_o    ;
wire        id_wreg_o  ;
//ID/EX模块的输出与EX输入的连接
wire [7:0]  ex_aluop_i ;
wire [2:0]  ex_alusel_i;
wire [31:0] ex_reg1_i  ;
wire [31:0] ex_reg2_i  ;
wire [4:0]  ex_wd_i    ;
wire        ex_wreg_i  ;
//EX模块的输出与EX/MEM输入的连接
wire [31:0] ex_wdata_o ;
wire [4:0]  ex_wd_o    ;
wire        ex_wreg_o  ;
//EX/MEM模块的输出与MEM输入的连接
wire [31:0] mem_wdata_i;
wire [4:0]  mem_wd_i   ;     
wire        mem_wreg_i ;
//MEM模块的输出与MEM/WB输入的连接
wire [31:0] mem_wdata_o;
wire [4:0]  mem_wd_o   ;
wire        mem_wreg_o ;
//MEM/WB模块输出与regfile寄存器堆的连接
wire [31:0] wb_wdata_i;
wire [4:0]  wb_wd_i   ;
wire        wb_reg_i  ;
//ID译码模块与寄存器堆模块的连接
wire [4:0]  reg1_addr ;
wire [4:0]  reg2_addr ;
wire        reg1_read ;
wire        reg2_read ;
wire [31:0] reg1_data ;
wire [31:0] reg2_data ;

pc_reg  u_pc_reg (
    .clk                     ( clk         ),
    .rst                     ( rst         ),

    .pc                      ( pc   ),
    .ce                      ( rom_ce_o          )
);

//输出到指令存储器的地址就是pc地址
assign rom_addr_o = pc;

if_id  u_if_id (
    .clk                     ( clk             ),
    .rst                     ( rst             ),
    .if_pc                   ( pc),
    .if_inst                 ( rom_data_i ),

    .id_pc                   ( id_pc_i ),
    .id_inst                 ( id_inst_i)
);

id  u_id (
    .clk                     ( clk                 ),       
    .rst                     ( rst                 ),       
    .pc_i                    ( id_pc_i ),       
    .inst_i                  ( id_inst_i),       
    .reg1_data_i             ( reg1_data ),       
    .reg2_data_i             ( reg2_data ),       

    .reg1_addr_o             ( reg1_addr  ),       
    .reg2_addr_o             ( reg2_addr  ),       
    .reg1_read_o             ( reg1_read  ),       
    .reg2_read_o             ( reg2_read  ),       
    .aluop_o                 ( id_aluop_o      ),       
    .alusel_o                ( id_alusel_o     ),       
    .reg1_o                  ( id_reg1_o       ),       
    .reg2_o                  ( id_reg2_o       ),       
    .wd_o                    ( id_wd_o         ),       
    .wreg_o                  ( id_wreg_o       )        
);
    
id_ex  u_id_ex (
    .clk                     ( clk               ),
    .rst                     ( rst               ),
    .id_aluop                ( id_aluop_o   ),
    .id_alusel               ( id_alusel_o  ),
    .id_reg1                 ( id_reg1_o    ),
    .id_reg2                 ( id_reg2_o    ),
    .id_wd                   ( id_wd_o      ),
    .id_wreg                 ( id_wreg_o    ),

    .ex_aluop                ( ex_aluop_i  ),
    .ex_alusel               ( ex_alusel_i ),
    .ex_reg1                 ( ex_reg1_i   ),
    .ex_reg2                 ( ex_reg2_i   ),
    .ex_wd                   ( ex_wd_i     ),
    .ex_wreg                 ( ex_wreg_i   )
);

ex  u_ex (
    .rst                     ( rst              ),
    .aluop_i                 ( ex_aluop_i  ),
    .alusel_i                ( ex_alusel_i ),
    .reg1_i                  ( ex_reg1_i   ),
    .reg2_i                  ( ex_reg2_i   ),
    .wd_i                    ( ex_wd_i     ),
    .wreg_i                  ( ex_wreg_i   ),

    .wdata_o                 ( ex_wdata_o ),
    .wd_o                    ( ex_wd_o    ),
    .wreg_o                  ( ex_wreg_o  )
);

ex_mem  u_ex_mem (
    .clk                     ( clk               ),
    .rst                     ( rst               ),
    .ex_wdata                ( ex_wdata_o ),
    .ex_wd                   ( ex_wd_o    ),
    .ex_wreg                 ( ex_wreg_o  ),

    .mem_wdata               (  mem_wdata_i ),
    .mem_wd                  (  mem_wd_i    ),
    .mem_wreg                (  mem_wreg_i  )
);

mem  u_mem (
    .rst                     ( rst             ),
    .wdata_i                 ( mem_wdata_i ),
    .wd_i                    ( mem_wd_i    ),
    .wreg_i                  ( mem_wreg_i  ),

    .wdata_o                 ( mem_wdata_o ),
    .wd_o                    ( mem_wd_o    ),
    .wreg_o                  ( mem_wreg_o  )
);

mem_wb  u_mem_wb (
    .clk                     ( clk               ),
    .rst                     ( rst               ),
    .mem_wdata               ( mem_wdata_o ),
    .mem_wd                  ( mem_wd_o    ),
    .mem_wreg                ( mem_wreg_o  ),

    .wb_wdata                ( wb_wdata_i ),
    .wb_wd                   ( wb_wd_i    ),
    .wb_reg                  ( wb_reg_i   )
);

regfile  u_regfile (
    .clk                     ( clk                     ),   
    .rst                     ( rst                     ),   
    .raddr1                  ( reg1_addr  ),   
    .re1                     ( reg1_read                     ),   
    .raddr2                  ( reg2_addr ),   
    .re2                     ( reg2_read                     ),   
    .waddr                   ( wb_wd_i  ),   
    .wdata                   ( wb_wdata_i ),   
    .we                      ( wb_reg_i                      ),   

    .rdata1                  ( reg1_data         ),   
    .rdata2                  ( reg2_data         )    
);

endmodule

二 指令存储器ROM的实现

之前说过我们的my_mips模块的输入输出都需要从指令存储器进行关联。

指令存储器中存储的指令码,需要进行的操作就是对其按照指令地址进行读取指令码

故输入指令地址addr,输出指令码inst,另外需要一个使能信号,如下图:

具体实现过程注意以下几点:

1.首先要定义这个rom的大小,方法和实现寄存器堆一样,本质上是一个数组寄存器堆实现

2.然后要把指令写入这个rom,这里采用系统函数¥readmemh,它可以将指定的文件中的数据写入rom中

3.最后进行读操作时需要注意,指令存储器的寻址方式是字节型,而我们pc端口模块给出的地址是32位的,所以需要除以4再写入地址,具体的操作用左移两位来实现的。那么既然要移位地址肯定要知道地址的宽度,需要知道地址宽度如何计算。

2^地址宽度=元素大小,根据define文件发现定义的元素大小为131071即128k,那么地址宽度为17

`include "defines.v"

module inst_rom(
        input  ce,
        input  [4:0]  addr,
        output reg [31:0] inst
    );

    reg[31:0] inst_mem[0:`InstMemNum-1];

    initial $readmemh ("inst_rom.data", inst_mem);

    always @(*) begin
        if (!ce) begin
            inst = 0;
        end
        else begin
            inst = inst_mem[addr[`InstMemNumLog2+1:2]];//左移两位实现除以4
        end
    end


endmodule

 三 data文件分析(由于编译环境还未搭建,本质上是一个手动编译指令的操作)

inst_rom.data

34011100
34210020
3421ff00
342100ff

分析一下data文件中指令数据,其是按照十六进制存储的,我们展开一个第一个来看看

00110100000000010001 0001 0000 0000
ORI指令的oprsrtimm立即数

回顾ORI指令的功能

指令用法为: ori rs, rt, immediate,作用是将指令中的16位立即数immediate进行无符号
扩展至32位,然后与索引为rs的通用寄存器的值进行逻辑“或”运算,运算结果保存到索引
为rt的通用寄存器中。

 可以明白第一行指令的操作是将0号寄存器中的值和立即数进行逻辑或运算,由于0号寄存器中的值恒为0,故结果还是原来的imm立即数,并将这个结果保存到1号寄存器中。剩余指令以此类推

四 最小SOPC的实现

有了cpu和指令存储器,我们大概可以搭建一个最简单的SOPC,将两个模块连接如下:

最小SOPC对应的模块为mymips_min_sopc,其输入只有时钟和复位信号,然后按照上图进行例化即可。



module mymips_min_sopc(

        input clk,
        input rst
    );

wire rom_ce;
wire [31:0] inst;
wire [4:0]  inst_addr;

my_mips  u_my_mips (
    .clk                     ( clk                ),        
    .rst                     ( rst                ),        
    .rom_data_i              ( inst ),        

    .rom_addr_o              ( inst_addr ),        
    .rom_ce_o                ( rom_ce           )
);    

inst_rom  u_inst_rom (
    .ce                      ( rom_ce           ),
    .addr                    ( inst_addr ),

    .inst                    ( inst)
);

endmodule

五 编写测试程序

读者可能还是会疑惑data文件里的数据是怎么得来的,其实是基于我们编写的测试文件inst_rom.S

此文件为汇编文件,用于测试ori指令

ori $1,$0,0x1100
ori $2,$0,0x0020
ori $3,$0,0xff00
ori $4,$0,0xffff

可以知道此代码的功能是将立即数与0号寄存器(恒为0)进行或运算故结果还是原来的立即数

按照正常顺序是通过编译器编译此文件得出指令寄存器中的二进制data文件,由于配置编译环境花费额外篇幅讲解,此处采用手动编译的方法。

例如第一行指令对应的二进制如下图:

 转化为16进制为data文件中第一行的数据,其他直径以此类推,这就解释了data文件中的数据是怎么来的。

接下来就可以写testbech来测试我们的程序了,仿真文件很好写,对于sopc模块只需要给与时钟信号和复位信号即可,通过观察仿真文件里的寄存器的值即可知道指令运行是否正常。

  • 29
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 《Xilinx All Programmable Zynq-7000 SoC设计指南》是一本为设计师提供关于Xilinx Zynq-7000 SoC架构和应用的指南。该指南旨在帮助设计师全面了解并充分利用Xilinx Zynq-7000 SoC的优势。 首先,该指南详细介绍了Zynq-7000 SoC的基本架构和组成部分,包括处理系统(PS)和可编程逻辑(PL)。处理系统基于ARM Cortex-A9处理器,提供了高性能和低功耗的处理能力,而可编程逻辑则提供了高度可定制和可扩展的硬件加速功能。 其次,该指南介绍了如何使用Xilinx Vivado设计套件进行系统设计和开发。Vivado提供了丰富的设计工具和IP核库,帮助设计师快速实现各种功能模块。这些工具和IP核可以帮助设计师完成从系统级设计到RTL开发的整个设计流程。 此外,该指南还讨论了基于Zynq-7000 SoC的嵌入式系统设计的关键问题。例如,如何有效地利用处理系统和可编程逻辑的协同工作,如何优化系统引脚分配和时序约束,以及如何进行系统级的功耗优化等等。 最后,该指南还提供了大量的设计实例和案例分析,帮助设计师理解如何应用Zynq-7000 SoC进行具体的应用开发。这些实例和案例分析涵盖了各种应用领域,包括工业控制、汽车电子、通信系统等。 总之,Xilinx All Programmable Zynq-7000 SoC设计指南通过详细介绍Zynq-7000 SoC的架构和组成部分,以及提供丰富的设计工具和实例,帮助设计师快速掌握并应用Zynq-7000 SoC进行系统设计和开发。这本指南是设计师在使用Zynq-7000 SoC时的重要参考资料。 ### 回答2: 《Xilinx All Programmable Zynq-7000 SoC设计指南》是一本关于Xilinx Zynq-7000 SoC设计指南。Zynq-7000 SoC是一款Xilinx公司推出的集成了ARM Cortex-A9双核处理器和可编程逻辑的SoC芯片。 首先,指南介绍了Zynq-7000 SoC的基本架构和特性。它解释了SoC芯片中ARM处理器和FPGA可编程逻辑相互协作的方式,并详细说明了它们各自的功能和优势。此外,指南还介绍了Zynq-7000 SoC支持的外设和接口,如UART、SPI、I2C、GPIO等,以及相应的硬件和软件编程方法。 其次,指南详细说明了如何利用Xilinx Vivado工具和SDK进行Zynq-7000 SoC的开发和编程。它包含了从项目创建、设计激活到综合、实现和生成比特流的全过程。指南还介绍了如何将ARM处理器和可编程逻辑之间的数据传输进行优化,以实现最佳性能。 此外,指南还提供了一系列面向不同应用场景的示例设计。这些设计包括图像处理、机器人控制、工业自动化等,可以作为开发者的参考和借鉴。每个示例设计都包含了详细的框图、资源配置和软件代码,帮助开发者更好地理解和应用Zynq-7000 SoC。 最后,指南还介绍了一些与Zynq-7000 SoC相关的开发资源和工具。这些资源包括官方网站、社区论坛、开发板和模块等。开发者可以通过这些资源获取更多的技术支持和学习资料,加速项目的开发和上市时间。 总之,《Xilinx All Programmable Zynq-7000 SoC设计指南》详细介绍了Zynq-7000 SoC的架构、开发方法、示例设计以及相关资源和工具。对于希望了解和应用Zynq-7000 SoC的开发者来说,这本指南是一本非常有价值的参考资料。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值