RISC-V处理器的设计与实现(三)—— 上板验证

news2024/12/26 9:32:32

文章目录

RISC-V处理器的设计与实现(一)—— 基本指令集_Patarw_Li的博客-CSDN博客

RISC-V处理器的设计与实现(二)—— CPU框架设计_Patarw_Li的博客-CSDN博客

RISC-V处理器的设计与实现(三)—— 上板验证_Patarw_Li的博客-CSDN博客 


前面我们用Verilog实现了一个简易的RISC-V处理器,并且写了一个简易的C程序,把它编译成机器指令后放到我们的处理器中运行,运行结果也是正确的。这次我会把我们的处理器移植到板子上(板子是野火家的征途Pro,型号为EP4CE10F17C8),并实现用串口给rom烧录程序(C语言编译后的机器指令),方便我们的测试。

一、添加串口

串口(UART)又名异步收发传输器(Universal Asynchronous Receiver/Transmitter),是一种通用的数据通信协议,它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将串行数据转换成并行数据。

串口包括RS232、RS499、RS423等接口标准规范,我们这里使用的是RS232:

上图为串口的通信方式,可以同时收发(全双工通信)。其中rx负责接收,tx负责发送,每次发送10bit数据(起始位+8bit数据+停止位),从最低位开始发送。 

使用串口的目的是为了给我们在板子上的处理器烧录可执行程序,因为我们处理器的rom是使用寄存器资源模拟出来的,所以移植到板子上后rom里面的内容就无法更改了,为了避免每次修改程序都要重新移植,我们直接使用串口对rom里面的内容进行修改。

下面是串口程序的代码,其中rx和tx用于接收和传输bit数据;rom_erase_en_o是为了在指令写入rom之前,对rom进行全擦除;rom_wr_en_o、rom_wr_addr_o、rom_wr_data_o分别是写使能、写地址、写数据信号,用于给rom写入数据。

