前言:本章内容主要是演示在vivado下利用Verilog语言进行单周期简易CPU的设计。一步一步自己实现模型机的设计。本章先介绍单周期简易CPU中基本时序逻辑部件设计。

环境:一台内存4GB以上,装有64位Windows操作系统和Vivado 2017.4以上版本软件的PC机。

本章所采用的指令为LoongArch之LA32R版

目录

Ⅰ前置知识 

0x00 32位寄存器DR

0x01 32位的程序计数器PC

0x02 通用寄存器堆Registers

0x03  32位RAM存储器

Ⅱ Verilog实现

0x00 32位寄存器DR

0x01 32位的程序计数器PC

0x02 通用寄存器堆Registers

0x03 32位RAM存储器


Ⅰ前置知识 

0x00 32位寄存器DR

32位寄存器DR,全称为数据寄存器(Data Register),是计算机中常见的寄存器之一。它用于存储数据,是CPU中的一个重要组成部分。 

本章介绍的32位寄存器DR模块的参考电路框图如下: 

该模块的功能及引脚信号说明如下:

信号名称

功能说明

DataIn

32位数据输入信号

DataOut

32位数据输出信号

clk

时钟信号

WE

数据存入信号。当WE为1且在clk上升沿到来时,数据存入寄存器;当WE为0时,寄存器输出维持不变。

0x01 32位的程序计数器PC

32位程序计数器(Program Counter,PC)是计算机中一种重要的寄存器,它通常存储下一条指令的内存地址,是CPU能够顺利地执行指令的关键。

PC寄存器在CPU中被设计为自加的寄存器,它会在一个时钟周期中自动递增,从而指向下一条指令的地址,使得CPU可以连续地执行一系列指令。在程序执行过程中,PC寄存器会不断地指向下一条要执行的指令,直到程序结束或出现异常情况。

在现代计算机中,PC寄存器的位数一般为32位。当程序执行中遇到条件分支、循环等需要改变执行流程的指令时,CPU会根据条件跳转到不同的地址,PC寄存器也就被设置为相应的地址,从而改变了程序的执行路径。

本章介绍的32位的程序计数器PC模块的参考电路框图如下: 

该模块的功能及引脚信号说明如下:

信号名称

功能说明

rst

PC异步清零信号,高电平有效,即:rst为1时,PCdata =0

clk

时钟信号

offset

32位偏移量

pc_inc

自增控制信号,与clk上升沿配合工作。

在clk上升沿时刻,当pc_inc=1时,PCdata =原PCdata +1;当pc_inc=0时,PCdata =原PCdata +offset。

PCdata

32位数据输出信号

0x02 通用寄存器堆Registers

通用寄存器堆 (Registers) 是计算机体系结构中的一种硬件组件,用于存储和操作数据。它通常由多个独立的寄存器组成,每个寄存器都有固定的位宽。寄存器堆用于执行各种计算机指令和数据传输操作。 

本章介绍的通用寄存器堆Registers的参考电路框图如下:  

该模块的功能及引脚信号说明如下:

信号名称

功能说明

busA和busB

两路32位数据输出信号

Ra(5位)

读寄存器编号输入信号,该编号指定的寄存器的值经过“取数延迟”后,输出到busA

Rb(5位)

读寄存器编号输入信号,该编号指定的寄存器的值经过“取数延迟”后,输出到busB

Rw(5位)

写寄存器编号输入信号,该编号指定的数据要写入哪个寄存器

busW

32位数据输入信号

clk

写操作时钟控制信号,上升沿有效

RegWr

写使能控制信号,clk上升沿时刻,若RegWr为1,则busW上的数据被存入Rw指定的寄存器中

注:

(1)寄存器堆的读操作不受clk控制;

(2)0号寄存器的值恒为0,不受写操作的影响。

0x03 32位RAM存储器

32位RAM存储器是一种具有32位数据线的随机存取存储器。RAM (Random Access Memory) 指的是随机存取存储器,它允许随机访问存储器中的任何位置,而无需按照顺序访问。32位RAM存储器的数据线有32条,可以同时传送32位二进制数据,因此它能够以非常快的速度读取和写入数据。

