当前位置: 首页 > news >正文

Verilog 语法 (一)

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时序电路的标志
caseCase 语句起始标记
defaultCase 语句的默认分支标志
endcaseCase 语句结束标记
ifif 语句标记
else/else ifelse / else if 语句标记
forfor 语句标记
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;
elsecounter <= 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 begincase (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
endendmodule

 因为是第一次笔者讲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) beginif (sys_rst_n == 1'b0)counter <= 1'b0;       // 复位时,计数器清零else if (counter_en)counter <= 1'b0;       // 计数完成时,计数器清零elsecounter <= counter + 1'b1; // 每个时钟周期加 1
end

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

LED 控制计数器

always @(posedge sys_clk or negedge sys_rst_n) beginif (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) beginif (sys_rst_n == 1'b0)led <= 4'b0;  // 复位时,LED 灯关闭else begincase (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 : ;endcaseend
end

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

相关文章:

  • 第七章:Contribution Governance
  • 【Pandas】pandas DataFrame dot
  • 【C++QT】Buttons 按钮控件详解
  • 乐聚机器人与地瓜机器人达成战略合作,联合发布Aelos Embodied具身智能
  • flask学习(1)
  • MongoDB Compass可视化工具
  • 常见接口测试常见面试题(JMeter)
  • 在 Ubuntu 环境为 Elasticsearch 引入 `icu_tokenizer
  • 深入理解表单---提交用户与网页交互的重要方式:GET 与 POST 的本质区别与应用实践
  • vue3:十一、主页面布局(修改顶部导航栏样式-右侧:用户信息+退出登录+全屏显示)
  • 突破厚铜PCB阻抗控制难题:多级阻抗实现方法
  • 工厂模式:解耦对象创建与使用的设计模式
  • vue项目,基于echarts的各省份地图展示
  • 解决:springmvc工程 响应时,将实体类对象 转换成json格式数据
  • Windows申请苹果开发者测试证书Uniapp使用
  • 二分小专题
  • [特殊字符] 分布式定时任务调度实战:XXL-JOB工作原理与路由策略详解
  • WGAN+U-Net架构实现图像修复
  • U盘能识别但无法写入数据的原因
  • 数据结构-图
  • 下周起上海浦东将投放5000万元消费券,预计分五周发放
  • 联手华为猛攻主流市场,上汽集团总裁:上汽不做生态孤岛
  • 正荣地产旗下“H20正荣2”债未能于宽限期内支付分期偿付款,尚未就新兑付方案达成一致
  • 吉林省委原书记、吉林省人大常委会原主任何竹康逝世
  • 世界读书日丨人均一年超10本!你达到上海平均阅读水平了吗
  • 龚正会见巴西里约热内卢州州长克劳迪奥·卡斯特罗