// 串口模块,目前只用于下载程序到rom中,波特率为9600,系统时钟频率为50MHz,传输一位需要5208个时钟周期
module uart(

    input   wire                        clk                 ,
    input   wire                        rst_n               ,
    
    input   wire                        uart_rx             ,
    output  wire                        uart_tx             ,

    output  reg                         rom_erase_en_o      , // rom全擦除使能信号
    output  reg                         rom_wr_en_o         , // rom写使能信号
    output  reg[`INST_ADDR_BUS]         rom_wr_addr_o       , // rom写地址信号
    output  reg[`INST_DATA_BUS]         rom_wr_data_o         // rom写数据信号

    );
    
    parameter   BAUD_CNT_MAX = `CLK_FREQ / `UART_BPS;
    parameter   IDLE = 4'd0,
                BEGIN= 4'd1,
                BIT0 = 4'd2,
                BIT1 = 4'd3,
                BIT2 = 4'd4,
                BIT3 = 4'd5,
                BIT4 = 4'd6,
                BIT5 = 4'd7,
                BIT6 = 4'd8,
                BIT7 = 4'd9,
                END  = 4'd10;
    
    wire                        uart_rx_temp;
    reg                         uart_rx_delay; // 延迟后的rx输入
    reg[12:0]                   baud_cnt;      // 计数器
    reg[2:0]                    byte_cnt;      // 接收到的字节数
    reg[3:0]                    uart_state;    // 状态机
    reg[7:0]                    byte_data;     // 接收到的字节数据
    reg[`INST_DATA_BUS]         wr_data_reg;   // 字节数据拼接成的32位数据
    reg                         data_rd_flag;  // 数据就绪标志位
    
    // 将输入rx延迟4个时钟周期,减少亚稳态的影响
    delay_buffer #(
        .DEPTH(4),
        .DATA_WIDTH(1)
    ) u_delay_buffer(
        .clk           (clk),   //  Master Clock
        .data_i        (uart_rx),   //  Data Input
        .data_o        (uart_rx_temp)    //  Data Output
    );
    
    
    always @ (posedge clk) begin
        uart_rx_delay <= uart_rx_temp;
    end
    
    // baud_cnt计数
    always @ (posedge clk or negedge rst_n) begin
        if(!rst_n) begin 
            baud_cnt <= 13'd0;
        end
        else if(uart_state == IDLE || baud_cnt == BAUD_CNT_MAX - 1) begin
            baud_cnt <= 13'd0;
        end
        else begin
            baud_cnt <= baud_cnt + 1'b1;
        end
    end
    
    // byte_cnt计数
    always @ (posedge clk or negedge rst_n) begin
        if(!rst_n) begin 
            byte_cnt <= 3'd0;
        end
        else if(byte_cnt == 3'd4) begin
            byte_cnt <= 3'd0;
        end
        else if(uart_state == END && baud_cnt == 13'd0) begin
            byte_cnt <= byte_cnt + 1'b1;
        end
        else begin
            byte_cnt <= byte_cnt;
        end            
    end
    
    // data_rd_flag
    always @ (posedge clk or negedge rst_n) begin
        if(!rst_n) begin 
            data_rd_flag <= 1'b0;
        end
        else if(byte_cnt == 3'd4) begin
            data_rd_flag <= 1'd1;
        end
        else begin
            data_rd_flag <= 1'b0;
        end            
    end
    
    // wr_data_reg
    always @ (posedge clk or negedge rst_n) begin
        if(!rst_n) begin 
            wr_data_reg <= 32'd0;
        end
        else if(uart_state == END && byte_cnt != 3'd0 && baud_cnt == 13'd1) begin
            wr_data_reg <= {wr_data_reg[23:0], byte_data};
        end
        else begin
            wr_data_reg <= wr_data_reg;
        end            
    end
    
    // rom_wr_en_o,rom_wr_data_o
    always @ (posedge clk or negedge rst_n) begin
        if(!rst_n) begin 
            rom_wr_en_o <= 1'b0;
            rom_wr_data_o <= 32'd0;
        end
        else if(data_rd_flag == 1'b1) begin
            rom_wr_en_o <= 1'b1;
            rom_wr_data_o <= wr_data_reg;
        end
        else begin
            rom_wr_en_o <= 1'b0;
            rom_wr_data_o <= rom_wr_data_o;
        end            
    end
    
    // rom_wr_addr_o
    always @ (posedge clk or negedge rst_n) begin
        if(!rst_n) begin 
            rom_wr_addr_o <= 32'd0;
        end
        // 待数据写入后,地址+4
        else if(rom_wr_en_o == 1'b1) begin
            rom_wr_addr_o <= rom_wr_addr_o + 3'd4;
        end
        else begin
            rom_wr_addr_o <= rom_wr_addr_o;
        end            
    end
    
    // rom_erase_en_o
    always @ (posedge clk or negedge rst_n) begin
        if(!rst_n) begin 
            rom_erase_en_o <= 1'b0;
        end
        else if(uart_state == BEGIN && baud_cnt == 13'd0 && byte_cnt == 3'd0 && rom_wr_addr_o == 32'd0) begin
            rom_erase_en_o <= 1'b1;
        end
        else begin
            rom_erase_en_o <= 1'b0;
        end            
    end
    
    // uart_state状态机
    always @ (posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            uart_state <= IDLE;
            byte_data <= 8'd0;
        end
        else begin
            case(uart_state)
                IDLE: begin
                    if(uart_rx_temp == 1'b0 && uart_rx_delay == 1'b1) begin
                        uart_state <= BEGIN; 
                    end
                    else begin
                        uart_state <= uart_state;
                    end
                end
                BEGIN: begin
                    if(baud_cnt == BAUD_CNT_MAX - 1) begin
                        uart_state <= BIT0; 
                    end
                    else begin
                        uart_state <= uart_state;
                    end
                end
                BIT0: begin
                    if(baud_cnt == BAUD_CNT_MAX / 2 - 1) begin
                        byte_data <= {uart_rx_delay, byte_data[7:1]};
                    end
                    else if(baud_cnt == BAUD_CNT_MAX - 1) begin
                        uart_state <= BIT1; 
                    end
                    else begin
                        uart_state <= uart_state;
                    end
                end
                BIT1: begin
                    if(baud_cnt == BAUD_CNT_MAX / 2 - 1) begin
                        byte_data <= {uart_rx_delay, byte_data[7:1]};
                    end
                    else if(baud_cnt == BAUD_CNT_MAX - 1) begin
                        uart_state <= BIT2; 
                    end
                    else begin
                        uart_state <= uart_state;
                    end
                end
                BIT2: begin
                    if(baud_cnt == BAUD_CNT_MAX / 2 - 1) begin
                        byte_data <= {uart_rx_delay, byte_data[7:1]};
                    end
                    else if(baud_cnt == BAUD_CNT_MAX - 1) begin
                        uart_state <= BIT3; 
                    end
                    else begin
                        uart_state <= uart_state;
                    end
                end
                BIT3: begin
                    if(baud_cnt == BAUD_CNT_MAX / 2 - 1) begin
                        byte_data <= {uart_rx_delay, byte_data[7:1]};
                    end
                    else if(baud_cnt == BAUD_CNT_MAX - 1) begin
                        uart_state <= BIT4; 
                    end
                    else begin
                        uart_state <= uart_state;
                    end
                end
                BIT4: begin
                    if(baud_cnt == BAUD_CNT_MAX / 2 - 1) begin
                        byte_data <= {uart_rx_delay, byte_data[7:1]};
                    end
                    else if(baud_cnt == BAUD_CNT_MAX - 1) begin
                        uart_state <= BIT5; 
                    end
                    else begin
                        uart_state <= uart_state;
                    end
                end
                BIT5: begin
                    if(baud_cnt == BAUD_CNT_MAX / 2 - 1) begin
                        byte_data <= {uart_rx_delay, byte_data[7:1]};
                    end
                    else if(baud_cnt == BAUD_CNT_MAX - 1) begin
                        uart_state <= BIT6; 
                    end
                    else begin
                        uart_state <= uart_state;
                    end
                end
                BIT6: begin
                    if(baud_cnt == BAUD_CNT_MAX / 2 - 1) begin
                        byte_data <= {uart_rx_delay, byte_data[7:1]};
                    end
                    else if(baud_cnt == BAUD_CNT_MAX - 1) begin
                        uart_state <= BIT7; 
                    end
                    else begin
                        uart_state <= uart_state;
                    end
                end
                BIT7: begin
                    if(baud_cnt == BAUD_CNT_MAX / 2 - 1) begin
                        byte_data <= {uart_rx_delay, byte_data[7:1]};
                    end
                    else if(baud_cnt == BAUD_CNT_MAX - 1) begin
                        uart_state <= END; 
                    end
                    else begin
                        uart_state <= uart_state;
                    end
                end
                END: begin
                    if(baud_cnt == 2) begin
                        uart_state <= IDLE; 
                    end
                    else begin
                        uart_state <= uart_state;
                    end
                end
                default: begin
                    byte_data <= 8'd0;
                    uart_state <= IDLE;
                end
            endcase
        end
    end
    
endmodule

目前该串口只用于往rom中写入指令,之后会增加一些其他的功能。

二、上板验证

可以到我的仓库里面下载整个项目的代码:cpu_prj: 一个基于RISC-V指令集的CPU实现

进入到FPGA目录下,使用quartus打开工程(因为我现在手上只有altera的板子)。

首先绑定引脚:

clk为系统时钟,绑定你板子对应的时钟引脚即可;rst_n为复位信号,低电平有效;uart_rx和uart_tx为串口的接收和发送引脚,绑定你们板子上的串口引脚即可(这里要注意,不同板子串口使用的接口标准和波特率不一样,需要相应的修改,我这里接口规范是RS232,波特率为9600);res_data为ram中地址为0x00000000位置的数据,等下编写C程序会把结果存放到这个位置,绑定的引脚为我板子上的四个led灯:

如果程序计算结果为15,即1111,那么四个灯全亮,如果为3,则只亮右边两个灯。

引脚绑定完后进行编译, 连好板子烧录程序:

接下来就是去写一个C程序了,下面是一个简单的求和程序,计算结果为15,处理器执行完程序会让四个led灯全亮:

int main(){
    int n = 5;
    int sum = 0;
    for (int i = 1; i <= n; ++i) {
        sum = sum + i;
    }
    int* point;
    point = (int*) 0x00000000;
    *point = sum;
    return 0;
}

然后使用ripes把程序编译成二进制代码(不知道怎么使用配置ripes的可以看我上一篇文章:RISC-V处理器的设计与实现(二)—— CPU框架设计_Patarw_Li的博客-CSDN博客):

复制右边的二进制指令到matlab文件夹下的instructions.txt文件中: 

然后用matlab打开b2h文件,点击运行,把二进制指令转成16进制(因为野火的串口发送工具只能发送16进制):

将串口连接PC:

 

 打开野火的串口发送工具,配置如下:

加载我们之前生成的16进制txt文件:

先按一下板子上的复位键,然后打开串口,点击发送数据:

可以发现还没有变化: 

再按一下复位键,可以发现四个灯亮起:

既然可以执行C程序了,并且可以用C来控制led灯,那么我们用C语言来实现一个流水灯程序来看看把:

int main(){
    int* point;
    int sum1 = 1; // 0001
    int sum2 = 2; // 0010
    int sum3 = 4; // 0100
    int sum4 = 8; // 1000
    point = (int*) 0x00000000;
    *point = sum1;

    while(1){
        // 第一个灯亮起
        *point = sum1;
        for(int i = 0; i < 1000000; i++); // delay

        // 第二个灯亮起
        *point = sum2;
        for(int i = 0; i < 1000000; i++); // delay

        // 第三个灯亮起
        *point = sum3;
        for(int i = 0; i < 1000000; i++); // delay

        // 第四个灯亮起
        *point = sum4;
        for(int i = 0; i < 1000000; i++); // delay
    }

    return 0;
}

 还是和上面步骤一样,转成16进制后,先按复位键,然后点击发送:

等待一会之后,按下复位键,可以发现板子上的led交替闪烁,我们用C写的流水灯程序就实现啦!

 

 

三、总结与思考

这一次我们完成了将我们做的处理器移植到板子上,并且在使用我们的处理器运行C语言实现的流水灯程序,并且成功运行。这是不是意味着。。。。我们也能在我们的处理器上跑一个简易的操作系统!接下来我会研究如何到我们的处理器上跑起来一个简易的操作系统,之后也会更新相关的文章~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/695051.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

人机混合智能概述

人机混合智能是指将人类的智能和计算机的智能结合起来&#xff0c;实现更加智能化的决策和行动。人机混合智能的发展历史可以追溯到20世纪50年代早期&#xff0c;当时计算机还是庞大的机器&#xff0c;只能由专业人员操作。但随着计算机技术的不断发展&#xff0c;出现了更为普…

JavaScript之鼠标事件、坐标轴、定位、clientXY、offsetXY、layerXY、pageXY、screenXY

文章目录 MouseEvent的事件类别阻止鼠标的默认事件去除单击右键菜单阻止图像默认拖拽阻止文字的拖拽和选择阻止表单提交及重置打印输出MouseEvent对象内容clientX和clientY与x和yoffsetXYlayerXYpageXYscreenXY总结 MouseEvent的事件类别 序号事件描述1mousedown鼠标按下2mouse…

多元回归预测 | Matlab鲸鱼算法(WOA)优化极限学习机ELM回归预测,WOA-ELM回归预测,多变量输入模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元回归预测 | Matlab鲸鱼算法(WOA)优化极限学习机ELM回归预测,WOA-ELM回归预测,多变量输入模型 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源码 %% 清空环…

【C/C++】使用类和对象 设计立方体案例

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

6.2.2 复制、删除与移动: cp, rm, mv

要复制文件&#xff0c;使用cp(copy)指令&#xff0c;移动目录与文件&#xff0c;使用mv(move)&#xff0c;这个指令直接拿来作更名的动作&#xff08;rename&#xff09;&#xff0c;移除是rm(remove)。 cp &#xff08;复制文件或目录&#xff09; 复制&#xff08;cp&#…

(07)装拆箱,自定义泛型,泛型约束,foreach,枚举器,迭代器,文件目录操作,TreeView,递归

一、作业问题 1.CompareTo是按什么规则标准进行比较的&#xff1f; 当前区域性执行单词 (区分大小写和区分区域性) 比较。 有关单词、字符串和序号排序 的详细信息&#xff0c;请参阅 System.Globalization.CompareOptions。 并不是按照…

每天一点Python——day42

#第四十二天 #判断字典中关键字是否存在in 存在返回Ture&#xff1b;反之为False not in 不存在返回True&#xff1b;反之为False#例&#xff1a; b{师傅:1000,师祖:10000,徒弟:500} print(师傅in b) print(师傅 not in b) #字典元素的删除del 字典名[健名]#例 a{张三:100,李四…

为什么现代的低代码开发平台都不支持导出源代码?

摘要&#xff1a;本文由葡萄城技术团队于CSDN原创并首发。转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 初次接触低代码的程序员大多会纠结一个问题&#xff0c;为什么功能越强大的低代码开发平…

C语言进阶--自定义类型详解

目录 一.结构体 1.1.结构的声明 1.2.结构的自引用 1.3.结构体变量的定义和初始化 1.4.结构成员的访问 1.5.结构体内存对齐 1.6.修改默认对齐数 1.7.offsetof宏 1.8.结构体传参 1.9.位段 二.枚举 2.1.枚举的定义 2.2.枚举的使用 2.3.枚举的优点 三.联合(共用体) …

ODrive电路设计中的接地环路

对于要进行通信的电气设备,大多数时候它们需要公共接地连接。最佳实践是将接地连接回一个点,称为“星形接地”。如果有多个接地路径,则会形成“接地环路”。接地环路和导线电感可能会导致 ODrive 等大电流电子设备出现问题。作为可能出错的示例,请查看下图。 问题: 问题在…

【计算机网络】数据链路层--点对点协议PPP

1.概念 2.构成 3.封装成帧 - 帧格式 4.透明传输 4.1字节填充法&#xff08;面向字节的异步链路&#xff09; 4.2.比特填充法&#xff08;面向比特的同步链路&#xff09; 5.差错检测 6.工作状态 7.小结

使用Vite 搭建高可用的服务端渲染SSR工程

在非常早期的 Web 开发中&#xff0c;大家还在使用 JSP 这种古老的模板语法来编写前端的页面&#xff0c;然后直接将 JSP 文件放到服务端&#xff0c;在服务端填入数据并渲染出完整的页面内容&#xff0c;可以说那个时代的做法是天然的服务端渲染。但随着 AJAX 技术的成熟以及各…

Typescript中的interface,type和class的相同点和不同点

感觉他们很像是不是&#xff1f; 他们确实有一些相同点&#xff1a; 相同点&#xff1a; 它们都可以用来描述对象的形状&#xff0c;即属性和方法。它们都可以被继承或实现&#xff0c;形成新的类型或类。它们都可以使用泛型参数&#xff0c;增加类型的灵活性和复用性。 不同…

jenkins shell脚本问题

问题描述&#xff1a; mac电脑配置了jenkins,同样的脚本&#xff0c;mac 电脑终端执行没有问题&#xff0c;复制到jenkins时&#xff0c;jenkins shell命令识别不了 -n指令。 解决方案&#xff1a; jenkins 系统配置中&#xff0c;找到shell 模块&#xff0c;配置上本地的路…

继骨传导耳机之后,新发布开放式耳机又成断货王!2年3代爆款,南卡怎么吸引年轻人?

今年618后&#xff0c;南卡的开放式耳机OE Pro成了新一代“断货王”&#xff0c;火爆程度直逼南卡的骨传导耳机Pro系列。 仔细想想&#xff0c;南卡已做出了3代爆款&#xff1a;骨传导Pro系列、骨传导Noe系列&#xff0c;南卡开放式OE系列&#xff0c;并且每一代都带动了该系列…

四、Docker镜像详情

学习参考&#xff1a;尚硅谷Docker实战教程、Docker官网、其他优秀博客(参考过的在文章最后列出) 目录 前言一、Docker镜像1.1 概念1.2 UnionFS&#xff08;联合文件系统&#xff09;1.3 Docker镜像加载原理1.4 重点理解 二、docker commit 命令2.1 是什么&#xff1f;2.2 命令…

分布式调用与高并发处理 Zookeeper分布式协调服务

一、Zookeeper概述 1.1 集中式和分布式 单机架构 一个系统业务量很小的时候所有的代码都放在一个项目中就好了&#xff0c;然后这个项目部署在一台服务器上&#xff0c;整个项目所有的服务都由这台服务器提供。 缺点&#xff1a; 服务性能存在瓶颈&#xff0c;用户增长的时候…

LENOVO联想笔记本电脑 拯救者Y520-15IKBN(80Y5)原装Win10系统文件,恢复出厂OEM系统

lenovo联想笔记本电脑&#xff0c;拯救者Y520-15IKBN(1050、1050Ti) (80Y5)出厂状态Windows10系统&#xff0c;原装OEM系统镜像 系统自带所有驱动、出厂主题壁纸LOGO、Office办公软件、联想电脑管家等预装程序 所需要工具&#xff1a;16G或以上的U盘 文件格式&#xff1a;IS…

Python基础学习注意事项

1.Python中 小数字符串不可以转成int&#xff0c;即int("98.9")会报错&#xff01; 数字字符串串才可以转对应的int、float 2.float数据计算的时候精度会丢失&#xff01;解决办法&#xff1a;&#xff08;from decimal import Decimal&#xff08;可以计算准确&am…

npm启动,node.js版本过高

“dev_t”: “set NODE_OPTIONS”–openssl-legacy-provider" & npm run dev\n"