八、RISC-V SoC外设——GPIO接口 代码讲解

前几篇博文中注释了RISC-V的内核CPU部分,从这篇开始来介绍RISC-V SoC的外设部分。

另外,在最后一个章节中会上传额外添加详细注释的工程代码,完全开源,如有需要可自行下载。

目录

0 RISC-V SoC注解系列文章目录

1. 结构

2. GPIO模块

2.1 输入和输出端口

2.2 代码注解

2.3 GPIO功能实现


 

0 RISC-V SoC注解系列文章目录

零、RISC-V SoC软核笔记详解——前言

 一、RISC-V SoC内核注解——取指

 二、RISC-V SoC内核注解——译码

三、RISC-V SoC内核注解——执行

四、RISC-V SoC内核注解——除法(试商法)

五、RISC-V SoC内核注解——中断

六、RISC-V SoC内核注解——通用寄存器

七、RISC-V SoC内核注解——总线

八、RISC-V SoC外设注解——GPIO

九、RISC-V SoC外设注解——SPI接口

十、RISC-V SoC外设注解——timer定时器

十一、RISC-V SoC外设注解——UART模块(终篇)

1. 结构

如下图,我们之前介绍的RISC-V内核部分,是图中左上角的RISC-V处理器核。而内核和所有的外设都挂载在总线上,内核通过总线和外设进行数据交互。这六个外设中,RAM和ROM外设已经在之前的博文中进行了解析,因此不再赘述。现在我们来介绍外设中的GPIO外设。

八、RISC-V SoC外设——GPIO接口 代码讲解_第1张图片

2. GPIO模块

2.1 输入和输出端口

input wire clk,  
input wire rst,  
  
input wire we_i,  //总线写使能
input wire[31:0] addr_i,   //总线 配置IO口寄存器地址
input wire[31:0] data_i,   //总线 写数据(用来配置IO口相关寄存器)
input wire[1:0] io_pin_i,  //输入模式下,IO口的输入逻辑电平
  
output wire[31:0] reg_ctrl,  //IO口控制寄存器数据 0: 高阻,1:输出,2:输入
output wire[31:0] reg_data,  //IO口数据寄存器数据
output reg[31:0] data_o       // 总线读IO口寄存器数据

2.2 代码注解

Step1:先设计两个寄存器:gpio_ctrl(控制GPIO的输入和输出模式); gpio_data(存放GPIO的输入或输出数据)。

// 每2位控制1个IO的模式,最多支持16个IO  
// 0: 高阻,1:输出,2:输入  
reg[31:0] gpio_ctrl;  
// 输入输出数据  
reg[31:0] gpio_data;  
  
assign reg_ctrl = gpio_ctrl;  
assign reg_data = gpio_data; 

Step2:给这两个寄存器规划地址。

// GPIO控制寄存器的地址  
localparam GPIO_CTRL = 4'h0;  
// GPIO数据寄存器的地址  
localparam GPIO_DATA = 4'h4;  

Step3:通过寄存器寻址来,对上述定义的两个寄存器进行写操作,通过配置gpio_ctrl寄存器来实现GPIO的输入输出。

// 写寄存器  
always @ (posedge clk) begin  
    if (rst == 1'b0) begin  
        gpio_data <= 32'h0;  
        gpio_ctrl <= 32'h0;  
    end else begin  
        if (we_i == 1'b1) begin  
            case (addr_i[3:0])  //寄存器寻址
                GPIO_CTRL: begin  
                    gpio_ctrl <= data_i;  //通过配置寄存器gpio_ctrl来实现GPIO的输入输出
                end  
                GPIO_DATA: begin  
                    gpio_data <= data_i;  
                end  
            endcase  
        end else begin  //如果IO口是输入模式,则将输入的逻辑电平存放到gpio_data寄存器中
            if (gpio_ctrl[1:0] == 2'b10) begin  
                gpio_data[0] <= io_pin_i[0];  
            end  
            if (gpio_ctrl[3:2] == 2'b10) begin  
                gpio_data[1] <= io_pin_i[1];  
            end  
        end  
    end  
end

以下代码是通过总线来读IO口相关寄存器数据。

// 读寄存器  
always @ (*) begin  
    if (rst == 1'b0) begin  
        data_o = 32'h0;  
    end else begin  
        case (addr_i[3:0])  
            GPIO_CTRL: begin  
                data_o = gpio_ctrl;  
            end  
            GPIO_DATA: begin  
                data_o = gpio_data;  
            end  
            default: begin  
                data_o = 32'h0;  
            end  
        endcase  
    end  
end  

2.3 GPIO功能实现

在顶层tinyriscv_soc_top.v模块中,

// io0  
assign gpio[0] = (gpio_ctrl[1:0] == 2'b01)? gpio_data[0]: 1'bz;  
assign io_in[0] = gpio[0];  
// io1  
assign gpio[1] = (gpio_ctrl[3:2] == 2'b01)? gpio_data[1]: 1'bz;  
assign io_in[1] = gpio[1];  
// gpio模块例化  
gpio gpio_0(  
    .clk(clk),  
    .rst(rst),  
    .we_i(s4_we_o),  
    .addr_i(s4_addr_o),  
    .data_i(s4_data_o),  
    .data_o(s4_data_i),  
    .io_pin_i(io_in),  
    .reg_ctrl(gpio_ctrl),  
    .reg_data(gpio_data)  
);  

上述代码中:

gpio[0] = (gpio_ctrl[1:0] == 2'b01)? gpio_data[0]: 1'bz; 

是说当配置寄存器gpio_ctrl[1:0]为1时,说明GPIO为输出模式,将gpio_data[0]输出至对应的IO口,如果gpio_ctrl[1:0]不为1,即为0或2,对应高阻态和输入模式,都将GPIO设置为高阻态,原因如下:

高阻态是一个数字电路里常见的术语,指的是电路的一种输出状态,既不是高电平也不是低电平,如果高阻态再输入下一级电路的话,对下级电路无任何影响,和没接一样,如果用万用表测的话有可能是高电平也有可能是低电平,随它后面接的东西而定。

 

参考:

从零开始写RISC-V处理器 | liangkangnan的博客 (gitee.io)

tinyriscv: 一个从零开始写的极简、非常易懂的RISC-V处理器核。 (gitee.com)

 

 

你可能感兴趣的:(数字IC设计,RISC-V,risc-v,soc,fpga开发,verilog)