通过前面的博文我们已经学会如何将一个简单的计算多项式的值C语言函数转换为具有相同功能的Verilog模块,并为其编写相应的测试模块,即所谓testbench
对其进行仿真,通过对仿真波形的检查可以验证所设计模块的功能是否与C语言函数的功能相同,也就是确保模块功能的正确性。
本篇博文介绍如何将C语言
的选择结构转换为Verilog
硬件模块。我们知道,C语言
的选择结构有两种形式,if-else
结构和switch-case
结构。一般if-else
结构实现双分支,而switch-case
结构可以简洁地表达多分支结构。本博文先讨论双分支结构的实现方法,下一篇讨论多分支的情况。通过前面的学习,大家已经了解到同样的功能,Verilog既可以用组合逻辑实现,也可以用时序逻辑实现。本文分别用两种方法实现,方便大家进一步理解组合逻辑和时序逻辑的区别与联系。
//C语言函数实现双分支选择功能
void selection(
char a,
char b,
bool sel,
char* out
)
{
if(sel == true)
*out = a;
else
*out = b;
return;
}
我们以一个很简单的函数来讲解Verilog实现选择结构的方法。函数selection
有三个输入参数char a,b
和bool sel
, 依据sel的不同值选择将a或者b值赋给out。当sel为真时out的值为a,否则out的值为b。
组合逻辑的表达形式有两种,一是用assign
对wire
型变量(或信号)赋值,二是采用alway @(*)
块结构对reg
型变量赋值。
为了进一步理解组合逻辑的两种不同表达形式,我们稍微修改了一下上面C语言函数的功能,在一个Verilog模块内部用两种形式实现双分支结构,然后在仿真波形中观察两种形式实现的结果是否一致。
//selection_wire.v
//用组合逻辑实现双分支选择结构
module selection_wire(
input [7:0] a,
input [7:0] b,
input sel,
output wire [7:0] out0
output wire [7:0] out1
);
//1. 用assign赋值形式实现双分支选择
assign out0 = (sel == 1'b1) ? a : b;
//2. 组合逻辑的另一种写法always @(*)
// 注意:always块只能对reg型变量赋值
reg [7:0] tmp;
always @(*) begin
if(sel == 1) tmp = a;
else tmp = b;
//这种形式也可以实现同样功能
//tmp = (sel == 1'b1) ? a : b;
end
assign out1 = tmp;
endmodule
时序逻辑的同样可以实现上面C语言函数的功能,大多时候时序逻辑相对组在实现复杂功能的时候会更稳定,更有优势。
//selection_reg.v
//用时序逻辑实现双分支选择结构
module selection_reg(
input clk,
input rst, //复位信号
input [7:0] a,
input [7:0] b,
input sel,
output reg [7:0] out
);
//用if-else语句实现双分支结构
always @(posedge clk) begin
if(rst) out <= 8'd0;
else begin
if(sel == 1'b1) out <= a;
else out <= b;
end
end
endmodule
与组合逻辑相比,时序逻辑多了一个clk
信号和一个rst
信号。rst
是复位信号,用于将模块的各种变量初始化,只在时序逻辑结构中适用。
与第一个计算表达式的例子相比,本例所用变量的bit数只有8位了,因为
C语言
的对应类型为char
类型,取值范围是~127~126
,只需要8个bit位就可以满足要求,因此我们的Veriog
代码用的[7:0]
的形式,也就是只使用了8bit。另外,
C语言
中使用的bool
类型在Verilog
中没有对应类型,因为bool
只有真和假两个值,恰好可以用一个bit就能表达,因此我们使用一个bit表达sel
变量,即input sel
,使用是否等于1'b1
表示是否为true
。
同样的,我们为上面两种不同形式实现的双分支选择功能模块编写一个统一的测试模块进行验证,同时比对组合逻辑和时序逻辑实现结果的异同之处,更深入地理解时序逻辑与组合逻辑的区别和联系。
//selection_testbench.v
`timescale 1ns/1ps
module selection_testbench();
//1. 生成时钟与复位信号
// 时钟周期为50ns*2 = 100ns, 时钟频率= 10MHz
reg clk;
reg rst;
initial begin
clk = 0;
rst = 1;
#120;
rst = 0;
end
always clk = #50 ~clk;
//2. 给出待测模块需要的输入参数
reg [7:0] aa;
reg [7:0] bb;
reg sel;
initial begin
aa = 8'd30;
bb = 8'd40;
sel = 1'b0;
#100;
sel = 1'b1;
#60;
sel = 1'b0;
#80;
bb = 8'd30;
#206;
aa = 8'd100;
#20;
sel = 1'b1;
#200;
$finish;
end
//3. 测试组合逻辑实现的模块
wire [7:0] out_wire0;
wire [7:0] out_wire1;
selectio_wire dut_0(
.a(aa),
.b(bb),
.sel(sel),
.out0(out_wire0),
.out1(out_wire1)
);
//4. 测试时序逻辑实现的模块
wire [31:0] out_reg;
selectio_reg dut_1(
.clk(clk),
.rst(rst),
.a(aa),
.b(bb),
.sel(sel),
.sum(out_reg)
);
endmodule
新建一个名为selection_test
的Vivado工程;
将上面设计模块selectio_wire()和selectio_reg()的源码分别保存为文件selectio_wire.v
和selectio_reg.v
,并添加到Vivado工程, 注意添加文件时选择Add or Create design sources
,因为这两个文件是生成实际硬件模块的可综合设计模块;
将上面selection_testbench()仿真模块源码的内容保存为文件将上面selection_testbench.v
,并添加到Vivado工程, 注意添加文件时注意选择Add or Create simulation sources
,因为这个文件是仿真文件,并不会生成实际的硬件模块;
点击Vivado的【Run Simulation】开始仿真。
仿真波形的查看方法此处不再赘述,请大家自行熟悉。