Verilog 语法 (一)

发布于:2025-05-01 ⋅ 阅读:(33) ⋅ 点赞:(0)
Verilog 是硬件描述语言,在编译下载到 FPGA 之后, FPGA 会生成电路,所以 Verilog 全部是并行处理与运行的;C 语言是软件语言,编译下载到单片机 /CPU 之后,还是软件指令,而不会根据你的代码生成相应的硬件电路,而单片机/CPU 处理软件指令需要取址、译码、执行,是串行执行的。 Verilog 和 C 的区别也是 FPGA 和单片机 /CPU 的区别,由于 FPGA 全部并行处理,所以处理速度非常快,这个是 FPGA 的最大优势,这一点是单片机 /CPU 替代不了的。
逻辑 0 :表示低电平,也就是对应我们电路的 GND
逻辑 1 :表示高电平,也就是对应我们电路的 VCC
逻辑 X :表示未知,有可能是高电平,也有可能是低电平;
逻辑 Z :表示高阻态,外部没有激励信号是一个悬空状态。

有意义的名称

确保命名能够直观表达信号的用途和功能。例如,sum 应该表示一个和,cpu_addr 应该表示 CPU 的地址线。

下划线风格

使用下划线(_)来分隔不同的词。这样可以提高可读性,并避免单词混淆。例如,data_inaddress_outclk_50m 等。

前缀与后缀

时钟信号:如你所说,使用 clk_ 作为前缀,如 clk_50mclk_cpu

低电平信号:使用 _n 后缀标识低电平有效的信号,如 reset_nenable_n

有效信号:信号名可以使用 _valid 后缀,如 data_valid 表示数据有效信号。

使能信号:使用 _en 后缀,如 write_enread_en

统一缩写

使用标准和一致的缩写。例如,rst 一般表示复位信号,en 表示使能信号。

避免自定义缩写,除非它们能增加可读性且广泛被使用。

信号一致性

在同一个模块或不同模块中保持信号命名的一致性,尤其是在多层次设计中。例如,同一时钟信号可以保持命名为 clk_50m,而在模块中使用相同的名称。

在层次化的设计中,层级之间的信号可以有统一的命名规则,比如 top_clksubmodule_clk

避免保留字冲突

确保自定义的标识符与 Verilog 语言的保留字(如 if, else, module, input, output 等)不冲突。

如果有疑问,可以参考 Verilog 的保留字列表。

参数命名规范

参数使用全大写字母,如 SIZEWIDTHDEPTH,这可以让它们区别于信号名或变量。

如果参数有多个单词,可以使用下划线分隔,如 MAX_DEPTHADDR_WIDTH

模块名称和实例化命名

模块名通常采用首字母大写的驼峰命名法,如 DataProcessorControlUnit

实例化时,可以使用类似 data_processor_0control_unit_top 的命名方式,明确表示该实例的作用和层级。

信号类型和位宽标识

对于带宽的信号,可以在名称中包含位宽信息,如 data_in_8 表示 8 位数据输入。

对于多维信号(如多位总线或数组),可以使用带宽或索引信息,如 addr_bus[7:0]

Verilog 数字进制
格式包括二进制、八进制、十进制和十六进制,一般常用的为二进制、十进制和十六进制。
        二进制表示如下:4’b0101 表示 4 位二进制数字 0101
        十进制表示如下:4’d2 表示 4 位十进制数字 2 (二进制 0010 );
        十六进制表示如下:4’ha 表示 4 位十六进制数字 a (二进制 1010 ),十六进制的计数方式为 0 1 ,2…9, a b c d e f ,最大计数为 f f :十进制表示为 15 )。
        当代码中没有指定数字的位宽与进制时,默认为 32 位的十进制,比如 100 ,实际上表示的值为32’d100。
