一生一芯——用RTL实现最简单的处理器NPC(南大数逻计组RV32ICPU) 代码及思考参考

计算机组成原理实验——五、单周期CPU设计 实验目的 1. 掌握指令执行过程的5个阶段 2. 掌握每条指令的数据通路选择 3. 掌握译码器和控制器的功能和实现 4. 掌握数据输入输出处理的方法 5. 实现risc-v中RV32I指令的单周期CPU 6. 利用实现的risc-v CPU实现平方数 二.实验内容 1. 实现risc-v中37条RV32I指令的单周期 cpu; 2. 完成1后在该cpu上实现斐波那契数。 阅读详情

前言

本文介绍自己前期用Verilog编写的基于RV32I指令集的CPU,个人认为是最简单最易懂的版本(即没做什么优化,即代码很水,没有考虑一些奇怪的情况,只适用简单指令序列,即题目所示最简单的处理器),适合新手练手Verilog以及了解简单CPU的运行过程。本文基于一生一芯,利用Verilator进行仿真,并用gtkwave查看波形,不了解verilator和gtkwave的可以看前几篇文章。

CPU数据通路

首先参考南京大学的单周期RV32ICPU,画出自己处理器的数据通路,主要包括PC自增,跳转(Branch),指令寄存器,通用寄存器,译码,ALU,内存Ram等部件。
基本上是之前实验的一个集成。
cpu数据通路
不多说了,直接上代码。

Makefile

同样,makefile参考NVboard的编写,不了解的可以看前几篇文章

TOPNAME = top
NXDC_FILES = constr/top.nxdc
INC_PATH ?=

VERILATOR = verilator
VERILATOR_CFLAGS += -MMD --build --trace -cc \
				-O3 --x-assign fast --x-initial fast --noassert

BUILD_DIR = ./build
OBJ_DIR = $(BUILD_DIR)/obj_dir
BIN = $(BUILD_DIR)/$(TOPNAME)

default: $(BIN)

$(shell mkdir -p $(BUILD_DIR))

# constraint file
SRC_AUTO_BIND = $(abspath $(BUILD_DIR)/auto_bind.cpp)
$(SRC_AUTO_BIND): $(NXDC_FILES)
	python3 $(NVBOARD_HOME)/scripts/auto_pin_bind.py $^ $@

# project source
VSRCS = $(shell find $(abspath ./vsrc) -name "*.v")
CSRCS = $(shell find $(abspath ./csrc) -name "*.c" -or -name "*.cc" -or -name "*.cpp")
CSRCS += $(SRC_AUTO_BIND)

# rules for NVBoard
include $(NVBOARD_HOME)/scripts/nvboard.mk

# rules for verilator
INCFLAGS = $(addprefix -I, $(INC_PATH))
CXXFLAGS += $(INCFLAGS) -DTOP_NAME="\"V$(TOPNAME)\""
LDFLAGS += -lSDL2 -lSDL2_image

$(BIN): $(VSRCS) $(CSRCS) $(NVBOARD_ARCHIVE)
	@rm -rf $(OBJ_DIR) wave.vcd
	$(VERILATOR) $(VERILATOR_CFLAGS) \
		--top-module $(TOPNAME) $^ \
		$(addprefix -CFLAGS , $(CXXFLAGS)) $(addprefix -LDFLAGS , $(LDFLAGS)) \
		--Mdir $(OBJ_DIR) --exe -o $(abspath $(BIN))

all: default

run: $(BIN)
	./build/top +trace

clean:
	rm -rf $(BUILD_DIR) wave.vcd

.PHONY: default all clean run

sim_main.cpp

cpp文件也是可以参考前几篇文章

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<nvboard.h>

#include"Vtop.h"
#include"verilated.h"
#include"verilated_vcd_c.h"

//static Vtop* top;

void nvboard_bind_all_pins(Vtop* top);

// void single_cycle(){
   
   
//     top->clk = 0; top->eval(); m_trace->dump(contextp->time());  // update output
//     top->clk = 1; top->eval(); m_trace->dump(contextp->time()); 
// }

// void reset(int n) {
   
   
//   top->rst = 1;
//   while (n-- > 0) single_cycle();
//   top->rst = 0;
// }

int main(int argc, char** argv) {
   
   
    VerilatedContext* contextp = new VerilatedContext;
    contextp->traceEverOn(true);
    contextp->commandArgs(argc, argv);

    Vtop* top = new Vtop{
   
   contextp};

    nvboard_bind_all_pins(top);
    nvboard_init();
    VerilatedVcdC* m_trace = new VerilatedVcdC;
    top->trace(m_trace, 99);
    m_trace->open("wave.vcd");
    
    //reset(10);
    while (!contextp->gotFinish()) {
   
   
    	//single_cycle();
	nvboard_update();

        contextp->timeInc(1);
        top->clk = 0; top->eval(); // update output
        m_trace->dump(contextp->time());
        contextp->timeInc(1);
        top->clk = 1; top->eval();
        m_trace->dump(contextp->time());
    }
    m_trace->close();
    delete top;
    delete contextp;
    return 0;
}

