RISC-V处理器的设计与实现(三)—— 上板验证(基于野火征途Pro开发板)

目录

文章传送门

一、添加串口

二、上板验证

三、总结与思考


文章目录

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

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

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

RISC-V处理器设计(四)—— Verilog 代码设计-CSDN博客 

RISC-V处理器设计(五)—— 在 RISC-V 处理器上运行 C 程序-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里面的内容进行修改。

下面是串口烧录模块的Verilog代码,文件为FPGA\rtl\debug\uart_debug.v。

  • uart_rx:用于接收bit数据。
  • debug_en_i:用于使能串口模块(否则会和uart.v模块冲突),该引脚绑定到板子上的key1,在烧录程序时按住key1,烧录完后松开即可。
  • rib_wr_req_o:请求使用总线传递数据。
  • mem_wr_en_o:memory写使能信号
  • mem_wr_addr_o:memory写地址。
  • mem_wr_data_o:memory写数据。
// 串口模块,目前只用于下载程序到memory中,波特率为9600,系统时钟频率为50MHz,传输一位需要5208个时钟周期
module uart_debug(

    input   wire                        clk                 ,
    input   wire                        rst_n               ,
    
    input   wire                        debug_en_i          , // 模块使能信号
    input   wire                        uart_rx             ,

    output  reg                         rib_wr_req_o        , // 总线请求信号
    output  reg                         mem_wr_en_o         , // mem写使能信号
    output  reg[`INST_ADDR_BUS]         mem_wr_addr_o       , // mem写地址信号
    output  reg[`INST_DATA_BUS]         mem_wr_data_o         // mem写数据信号

    );
    
    parameter   BAUD_CNT_MAX = `CLK_FREQ / `UART_BPS;
    parameter   IDLE = 4'd0,
                BEGIN= 4'd1,
                SEND_BYTE = 4'd2,
                END  = 4'd3;
    
    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;  // 数据就绪标志位
    reg[3:0]                    bit_cnt;       // 比特计数
    
    // 将输入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
    
    // rib_wr_req_o
    always @ (posedge clk or negedge rst_n) begin
        if(!rst_n) begin 
            rib_wr_req_o <= 1'b0;
        end
        else if(debug_en_i == 1'b0) begin
            rib_wr_req_o <= 1'b0;
        end
        else if(data_rd_flag == 1'b1) begin
            rib_wr_req_o <= 1'b1;
        end
        else begin
            rib_wr_req_o <= 1'b0;
        end
    end
    
    // baud_cnt计数
    always @ (posedge clk or negedge rst_n) begin
        if(!rst_n) begin 
            baud_cnt <= 13'd0;
        end
        else if(debug_en_i == 1'b0) 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(debug_en_i == 1'b0) 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(debug_en_i == 1'b0) 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(debug_en_i == 1'b0) begin
            wr_data_reg <= 32'd0;
        end
        else if(uart_state == END && byte_cnt != 3'd0 && baud_cnt == 13'd1) begin
            wr_data_reg <= {byte_data, wr_data_reg[31:8]};
        end
        else begin
            wr_data_reg <= wr_data_reg;
        end            
    end
    
    // mem_wr_en_o,mem_wr_data_o
    always @ (posedge clk or negedge rst_n) begin
        if(!rst_n) begin 
            mem_wr_en_o <= 1'b0;
            mem_wr_data_o <= 32'd0;
        end
        else if(debug_en_i == 1'b0) begin
            mem_wr_en_o <= 1'b0;
            mem_wr_data_o <= 32'd0;
        end
        else if(data_rd_flag == 1'b1) begin
            mem_wr_en_o <= 1'b1;
            mem_wr_data_o <= wr_data_reg;
        end
        else begin
            mem_wr_en_o <= 1'b0;
            mem_wr_data_o <= mem_wr_data_o;
        end            
    end
    
    // mem_wr_addr_o
    always @ (posedge clk or negedge rst_n) begin
        if(!rst_n) begin 
            mem_wr_addr_o <= 32'd0;
        end
        else if(debug_en_i == 1'b0) begin
            mem_wr_addr_o <= 32'd0;
        end
        // 待数据写入后,地址+4
        else if(mem_wr_en_o == 1'b1) begin
            mem_wr_addr_o <= mem_wr_addr_o + 3'd4;
        end
        else begin
            mem_wr_addr_o <= mem_wr_addr_o;
        end            
    end
    
    // uart_state状态机
    always @ (posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            uart_state <= IDLE;
            byte_data <= 8'd0;
            bit_cnt <= 4'd0;
        end
        else if(debug_en_i == 1'b0) begin
            uart_state <= IDLE;
            byte_data <= 8'd0;
            bit_cnt <= 4'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 <= SEND_BYTE; 
                    end
                    else begin
                        uart_state <= uart_state;
                    end
                end
                SEND_BYTE: begin
                    if(bit_cnt == 4'd7 && baud_cnt == BAUD_CNT_MAX - 1) begin
                        bit_cnt <= 4'd0;
                        uart_state <= END; 
                    end
                    else 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
                        bit_cnt <= bit_cnt + 1'b1; 
                    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
                    bit_cnt <= 4'd0;
                    byte_data <= 8'd0;
                    uart_state <= IDLE;
                end
            endcase
        end
    end
    
endmodule

二、上板验证

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

配套的简易操作系统程序也在更新中:riscv_os: 一个RISC-V上的简易操作系统

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

首先绑定引脚:

  • clk:系统时钟。
  • rst_n:复位信号,低电平有效。
  • gpio_pins:目前只用于控制led,绑定板子上的四个led(gpio_data寄存器位于地址0x30000004处,控制其低四位值即可控制led灯)。
  • uart_debug_pin:串口烧录模块使能信号,绑定板子上的key1。
  • uart_tx、uart_rx:串口发送和接收引脚,绑定你们板子上的串口引脚即可(这里要注意,不同板子串口使用的接口标准和波特率不一样,需要相应的修改,我这里接口规范是RS232,波特率为19200

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

接下来就是去写一个C程序了,下面是一个简单的求和程序,计算结果sum为15,后面会把sum结果写入gpio_data寄存器,这样gpio_data寄存器的内容的低四位即为1111,会让板子上的led灯全亮起来:

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

    int* gpio_data = (int*) 0x30000004; // gpio_data寄存器的地址
    *gpio_data = sum; // 将gpio_data寄存器的内容改为sum值
    return 0;
}

如何配置交叉编译工具链和烧录到板子上可以参考我的这篇文章 :

开发一个RISC-V上的操作系统(一)_Patarw_Li的博客-CSDN博客

将串口连接PC,执行Python串口发送程序 serial_send.py 烧录编译生成的.bin文件(烧录前一定先把上面的博客看完!):

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

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

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

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

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

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

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

    return 0;
}

还是和上面步骤一样,烧录程序到板子上后,按下复位键,可以发现板子上的led交替闪烁,我们用C写的流水灯程序就实现啦!

 

 

三、总结与思考

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

如果遇到问题也欢迎加群 892873718 交流~

  • 7
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 21
    评论
### 回答1: 香山开源高性能risc-v处理器设计实现 pdf 是一份介绍risc-v处理器设计实现的文档。risc-v是一种基于精简指令集的处理器架构,因其简洁、开放、可定制的特点,受到了广泛关注。 该文档详细介绍了如何在FPGA(现场可编程门阵列)上实现risc-v处理器。作者分享了开发处理器的具体步骤、设计方案、软件支持等方面的知识。并且,该处理器的性能也在文档中详细评估。 该处理器在性能、功耗等方面有着不俗表现。其主频可以达到400MHz以上,实现了乘-累加指令,并且具有64位寄存器和32个寄存器,支持RV64I标准指令集,内存延迟很低,具有较好的实时性能。 总之,香山开源高性能risc-v处理器设计实现 pdf是一份非常值得阅读的文档,其中对risc-v处理器设计实现有着详细的介绍,可以帮助人们了解risc-v处理器的优势和不足,为相关领域的开发提供指导。 ### 回答2: 《香山开源高性能risc-v处理器设计实现》是一本介绍如何设计实现RISC-V处理器的书籍,也是一本介绍RISC-V处理器架构的入门读物。该书深入浅出地介绍了RISC-V架构和处理器设计的基本知识,并通过实例详细地述说了如何基于该架构,设计实现一个高性能的RISC-V处理器。 该书的作者陈海波教授是一位专业的计算机架构工程师,他在书中将自己多年来的实践经验与理论知识完美结合,将复杂的概念以通俗易懂的方式呈现给读者。全书以RISC-V指令集架构、CPU内部运行机制、核心子系统设计等方面为主线,详细地介绍了处理器设计流程和实现细节。最后以在FPGA上的逻辑仿真和运行测试为实例,让读者真正了解该处理器的可靠性和高性能。 通过学习《香山开源高性能risc-v处理器设计实现》这本书,读者可以深入学习RISC-V处理器架构以及处理器设计方面的知识。不仅可以对计算机组成原理和计算机系统结构有更深入的理解,还可以掌握实际的设计开发技巧。对于从事处理器设计嵌入式系统设计等领域的专业人员,是一本不可多得的参考书籍。 ### 回答3: 《香山开源高性能risc-v处理器设计实现》是一本介绍RISC-V处理器架构的书籍,该处理器架构是一个基于精简指令集(RISC)的开源处理器架构。本书主要介绍了香山开源处理器设计实现,是一本深入学习RISC-V架构的重要参考书。 该书共分为八章,首先介绍了处理器的基本概念和RISC-V处理器架构的特点。接着深入分析了RISC-V处理器的指令集和管道,讲解了指令集概述、流水线架构、指令编码等内容。紧接着,本书介绍了一款基于RISC-V架构的香山开源处理器,详细介绍了处理器的数据通路、控制单元、存储结构等模块的实现原理。 在完成处理器架构描述之后,本书详细介绍了基于Vivado实现处理器设计和仿真调试的方法。通过实际例子的讲解,读者可以深入了解如何通过Vivado进行处理器设计和仿真调试。 该书最后介绍了一些关于RISC-V处理器架构的扩展和应用的内容,如在处理器架构扩展方面的内容,以及如何在RISC-V处理器架构上实现操作系统和应用程序的开发。 总之,《香山开源高性能risc-v处理器设计实现》是一本详细介绍RISC-V处理器架构和实现方法的书籍,对于学习处理器架构和实现的读者是一本不可多得的参考书。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值