4'b0101    // 4 位二进制,表示 5 (十进制)
4'd2       // 4 位十进制,表示 2 (二进制 0010)
4'ha       // 4 位十六进制,表示 10 (十进制) -> 二进制 1010
32'd100    // 默认 32 位十进制,表示 100
8'o17      // 8 位八进制,表示 15 (十进制) -> 二进制 00001111
Verilog 的数据类型
        在 Verilog 语法中,主要有三大类数据类型,即寄存器类型、线网类型和参数类型。从名称中,我们可以看出,真正在数字电路中起作用的数据类型应该是寄存器类型和线网类型。寄存器类型表示一个抽象的数据存储单元,它只能在 always 语句和 initial 语句中被赋值,并且它的值从一个赋值到另一个赋值过程中被保存下来。如果该过程语句描述的是时序逻辑,即 always 语句带有时钟信号,则该寄存器变量对应为寄存器;如果该过程语句描述的是组合逻辑,即 always 语句不带有时钟信号,则该寄存器变量对应为硬件连线;寄存器类型的缺省值是 x (未知状态)。 寄存器数据类型有很多种,如 reg integer real 等,其中最常用的就是 reg 类型,它的使用方法如下:
//reg define
reg [31:0] delay_cnt; //延时计数器
reg key_flag ; //按键标志

reg 是最常用的寄存器类型,它用于存储逻辑值(01X 等)。

它只能在 always 语句(带时钟)或 initial 语句中被赋值。

reg 的大小可以由方括号 [] 来指定,如 reg [31:0] 表示一个 32 位的寄存器。

线网表示 Verilog 结构化元件间的物理连线。它的值由驱动元件的值决定,例如连续赋值或门的输出。 如果没有驱动元件连接到线网,线网的缺省值为 z (高阻态)。线网类型同寄存器类型一样也是有很多种, 如 tri wire 等,其中最常用的就是 wire 类型,它的使用方法如下:
//wire define
wire data_en; //数据使能信号 
wire [7:0] data ; //数据

wire 类型用于表示 连接信号传输信号,它是 线网类型 的一部分。你给出的示例是两个 wire 类型的信号:data_endata

wire data_en;

这个 wire 类型的信号 data_en 被定义为一个单比特(1 位)信号,用于表示 数据使能信号。它通常用来控制某个数据传输操作的启用或禁用。例如,data_en 可以用来控制 data 是否有效。

用途示例

数据使能信号:当 data_en 为高电平(1)时,表示数据有效,可以进行传输或操作;当 data_en 为低电平(0)时,数据可能被忽略或不传输。

wire [7:0] data;

这个 wire 类型的信号 data 被定义为 8 位宽的信号([7:0]),用于表示 数据。这个信号通常用来传输实际的数据值,例如一个 8 位的字节数据。

用途示例

数据传输data 可以传递一个 8 位的数据单元,可以是从一个模块传到另一个模块,或者用于在不同寄存器间传输数据。

parameter 的定义和作用

parameter 类型的常量在模块中定义,并且可以在模块实例化时进行修改。这样可以使得模块具有灵活的配置能力,而无需修改模块内部的实现代码。定义方法parameter 可以在模块中定义并赋予默认值。例如:

parameter DATA_WIDTH = 8;  // 数据位宽为 8 位

数据位宽:可以用 parameter 来定义数据总线的宽度,便于模块复用。

状态机状态数量:在设计状态机时,可以使用 parameter 来定义状态的数量或者状态的编码值。

延迟大小:在某些模块中,可以用 parameter 来定义延迟的大小,以便根据实际需求进行调整。

关键词 描述
module 模块开始定义
input 输入端口定义
output 输出端口定义
inout 双向端口定义
parameter 信号的参数定义
wire 信号定义(用于线网类型信号)
reg 信号定义(用于寄存器类型信号)
always 产生 reg 信号语句的关键字
assign 产生 wire 信号语句的关键字
begin 语句的起始标志
end 语句的结束标志
posedge/negedge 时序电路的标志
case Case 语句起始标记
default Case 语句的默认分支标志
endcase Case 语句结束标记
if if 语句标记
else/else if else / else if 语句标记
for for 语句标记
endmodule 模块结束定义

接下来呢,我们就通过实际的一个例子,来将上述的知识点融会贯通。

module led(
	input sys_clk , //系统时钟
	input sys_rst_n, //系统复位,低电平有效
	output reg [3:0] led //4 位 LED 灯
);

//parameter define
parameter WIDTH = 25 ;
parameter COUNT_MAX = 25_000_000; //板载 50M 时钟=20ns,0.5s/20ns=25000000,需要 25bit
//位宽

//reg define
reg [WIDTH-1:0] counter ;
reg [1:0] led_ctrl_cnt;

//wire define
wire counter_en ;

//***********************************************************************************
//** main code
//***********************************************************************************