本章介绍的32位RAM存储器参考电路框图如下:   

 该模块的功能及引脚信号说明如下:

信号名称

功能说明

addr

32位地址总线,用于传送地址,以便按地址访问存储单元。

data_in

32位数据输入总线

data_out

32位数据输出总线

clk

时钟信号,上升沿有效

MemWrEn

写使能信号。

当MemWrEn为0时,数据从addr地址端口指定的内存单元读出。

当MemWrEn为1时,配合clk时钟信号工作,在clk上升沿,数据存入由addr地址端口指定的内存单元。

 Verilog实现

0x00 32位寄存器DR

设计一个32位的寄存器DR,利用Verilog HDL完成建模设计

设计代码:


 
module Register32 (
  input [31:0] DataIn,
  output reg [31:0] DataOut,
  input clk,
  input WE
);
 
  always @(posedge clk) begin
    if (WE) begin
      DataOut <= DataIn;
    end
  end
 
endmodule

  在Vivado中点击”RTL ANALYSIS->Open Elaborated Design”,可以查看综合得到的 32位寄存器DR的逻辑电路,如图所示: 

 仿真代码:

module Register32_Test;
 
  reg [31:0] DataIn;
  wire [31:0] DataOut;
  reg clk;
  reg WE;
 
  Register32 dut (
    .DataIn(DataIn),
    .DataOut(DataOut),
    .clk(clk),
    .WE(WE)
  );
 
  always begin
    #5 clk = ~clk; // 时钟周期为10个时间单位
  end
 
  initial begin
    clk = 0;
    WE = 0;
    DataIn = 32'h11111111; // 设置输入数据
    #20 WE = 1; // 在20个时间单位后,使WE为1,触发数据存入
    #20 WE = 0; // 在另外20个时间单位后,使WE为0,保持输出稳定
    #20 DataIn = 32'h11101011; // 修改输入数据
    #20 WE = 1; // 再次触发数据存入
    #50 $finish; // 结束仿真
  end
 
endmodule


仿真结果:

思考:

读者可以思考一个问题,刚开始时为什么DataOut为xxx态?

0x01 32位的程序计数器PC

设计代码:

 
module ProgramCounter(
  input rst,
  input clk,
  input [31:0] offset,
  input pc_inc,
  output reg [31:0] PCdata
);
 
  always @(posedge clk or posedge rst) begin
    if (rst) begin
      PCdata <= 32'd0;
    end else begin
      if (pc_inc) begin
        PCdata <= PCdata + 32'd1;
      end else begin
        PCdata <= PCdata + offset;
      end
    end
  end
 
endmodule


  在Vivado中点击”RTL ANALYSIS->Open Elaborated Design”,可以查看综合得到的32位的程序计数器PC的逻辑电路,如图所示:  

仿真代码:

module ProgramCounter_tb;
 
  reg rst;
  reg clk;
  reg [31:0] offset;
  reg pc_inc;
  wire [31:0] PCdata;
 
  ProgramCounter dut (
    .rst(rst),
    .clk(clk),
    .offset(offset),
    .pc_inc(pc_inc),
    .PCdata(PCdata)
  );
 
  initial begin
    rst = 1;
    clk = 0;
    offset = 32'h00000001;
    pc_inc = 0;
    #10;
 
    rst = 0;
    #10;
 
    offset = 32'h00000002;
    pc_inc = 1;
    #10;
 
    pc_inc = 0;
    #10;
 
    $display("PCdata = %h", PCdata);
 
    #10;
    $finish;
  end
 
  always #5 clk = ~clk;
 
endmodule


仿真结果:

调整进制后可观察到下列波形

注: 若不知道如何调整进制,可翻看本专栏的第一篇博客:

【单周期CPU】LoongArch | 立即数扩展模块Ext | 32位算术逻辑运算单元(ALU)_流继承的博客-CSDN博客

0x02 通用寄存器堆Registers

 设计代码:


module Registers(
  input [4:0] Ra,
  input [4:0] Rb,
  input [4:0] Rw,
  input [1:0] busW,
  input clk,
  input RegWr,
  output reg [31:0] busA,
  output reg [31:0] busB
);
 
  reg [31:0] regfile [0:31];
 
  always @(*) begin
    busA = regfile[Ra];
    busB = regfile[Rb];
  end
 
  always @(posedge clk) begin
    if (RegWr) begin
      if (Rw != 0)
        regfile[Rw] <= busW;
    end
  end
  initial begin
    for (integer i = 0; i <= 31; i = i + 1)
      regfile[i] = 32'h0;
  end
 
endmodule
 

   在Vivado中点击”RTL ANALYSIS->Open Elaborated Design”,可以查看综合得到的通用寄存器堆Registers的逻辑电路,如图所示:  

仿真代码:

module Registers_Test;
 
  reg [4:0] Ra;
  reg [4:0] Rb;
  reg [4:0] Rw;
  reg [1:0] busW;
  reg clk;
  reg RegWr;
  wire [31:0] busA;
  wire [31:0] busB;
 
  Registers dut (
    .Ra(Ra),
    .Rb(Rb),
    .Rw(Rw),
    .busW(busW),
    .clk(clk),
    .RegWr(RegWr),
    .busA(busA),
    .busB(busB)
  );
 
  initial begin
    clk = 0;
    RegWr = 0;
    Ra = 0;
    Rb = 0;
    Rw = 0;
    busW = 2'b01;
 
    #25;
 
    
   
    Ra = 2;
    Rb = 4;
    #25;
    $display("busA = %h, busB = %h", busA, busB);
   
 
 
    Rw = 6;
    busW = 2'b11;
    RegWr = 1;
    #50;
    $display("busA = %h, busB = %h", busA, busB);
 
    
    Ra = 6;
    Rb = 0;
    #25;
    $display("busA = %h, busB = %h", busA, busB);
 
    
    #10;
    $display("busA = %h, busB = %h", busA, busB);
  end
 
  always begin
    #50;
    clk = ~clk;
  end
 
endmodule


仿真结果:

调整进制后可观察到下列波形

0x03 32位RAM存储器

设计代码:

module RAM(
input MemWrEn, input clk,
input [31:0] addr,
input [31:0] data_in, output reg [31:0] data_out
);
reg [31:0] ram [0:31];
 
always@(posedge clk) begin
if (MemWrEn) ram[addr] <= data_in; end
always@(*) begin
if(MemWrEn==0) data_out <= ram[addr]; end
endmodule

    在Vivado中点击”RTL ANALYSIS->Open Elaborated Design”,可以查看综合得到的32位RAM存储器的逻辑电路,如图所示:  

仿真代码:

module RAM_sim; reg MemWrEn; reg clk;
reg [31:0] addr;
reg [31:0] data_in;
wire [31:0] data_out;
 
RAM
RAM1(.MemWrEn(MemWrEn),.clk(clk),
.addr(addr),.data_in(data_in),.data_out(data_out));
 
initial begin MemWrEn=1; addr=20; data_in=60; #200;
addr=15; data_in=30; #200;
addr=5; data_in=120; #200;
MemWrEn=0; addr=20; #100;
addr=15; #100;
addr=5; #200;
end
 
initial clk = 0;
always # 50 clk = ~clk; endmodule


仿真结果:

调整进制后可观察到下列波形

思考

在完成上述四个模块之后,读者可以思考一下:

1.“如何将数据存入暂存器”、“如何读取暂存器的值”等问题。

2.“如何控制PC的值自增或加偏移量”、“如何给PC清0”等问题。

3.“如何指定要写入数据的寄存器编号”、“如何保证0号寄存器的值不受写入操作的影响而始终保持为0”,“如何指定从哪个寄存器读出数据”和“是否具有将所有寄存器一次性全部清0的功能”等问题。

4.“如何指定要写入数据的内存单元地址” “如何指定从哪个内存单元读出数据”和“是否具有将所有内存单元一次性全部清0的功能”等问题。

END 


因为作者的能力有限,所以文章可能会存在一些错误和不准确之处,恳请大家指出!