描述方式
之前说过,verilog属于高级语言,需要软件设计硬件结构来实现,也说过描述方式是有很多种的,下面介绍一下。
结构描述十分底层,相当于自己搭电路;
行为描述最简单,只需要写出输入对应的输出就行,例子中需要case语句。
数据流描述比较玄学,还需要找关系,感觉也不太好。
对于题目38译码器这样一个简单的器件,我们选择行为描述的方式最简单(要是有个十几个输入,case语句直接爆炸,所以这种方式也是有限制的)
代码:
`timescale 1ns / 1ps //时钟信号的精度和周期
module decoder_38(
input [2:0] data_i, //输入数据
input [2:0] en_i, //使能端,高低有效看要求,这里是0、1位低有效,2位高有效
output reg [7:0] data_o //输出的八位数据
);
always @(*) begin
if(en_i[0] || en_i[1] || !en_i[2]) //注意一下,译码器一般默认是低有效,所以全1相当于使能端无效
data_o = 8'b1111_1111;
else
case (data_i)
3'b000: data_o = 8'b1111_1110;
3'b001: data_o = 8'b1111_1101;
3'b010: data_o = 8'b1111_1011;
3'b011: data_o = 8'b1111_0111;
3'b100: data_o = 8'b1110_1111;
3'b101: data_o = 8'b1101_1111;
3'b110: data_o = 8'b1011_1111;
3'b111: data_o = 8'b0111_1111;
endcase
end
endmodule
(虽然说是低有效,但是最后连接板子上的led还是高有效的,然后就会全反了……实际需要还是按需求改一下吧)
仿真文件
先贴代码(因为verilog就是仿C的,这里直接使用了C的代码块)
`timescale 1ns / 1ps
module decoder_38_sim ();
// 输入端口
reg [2:0] data_in;
reg [2:0] en;
// 输出端口
wire [7:0] data_out;
// 结合自己的实现完成实例化
decoder_38 U_dec38_0(
.data_i (data_in),
.en_i (en),
.data_o (data_out)
);
initial begin
// 构造输入激励信号
#5 begin en = 3'b100; data_in = 3'b000; end
#5 begin en = 3'b100; data_in = 3'b001; end
#5 begin en = 3'b100; data_in = 3'b010; end
#5 begin en = 3'b100; data_in = 3'b011; end
#5 begin en = 3'b100; data_in = 3'b100; end
#5 begin en = 3'b100; data_in = 3'b101; end
#5 begin en = 3'b100; data_in = 3'b110; end
#5 begin en = 3'b100; data_in = 3'b111; end
// 使能端无效
#5 begin en = 3'b101; data_in = 3'b000; end
// 结束仿真
#5 $stop;
end
endmodule
- 第一行,1ns是时间间隔,也就是仿真上的一个单位长度,但是其精度达到了1np,当然这些都是套话,不用管就行。
- module decoder_38_sim ()是文件创建自带的,我们不需要这个模块,不用管他
- 创建reg和wire型变量,眼尖的人就会看出这些变量刚好就是decoder_38的输入输出(reg输入wire输出),如果分不清reg和wire区别这样记也还好。
- reg需要赋初值,不然仿真会有不定态;wire千万别赋值,赋值biss
- 调用之前写好的译码器模块,调用注意事项:
- 首先要知道,sim和代码文件都是.v文件,也就意味着两者书写规则基本上差不多,只不过一个因为有变量赋值可以跑出来波形而已。
- 模块调用不但是这里,包括在源文件如果有多个模块,也需要这样调用,最后只能有一个顶层模块被使用。
- 模块调用的基本格式:被调模块+被调模块在该部分的小名+变量列表(小名的意义在于一个模块调用相同模块多次)
- 模块调用和高级语言的函数差不多,被调模块的输入输出都要给出。
- 模块调用的方式有两种,上面的形式比较标准,.被调模块变量名(调用模块变量名)不要搞反了,这种情况下变量顺序不做要求;
- 另外一种比较简短的形式就是直接调用,直接(data_in,en,data_out),这时候就不需要被调模块的变量名了,但是一定要对齐
仿真文件本质上就是调用模块并按照一定的时间给输入变量赋值,然后观看调用模块后的输出变量是怎样的,所以需要我们决定怎么赋值和赋值的时间问题。
赋值原则:要全面、要有边界条件(反正就是看着舒服,仿真的目的不就是让人放心吗)
#+数字:相隔时间,在initial语句中时间是累加的,
比如我先#5,那么这个语句就是在#5时有效;下一句#5则是在第十个时间单位有效。
always语句块略有不同,不过一般是在时序电路给时钟信号赋值才用得上,先不急。
仿真截图:(因为data_in没有初值,出现不定态,也就是红色部分)
约束文件
记一下模板就好
set_property -dict { IOSTANDARD LVCMOS33 PACKAGE_PIN 管脚名 } [get_ports 变量名]
不论是数组还是单个变量,我们都可以在板子的手册上找到对应的一个管脚,将其写上就行。
(这里的LVCMOS33表示高有效,一般用不上改)
另一种形式:
set_property PACKAGE_PIN 管脚名 [get_ports ]
set_property IOSTANDARD LVCMOS33 [get_ports 变量名]
(不就是拆开了吗,这种一般不用)
下板的后序操作在之前的博客有提到,这里不做赘述。