//计数到最大值时产生高电平使能信号
assign counter_en = (counter == (COUNT_MAX - 1'b1)) ? 1'b1 : 1'b0; 

//用于产生 0.5 秒使能信号的计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0)
	counter <= 1'b0;
else if (counter_en)
	counter <= 1'b0;
else
	counter <= counter + 1'b1;
end

//led 流水控制计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0)
	led_ctrl_cnt <= 2'b0;
else if (counter_en)
	led_ctrl_cnt <= led_ctrl_cnt + 2'b1;
end

//通过控制 IO 口的高低电平实现发光二极管的亮灭
always @(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0)
	led <= 4'b0;
else begin
	case (led_ctrl_cnt) 
		2'd0 : led <= 4'b0001;
		2'd1 : led <= 4'b0010;
		2'd2 : led <= 4'b0100;
		2'd3 : led <= 4'b1000;
		default : ;
	endcase
end
end

endmodule

 因为是第一次笔者讲verilog代码,详细讲一下,我们开始。

第一部分内容就是模块的定义,如下所示:

module led(
    input sys_clk,       // 系统时钟
    input sys_rst_n,     // 系统复位,低电平有效
    output reg [3:0] led // 4 位 LED 灯输出
);

该模块名为 led,包含一个系统时钟 sys_clk、复位信号 sys_rst_n,和一个 4 位的 LED 输出端口 led

参数定义

parameter WIDTH = 25 ;
parameter COUNT_MAX = 25_000_000; // 计数最大值

WIDTH 定义了计数器的位宽,这里设置为 25 位。

COUNT_MAX 设定了计数器的最大值,使用板载 50 MHz 时钟,经过 25 位计数器和 0.5 秒定时的计算结果为 25_000_000。

寄存器定义

reg [WIDTH-1:0] counter ;      // 用于计数的寄存器
reg [1:0] led_ctrl_cnt;       // 用于控制 LED 流水灯状态的计数器

counter 用于记录时钟周期计数,并达到 COUNT_MAX 后触发 LED 切换。

led_ctrl_cnt 控制流水灯的状态,在 counter_en 使能信号触发时改变。

信号定义

wire counter_en ; // 使能信号

counter_en 为计数器使能信号,当 counter 达到 COUNT_MAX 时,该信号为高电平,表示计数周期已完成。

计数器使能信号的生成

assign counter_en = (counter == (COUNT_MAX - 1'b1)) ? 1'b1 : 1'b0; 

counter_encounter 达到最大值时,产生高电平信号,表示计数周期已完成。

计数器模块

always @(posedge sys_clk or negedge sys_rst_n) begin
    if (sys_rst_n == 1'b0)
        counter <= 1'b0;       // 复位时,计数器清零
    else if (counter_en)
        counter <= 1'b0;       // 计数完成时,计数器清零
    else
        counter <= counter + 1'b1; // 每个时钟周期加 1
end

counter 计数器每次在时钟上升沿累加,计数到 COUNT_MAX 后复位。

LED 控制计数器

always @(posedge sys_clk or negedge sys_rst_n) begin
    if (sys_rst_n == 1'b0)
        led_ctrl_cnt <= 2'b0;  // 复位时,LED 控制计数器清零
    else if (counter_en)
        led_ctrl_cnt <= led_ctrl_cnt + 2'b1;  // 每次计数完成后加 1
end

led_ctrl_cnt 用于控制 LED 灯的流水显示,每次计数周期完成时自增,改变流水灯显示的顺序。

LED 灯控制

always @(posedge sys_clk or negedge sys_rst_n) begin
    if (sys_rst_n == 1'b0)
        led <= 4'b0;  // 复位时,LED 灯关闭
    else begin
        case (led_ctrl_cnt) 
            2'd0 : led <= 4'b0001; // LED 灯显示第一个
            2'd1 : led <= 4'b0010; // LED 灯显示第二个
            2'd2 : led <= 4'b0100; // LED 灯显示第三个
            2'd3 : led <= 4'b1000; // LED 灯显示第四个
            default : ;
        endcase
    end
end

根据 led_ctrl_cnt 控制 LED 灯的显示状态,形成一个 4 位 LED 的流水效果。每次计数周期完成后,led 的值依次切换。


网站公告

今日签到

点亮在社区的每一天
去签到