Verilog

指令寄存器instr_mem

首先我们考虑如何存储我们的指令,这是一切的开始,cpu本质上就是在不断执行一条条的指令。
很简单,利用一个每个元素为32位的大数组instr_memory进行指令存储。每次取指令只需要根据instr_addr进行数组访问,即可取出指令。需要注意的是,pc+4相当于指令寄存器的地址+1。(为什么? 详见计组)

module instr_mem(
    input [31:0] instr_addr,
    output reg [31:0] instr
);
    reg [31:0] instr_memory [65535:0];

    always@(*)begin
        instr = instr_memory[instr_addr>>2];  // pc + 4 -> instr_addr + 1
    end

initial begin
    instr_memory[0] = {
   
   12'b000000000001,5'b00001,3'b000,5'b00010,7'b0010011};
                     //      imm          rs1      func3   rd       opcode
    instr_memory[1] = {
   
   12'b000000000001,5'b00010,3'b001,5'b00011,7'b0010011};
end

endmodule

id

取完指令,就该进行指令的译码了。一条指令的各个字段代表什么意思,即如何解读指令,是由指令集定义的。这里使用的RISC-V指令集。
RISC-V指令集
译码器本质上就是根据指令的内容,将其分割并根据各个字段的意义,生成控制字段送往各个部件。
译码器写得很垃圾,但是足够简单明了,旨在希望你理解译码器的工作流程(其实是没时间优化)一共400多行的译码代码。
1.根据opcode字段(最低7位),识别出指令是什么类型(R-TYPE还是I-TYPE)
2.再根据func3(12-14位)识别出是什么具体的指令,有的指令还需要根据func7(28-31位)进行识别。
3.经过前两步可以识别一条具体的指令,根据具体指令的操作将对应的控制信号传出。
这就是译码部件的全部过程,可能你疑惑rs1和rs2为什么不传出,建议回去再看一下数据通路图,你应该能理解。(具体原因描述在通用寄存器实现处)

module id(
    input [31:0] instr,
    output reg [31:0] imm,
    output reg [3:0] ALUctr,
    output reg ALUBsrc,
    output reg RegWr,
    output reg [2:0] branch,
    output reg MemtoReg,
    output reg [2:0] MemOp,
    output reg MemWr
);
    reg [6:0] opcode;
    reg [2:0] func3;
    reg [6:0] func7;
    reg [31:0] immI;
    reg [31:0] immS;
    reg [31:0] immB;
    reg [31:0] immJ;

    assign opcode = instr[6:0];
    assign func3  = instr[14:12];
    assign func7  = instr[31:25];
    assign immI = {
   
   {
   
   20{
   
   instr[31]}}, instr[31:20]};
    assign immS = {
   
   {
   
   20{
   
   instr[31]}}, instr[31:25], instr[11:7]};
    assign immB = {
   
   {
   
   20{
   
   instr[31]}}, instr[7], instr[30:25], instr[11:8], 1'b0};
    assign immJ = {
   
   {
   
   12{
   
   instr[31]}}, instr[19:12], instr[20], instr[30:21], 1'b0};


    always @(*) begin
        case(opcode)
            7'b0010011: begin  // instr type I 
                case(func3)
                    3'b000: begin  // addi
                        imm     = immI;
                        ALUctr  = 4'b0000;
                        ALUBsrc = 1'b1;
                        RegWr   = 1'b1;
                        branch  = 3'b000;
                 
设计npc、为npc搭建基础设施 ysyx 阅读详情

相关推荐

从零构建RISC-V单周期CPU:37条RV32I指令的硬件实现与斐波那契数列验证

本文详细介绍了从零构建RISC-V单周期CPU的全过程,包括37条RV32I指令的硬件实现与斐波那契数列验证。通过模块化设计思路和Verilog代码示例,帮助读者深入理解计算机组成原理和CPU设计核心概念,特别适合初学者学习单周期CPU的实现方法。

weixin_29196315的博客 148

【一生一芯03】verilator仿真框架搭建

Verilator是一种开源的Verilog/SystemVerilog仿真器,可用于编译代码以及代码在线检查,Verilator能够读取Verilog或者SystemVerilog文件,并进行lint checks(基于lint工具的语法检测),并最终将其转换成C++的源文件.cpp和.h。

Springkiss的博客 1万+

【芯片设计- RTL 数字逻辑设计入门 5 -- RTL 全加器实现及验证】

逻辑综合(Synthesis)工具主要用于检查 RTL 代码是否可以综合成电路(与门、非门、或门、FF),具体来说的化就是在 RTL Code freeze 之后将前端设计工程师写的RTL code,映射到特定工艺库上(TSMC/UMC/SMIC),通过添加。逻辑综合完成后需要将 netlist + SDC 给到 backend,做物理版图(layout)clock transition 时钟从0->1 或者从 1->0 需要的时间;,那么就会执行 Makefile 文件中的 删除文件操作。

CodingCos的博客 1475

riscv 32单周期处理器cpu,工程基于vivado,指令集rv32i,systemverilog编写,结构简单,指令

此外,我们还可以通过阅读RISC-V中文手册和指令集文档的中文版本来深入了解这款处理器的相关知识。此外,这款处理器的仿真过程也相对便捷,这使得我们可以快速验证设计的正确性。riscv 32单周期处理器cpu,工程基于vivado,指令集rv32i,systemverilog编写,结构简单,指令存在ram中,可仿真,代码结构清晰,适合初学者学习,并赠送包括riscv中文手册和riscv指令集文档的中文版本。今天,就让我们从RISC-V 32位单周期处理器这个工程案例开始,探索其中的技术奥秘和编程魅力。

BLbmqBQBV的博客 979

可重定义RISC-V处理器核f32c.zip

f32c是可重定向的,标量的,流水线的32位处理器核,其可以执行RISC-V或MIPS指令集的子集。它在参数化VHDL中实现,其允许具有不同面积/速度权衡的合成,并且包括分支预测器,异常处理控制块和可选的直接映射高速缓存。 RTL代码还包括诸如多端口SDRAM和SRAM控制器,具有复合(PAL),HDMI,DVI和VGA输出的视频帧缓冲器,具有用于精灵和窗口的简单2D加速,浮点向量处理器,SPI,UART,PCM音频,GPIO,PWM输出和定时器,以及为各种制造商的众多流行FPGA开发板定制的胶合逻辑。在合成整数基准中,内核以存储在片上块RAM中的代码和数据产生3.06 CoreMark / MHz和1.63 DMIPS / MHz(1.81 DMIPS / MHz,功能内联)。当配置为16 KB指令和4 KB数据高速缓存,并且代码和数据存储在外部SDRAM中时,内核产生2.78 CoreMark / MHz和1.31 DMIPS / MHz。包括定时器和UART的性能优化的f32c SoC仅占用1048个6输入LUT,同时仍然能够在最紧凑配置(仅消耗697(649个逻辑加48个存储器)LUT)下合成时执行gcc生成的代码。可配置选项包括:C_arch RISC-V或MIPS ISAC_big_endian总线端C_mult_enable合成乘法单位C_branch_likely支持分支延迟时隙取消C_sign_extend支持符号扩展指令C_movn_movz支持条件移动指令C_ll_sc支持原子读 - 修改 - 写构造C_branch_prediction合成分支预测器C_bp_global_depth全局分支历史跟踪大小C_result_forwarding合成结果旁路C_load_aligner合成加载对齐器C_full_shifter pipelined而不是迭代移位器C_icache_size指令高速缓存大小(0到64 KB)C_dcache_size数据高速缓存大小(0到64 KB)C_debug综合单步调试模块

基于DE2-115红外解码实验

基于DE2-115红外解码实验   一、   功能描述 在同一个遥控电路中通常要使用实现不同的遥控功能或区分不同的机器类型, 这样就要求信号按一定的编码传送,编码则会由编码芯片或电路完成。对应于编码芯片通常会有相配对的解码芯片或包含解码模块的应用芯片。在实际的产品设计或业余电子制作中,编码芯片并一定能完成我们要求的功能,这时我们就需要了解所使用的编码芯片到底是如何编码的。只有知道编码方式,我...

乘风的博客 2505

MicroLogix1400编程(使用RSLogix 500 + RSLinx Classic Lite)

MicroLogix1400编程

noscallion的博客 1万+

R语言dlnm包避坑指南:手把手教你配置交叉基矩阵(附温度分层与滞后多项式设置)

本文详细介绍了R语言dlnm包在配置交叉基矩阵时的实用技巧与避坑指南,特别针对温度分层与滞后多项式设置提供了具体参数配置建议。通过解析`crossbasis()`函数的核心参数、温度分析的分层策略以及不同基函数的适用场景,帮助研究者高效构建分布滞后非线性模型,避免常见错误并优化模型性能。

weixin_34094282的博客 395

【漏洞复现】CVE-2024-44902 Thinkphp反序列化漏洞

Thinkphp v6.1.3至v8.0.4版本中存在反序列化漏洞,攻击者可利用此漏洞执行任意代码。

渗透之路,攻防之间皆学问 6691

Keras中神经网络可视化模块keras.utils.visualize_util安装配置方法

Keras中神经网络可视化模块keras.utils.visualize_util安装配置方法

jacke121的专栏 2754

【WRFDA教程】客观分析(OBSGRID)详解

客观分析(OBSGRID)详解

WW、forever的博客 1130

华为od 面试八股文_C++_02_含答案

华为od 面试八股文_C++_02_含答案

MISAYAONE的博客 3108

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

上一篇: 南大数字逻辑与计算机组成课程实验六移位寄存器和桶形移位器 思路与代码参考
下一篇: 国科大计算机体系结构第一次作业 思路及代码参考
long0_o
博客等级 码龄2年 140粉丝 8原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

long0_o

可以吗!真的可以吗!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值