Verilog GPIO 模块设计

    科技2024-11-04  12

    阅读须知

    本文章可适用于研究生找工作,此项目学习,意义在于标准化设计学习和验证,模块设计过程中的思考等都符合公司招生考察要素,虽然简单,但是值得认真思考。有想要详细了解的可以加微信w154785315,大家一起学习共同进步。

    GPIO 端口介绍

    GPIO 端口,即General purpose Input /Output port,是一种位于芯片数字部分和外面模拟PAD之间的模块,主要功能就是负责输入、输出数据,此外可以附加很多其他功能(Alternate Function),比如在模拟部分加上下拉电阻(pull-up/down),用三极管实现开/漏输出设计等。大家在网上可以看到很多基于现有GPIO进行其他应用设计的案例,作为数字IC设计工程师,我们当然要做一些自己行业相关的事情,那就是如何设计一个数字的GPIO模块,从而实现基本的逻辑功能。

    GPIO功能分析

    成熟的设计开始于完备的功能分析及模块划分,GPIO模块的设计同样如此。啊,我是大佬没错了 ~ 。~ 一般来说GPIO端口的功能有哪些呢?业内大佬ARM必须拥有姓名,现在来看一段STM32的GPIO模块功能介绍: 我们只关注数字部分,模拟去掉。首先,GPIO端口应可以软件配置,其次,GPIO端口应具有基本的输入、输出功能,最后,GPIO端口还应该有一些附加功能。 将分析得到的GPIO数字模块功能进一步分析,我们得到以下设计需求:

    GPIO模块应可以由软件进行模式配置。GPIO模块应可以接收外来信号,并将这些信号存进寄存器中供软件读取。GPIO模块应可以发送软件存放在寄存器中的值。GPIO模块还可以添加一些额外的功能,比如固定占空比、不同频率的方波输出,中断检测,电流监控等。(这些功能我拍脑袋随便加的)

    有了设计需求,我们就可以根据需求进行下一步工作:GPIO的模块划分。

    GPIO模块划分

    根据上一篇文章的内容,我们已经对GPIO模块所应该具有的功能进行了分析,下面我们根据功能分析结果进行模块划分。

    GPIO_top GPIO的顶层模块,在这个模块中设计模块与外部模块的接口并将其他模块例化在这里。为方便阅读及优化 ,顶层模块中并不进行复杂的逻辑设计。GPIO_controller GPIO的控制模块,此模块阅读芯片控制器对GPIO模块的控制配置,并且根据这些配置对其他模块的工作状态进行控制。GPIO的控制逻辑都设计在这个模块中,方便阅读和其他后续的改进。GPIO_reg GPIO的寄存器模块,在公司中这个模块一般都是脚本生成,方便逻辑及格式的统一。寄存器有多种属性:R/W、RO、WO。根据需求可以自己设计。GPIO_alternate GPIO的附加功能设计,在这个模块组中进行我们需要的附加功能设计,比如中断检测、不同占空比及频率的方波输出等。这个模块也可以扩展,比如扩展为一个和GPIO并行的模块,在进行附加功能的时候就关闭GPIO模块的功能,将附加功能模块的输出连到GPIO模块的输出即可。

    GPIO模块设计

    GPIO_top GPIO的顶层模块没有额外复杂逻辑。一般来说GPIO的顶层模块接口(interface)包括总线接口(reg_bus)以及GPIO自己的输入、输出、复位、时钟。(如果将附加功能模块与GPIO模块进行平行设计,那还有附加模块的输入及输出信号。)GPIO_controller GPIO控制模块集成了所有的控制逻辑,需要下一篇仔细讲。GPIO_reg GPIO_reg的输入是总线输入(reg_bus),所有寄存器的读输出内容。输出即为寄存器的现在值。如有需要可以写~~GPIO_alternate GPIO 附加功能模块。

    GPIO之控制器设计

    GPIO的控制器设计需要结合GPIO本身逻辑功能共同设计。 根据常识我们知道,芯片不止有一个GPIO口,所以在设计的时候有两种路线:

    芯片的每一个GPIO模块均为独立模块(一次设计,N次例化)。复用设计,设计一个GPIOs模块,模块内部包括多个GPIO端口。

    一个普通的芯片一般拥有十余个GPIO端口,较大的芯片可以用于二十个乃至更多的GPIO端口。那么如何选择合适的设计呢?

    在这里我的看法是根据模块本身的应用场景进行设计。一般来说,GPIO这种必要但是复杂度并不高的模块在设计中主要注重功能的完整性及代码的质量。设计结构最好是可以复用及移植。所以我认为单个模块只有一个GPIO端口的设计方式更好,但是在有些场景中,如果芯片在设计过程中本身可以使用的资源比较有限,那么在设计GPIO端口模块的时候就需要仔细的斟酌来降低GPIO模块的资源消耗。

    设计一个单GPIO端口的模块需要一个寄存器作为控制寄存器,设计NGPIO端口的模块就需要对控制寄存器的数量及种类进行划分,从而用较少的寄存器控制更多的GPIO端口。比如一般来说应用中一个寄存器有32bit,若用寄存器组进行控制,那么一组寄存器就可以控制最多32个端口。而单端口GPIO模块需要一个寄存器,若寄存器组的数量<GPIO端口数,则用NGPIO端口设计更加节省资源。

    (某种程度上来说,这种针对场景进行设计优化正是我们作为基层芯片设计工程师存在的意义,如果不是这种需求,我想那些EDA大厂早已写出一大堆脚本取代我们这些基层员工了吧,哈哈。

    确定了控制模式之后(这里我们使用单GPIO模块设计),我们将寄存器命名为gpio_ctrl,寄存器为32-bits W/R。 根据GPIO端口设计 1我们可以发现,在实现功能过程中需要加锁,锁的存在是有意义的,因为主机有可能无意间因为误发改变了我们配置寄存器的值,如果我们的端口配置立马就针对这些改变进行了变化,那么这种灵敏性会极大的降低我们模块的稳定性。锁的设计我们使用if-else结构实现。 下面将整个GPIO_CTRL模块代码放在这里:

    module gpio_ctrl ( interface ****** ); reg lock, reg,we; //组合逻辑得到 lock flag always @ (*) begin if(rst) lock = 1'b0; else if (ok == LOCKKEY) lock = 1'b1; else lock = 1'b0; end //组合逻辑用锁来完成对指定地址的寄存器的锁 always@(*) begin if (lock) begin if(reg_ad_i == 7'h0 && reg_we_i == 1'b1 || reg_ad_i == 7'h4 && reg_we_i == 1'b1) we = 1'b0; else if (reg_ad_i != 7'h0 && reg_we_i == 1'b1 || reg_ad_i != 7'h4 && reg_we_i == 1'b1) we = 1'b1; else we = reg_we_i; end else we = reg_we_i; end assign dm = lock ? id : 1'b0; asign od = cfg ? od_i : 1'b0; endmodule

    接下来就用剩下的位来实现其他功能的配置,利用一对一的解码原理完成对GPIO端口模式的配置:

    GPIO之附加功能设计

    在上面的设计中我们完成了GPIO模块的控制逻辑设计,接下来我们可以将一些常用的小模块作为附加功能添加在模块中丰富我们的功能选择。 在GPIO数字部分设计中我们设计的电路是纯数字功能,这样的模块功能比较单一,但是本身GPIO就是这样单一的模块,我们可以通过添加一些附加功能项来让整个模块更加具有实用价值,比如我们可以添加滤波功能。

    滤波模块放在GPIO输入接口之后,数据从GPIO input PAD输入之后,经过数字滤波模块滤波后再作为输入存入寄存器。这个滤波是有使用价值的,比如当你作为一个简单的数字模块外接一些按键啊,一些灵敏触发的开关等,物理的信号以模拟形式输入,因为信号本身从不稳定到稳定的过程,就会产生几个微秒的亚稳态,毛刺等,因为芯片本身的时钟频率较高,所以芯片具有对这些毛刺亚稳态产生反应的能力,在实际使用中,这就会对芯片的信号收集造成不必要的影响,因此我们可以添加一个简单的滤波模块。

    `define VALUE = 50 localparam threshold = VALUE; always @ (posedge clk or posedge reset) begin if (reset) begin threshold_1 <= 32'h0; flag <= 1'b0; end else if (threshold_1 <= threshold ) begin threshold_1 <= threshold_1 + 1'b1; flag <= 1'b0 end else begin threshold_1 <= 32'h0; flag <= 1'b1; end end always @ (posedge clk or posedge reset) begin if (reset) begin filter <= 1'b0; end else if ( flag) begin filter <= inputdata; end else begin filter <= filter; end end

    GPIO之gpio_reg子模块

    对于这种模块一般都是脚本生成,这类型模块主要是方便修改和复用,下面我写一段比较标准的gpio_reg代码供大家学习参考。(顺手求赞~~

    module gpio_reg{ clk_i, rst_i, cs_i, ad_i, we_i, re_i, wd_i, be_i, do_o, vd_o, dm_i, sm_i, id_i, pt_o }; input clk_i; input rst_i; input cs_i; input [6:0] ad_i; input we_i; input re_i; input [31:0] wd_i; input [3:0] be_i; output [31:0] do_o; output vd_o; input [10:0] dm_i; input [20:0] sm_i; output [31:0] pt_o; reg [31:0] gpio_pt; reg [31:0] reg_data; reg [31:0] latch_data; reg do_vld; wire sel_gpio_pt = cs_i &(ad_i[6:2] == 5'h0 ) ; wire wr_gpio_pt = sel_gpio_pt & we_i; wire rd_gpio_pt = sel_gpio_pt & re_i; assign gpio_pt_o [31:0] = gpio_pt[31:0] ; always @ (posedge clk_i or posedge rst_i) begin if (rst_i) gpio_pt <= 32'h0; else if (wr_gpio_pt) begin if(be_i[0]) begin gpio_pt[7:0] <= wd_i[7:0]; end if(be_i[1]) begin gpio_pt[15:8] <= wd_i[15:8]; end if(be_i[2]) begin gpio_pt[23:16] <= wd_i[23:16]; end if(be_i[3]) begin gpio_pt[31:24] <= wd_i[31:24]; end end end always @ (*) begin case (1'b1) gpio_pt : reg_data = {sm_i[20:0], dm_i[10:0]} ; default : reg_data = {32{1'b0}} ; endcase end always @ (posedge clk_i or posedge rst_i) begin if (rst_i) latch_data <= {32{1'b0}} ; else if (cs_i && re_i) begin latch_data <= reg_data ; end end always @ (posedge clk_i or posedge rst_i) begin if (rst_i) do_vld <= 1'b0 ; else if (cs_i && re_i) begin do_vld <= 1'b1 ; end else begin do_vld <= 1'b0 ; end end assign do_o = latch_data ; assign vd_o = do_vld ; endmodule

    完结撒花

    Processed: 0.017, SQL: 8