author: hjjdebug
date: 2024年 10月 12日 星期六 15:02:56 CST
description: verilog 介绍(附状态机实例)
初学者可以把菜鸟教程中的verilog 当参考手册.
但那里介绍的太多了,精简入门(或者入门后的概括)看看本博就够了.
1. 什么是HDL ?
HDL, hardware description language, 硬件描述语言
其分为两部分:
a. 逻辑功能设计.
用HDL 语言在不同层次描述对数字电路的功能、结构和行为进行描述.
b. 电路实现
通过综合工具将逻辑设计转化为门级电路网表,再将其与某种工艺的
基本元件逐一对应起来,再通过布局,布线工艺转换为电路的布局,布线
美国,中国,日本多使用verilog, 欧洲多使用vhdl
2. verilog 是什么?
verilog 是一种类C 语言, 它的词法和语法借鉴了C, 减小了它的入门门槛.
但verilog 是硬件描述语言,数字电路是它的基础,运算是并行的,这是与C不同之处.
1. 基础知识:(词法)
1.1: 逻辑值, 0,1,X,Z(低,高,不定,高阻)
1.2: 数据的表示方式:
表示方式是<位宽'类型数值>
常见类型: b,d,h (二进制,十进制,16进制)
例如:
4'b0101
4'd9
4'hb
16'b0101_1010_0101_1010 = 16'h5a5a
1.3: 标识符
标识符用来表示模块名,端口名,信号名
1.4: 数据类型(3种), 寄存器类型,线网类型,参数类型(非线路类型)
1.4.1: reg 关键字说明寄存器类型.
该类型代表一个抽象的数据存储单元.
通过赋值语句可以改变寄存器中储存的值.
它可以在 always 语句和 initial 语句中被赋值.
如果该过程语句描述的是时序逻辑,即 always 语句带有时钟信号,则该寄存器变量对应为寄存器;
如果该过程语句描述的是组合逻辑,即 always 语句不带有时钟信号,则该寄存器变量对应为硬件连线;
寄存器类型的缺省值是 x(未知状态)
reg [31:0] delay_cnt; //位宽32位的寄存器
1.4.2: wire 关键字说明线网类型
它代表元件间的物理连线。它的值由驱动元件的值决定
如果没有驱动元件连接到线网,线网的缺省值为 z(高阻态)
wire[7:0] data; // 8bits 数据
1.4.3: 由parameter 关键字说明的变量是参数类型
参数类型实际上是定义一个常数.
例:
parameter DATA_WITH = 8; //跟c语言中的宏类似
外部调用模块时,还可以传递参数.
类型后边跟一个标识符,说明这个变量时什么类型.
1.5: 注释
与c 一样, //注释单行, /* */注释多行
1.6: 运算符
与c 一致.
算术运算符: ±*/%
关系运算符: >,=,<,>=,<=,==,!=
逻辑运算符: &&,||,!
条件运算符: ?:
位运算符: ~,^,&,|
移位运算符: <<,>>
新增一条强势组合操作符,拼接运算符
例如: c={a,b} 把a,b 拼成c信号
1.7: 关键字.
关键字用来表示特定的意义,指导编译程序完成特定功能. 掌握常用关键字即可.
2. 常用关键字介绍
if - else 语句
if - else if -else 语句
for 语句
while 语句
以上与c 一致
case 语句
c 中是switch ... case 形如: switch(var) case val1: do1;break...;default: do_defaut;
v 中是case ... endcase 形如: case(var) val1: do1; val2:do2; default: do_default; endcase;
与c不同的地方
c 用{} 表示语句块, v用begin, end 表示语句块
c 用 = 直接给变量赋值,v 叠加了assign 或者 always 关键字给变量赋值
assign 用于简单的组合逻辑赋值语句
always 即可用于组合逻辑,此时功能与assign 一样, 也可用于时序逻辑.
always 语句常常带有敏感信号列表,带begin,end 语句块,看起来像一个c中的无名函数
= 是阻塞赋值, 用在组合电路中
<=是非阻塞赋值, 用在时序带路中
always 语句块, 其内部可以认为是顺序执行的,而always 块与块之间是并行执行的.
注: 硬件语句一定要注意语法的完整性,有if一定要跟else, 有case 一定要有default,不能有不周全的地方.
否则可能会有glitch 毛刺的产生.
3. 模块化设计
一个模块对应一种功能,上层模块调用下层模块.
每调用一次模块,需要先对模块实例化.
模块相当于c++中的类, 模块的参数相当于带参对象的构造函数
4.实战: 4位密码锁设计
当然,改成8位也是很简单的.
状态机的编写
典型的状态机例子就是一个密码锁. 它有10个按键输入
假如实现4位密码锁,其密码是1217,输入正确开锁,不正确不开锁.
c语言的状态机只需要一个switch-case 块.
v语言常用三段式状态机. 因为它有组合逻辑部分和时序逻辑部分,需要区别对待.
基本格式是:
第一个 always 语句实现同步状态跳转;(时序电路), 在时钟上升沿把下一状态付给当前状态
第二个 always 语句采用组合逻辑判断状态转移条件;//类似于c语言的switch-case块, 找到下一个状态
第三个 always 语句描述状态输出(可以用组合电路输出,也可以时序电路输出)。对应moore 和 mealy状态机
代码:
c
module Cipher(
input sys_clk,
input reset,
input key0,
input key1,
input key2,
input key3,
input key4,
input key5,
input key6,
input key7,
input key8,
input key9,
output reg result
);
//parameter define
parameter S0 = 3'b000 ;
parameter S1 = 3'b001 ;
parameter S2 = 3'b010 ;
parameter S3 = 3'b011 ;
parameter S4 = 3'b100 ;
//reg define, 当前状态和下一状态,3bits
reg [2:0] curr_st;
reg [2:0] next_st;
//main code
//状态机的第一段采用同步时序描述状态转移
always @(posedge sys_clk or negedge reset)
begin
if (!reset)
curr_st <= S0;
else
curr_st <= next_st;
end
//状态机的第二段根据当前状态及输入采用组合逻辑判断状态转移条件
//按键低有效
always @(
negedge key1,
negedge key2,
negedge key3,
negedge key4,
negedge key5,
negedge key6,
negedge key7,
negedge key8,
negedge key9
)
begin
case (curr_st)
S0:
begin
if(key1 == 1'b0) next_st = S1;
else next_st = S0;
end
S1:
begin
if(key2 == 1'b0) next_st = S2;
else next_st = S0;
end
S2:
begin
if(key1 == 1'b0) next_st = S3;
else next_st = S0;
end
S3:
begin
if(key7 == 1'b0) next_st = S4;
else next_st = S0;
end
default:
next_st = S0;
endcase
end
//状态机的第三段描述状态输出(这里采用时序电路输出)
always @(posedge sys_clk or negedge reset)
begin
if (!reset)
result <= 1'b0;
else if (curr_st == S4)
result <= 1'b1;
else
result <= 1'b0;
end
endmodule
测试代码:(test_bench)
c
//时间精度/显示精度
`timescale 1ns/1ns
module tb_basic();
//regeister and wire
reg sys_clk;
reg reset;
reg key0;
reg key1;
reg key2;
reg key3;
reg key4;
reg key5;
reg key6;
reg key7;
reg key8;
reg key9;
wire result;
//initial, 输入信号激励及仿真命令
initial
begin
$dumpfile("basic.vcd");
$dumpvars(0,u_Cipher);
sys_clk = 1'b1;
reset = 1'b0;
key0=1'b1;
key1=1'b1;
key2=1'b1;
key3=1'b1;
key4=1'b1;
key5=1'b1;
key6=1'b1;
key7=1'b1;
key8=1'b1;
key9=1'b1;
#100;
reset = 1'b1;
key1=1'b0; // 1 press
#100;
key1=1'b1;
key2=1'b0; // 2 press
#100;
key2=1'b1;
key1=1'b0; // 1 press
#100;
key1=1'b1;
key7=1'b0; // 7 press
#100;
// $stop;
$finish;
end
//时钟激励, 25M 时钟
always #20 sys_clk=~sys_clk;
Cipher u_Cipher(
.sys_clk(sys_clk),
.reset(reset),
.key0(key0),
.key1(key1),
.key2(key2),
.key3(key3),
.key4(key4),
.key5(key5),
.key6(key6),
.key7(key7),
.key8(key8),
.key9(key9),
.result(result)
);
endmodule
仿真结果:
测试输入密码1217, 给出了结果result=1. 测试通过.