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

文章目录

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里面的内容进行修改。

下面是串口接收程序的Verilog代码,其中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 <= {byte_data, wr_data_reg[31:8]};
        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;
}

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

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

将串口连接PC,使用Python串口发送程序烧录编译生成的.bin文件:

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

既然可以执行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;
}

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


三、总结与思考

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