本篇博文介绍如何将C语言
的多分支选择结构转换为Verilog
硬件模块。我们知道,C语言
的选择结构有两种形式,if-else
结构和switch-case
结构。一般if-else
结构实现双分支,而switch-case
结构可以简洁地表达多分支结构。本博文讨论多分支选择结构的实现方法。本文分别用组合逻辑和时序逻辑两种方法实现,方便大家进一步理解组合逻辑和时序逻辑的区别与联系。
//C语言函数实现多分支选择功能
//依据不同的opcode值将不同的输入通道映射到输出通道
//selection_from_multi.c
void selection_from_multi(
char a,
char b,
char c,
char d,
int opcode,
char* out
)
{
char tmp_out;
switch(opcode){
case 0: tmp_out = a; break;
case 1: tmp_out = b; break;
case 2: tmp_out = c; break;
case 3: tmp_out = d; break;
default: tmp_out = 0;
}
*out = tmp_out;
return;
}
函数selection_from_multi
有5个输入参数char a,b,c,d
和int opcode
, 依据opcode
的不同值选择将a,b,c或d赋给函数的输出out
,也就是实现一个多路输入-单路输出的选择模块,也叫多路复用器。
组合逻辑的表达形式有两种,一是用assign
对wire
型变量(或信号)赋值,二是采用alway @(*)
块结构对reg
型变量赋值。
为了进一步理解组合逻辑的两种不同表达形式,我们稍微修改了一下上面C语言函数的功能,在一个Verilog模块内部用两种形式实现多分支结构,然后在仿真波形中观察两种形式实现的结果是否一致。
//selection_from_multi_wire.v
//用组合逻辑实现多分支选择功能
module selection_from_multi_wire(
input [7:0] a,
input [7:0] b,
input [7:0] c,
input [7:0] d,
input [2:0] opcode,
output wire [7:0] out0
output wire [7:0] out1
);
//1. 用assign赋值形式实现多分支选择
assign out0 = (opcode == 0) ? a :
(opcode == 1) ? b :
(opcode == 2) ? c :
(opcode == 3) ? d : 0;
//2. 组合逻辑的另一种写法always @(*)
// 注意:always块只能对reg型变量赋值
reg [7:0] tmp;
always @(*) begin
case(opcode)
0: tmp = a;
1: tmp = b;
2: tmp = c;
3: tmp = d;
default: tmp = 0;
endcase
end
assign out1 = tmp;
endmodule
时序逻辑同样可以实现上面C语言函数的功能,大多时候时序逻辑相对组合逻辑来说,在实现复杂功能的时候会更稳定,更有优势。所以在实际的开发中尽量用时序逻辑实现相关功能模块。
//selection_from_multi_reg.v
//用时序逻辑实现多分支选择结构
module selection_from_multi_reg(
input clk,
input rst, //复位信号
input [7:0] a,
input [7:0] b,
input [7:0] c,
input [7:0] d,
input [2:0] opcode,
output reg [7:0] out
);
//用case语句实现多分支结构
always @(posedge clk) begin
if(rst) out <= 8'd0;
else begin
case(opcode)
0: out <= a;
1: out <= b;
2: out <= c;
3: out <= d;
default: out <= 0;
endcase
end
end
endmodule
与组合逻辑相比,时序逻辑多了一个clk
信号和一个rst
信号。rst
是复位信号,用于将模块中各种变量初始化,只在时序逻辑结构中适用。
因为
C语言
的对应类型为char
类型,取值范围是~127~126
,只需要8个bit位就可以满足要求,因此我们的Veriog
代码用到的信号量a,b,c,d和out的位宽都是[7:0]
的形式,也就是8bit位宽。
另外,
C语言
中使用的opcode
类型是int
类型,在Verilog
中可以直接使用对应的变量类型integer
(默认32bit)。不过,因为opcode
只有5种选择,因此opcode
表示的值只要大于等于5就可以,使用integer有点浪费资源,一般使用刚好大于所需bit位的数可以节省资源。本例中opcode的位宽只要3bit,可以表达0~7之间的值,满足模块需要。
同样的,我们为上面两种不同形式实现的多分支选择功能模块编写一个统一的测试模块进行验证,同时比对组合逻辑和时序逻辑实现结果的异同之处,更深入地理解时序逻辑与组合逻辑的区别和联系。
//selection_from_multi_testbench.v
`timescale 1ns/1ps
module selection_from_multi_testbench();
//1. 生成时钟与复位信号
// 时钟周期为50ns*2 = 100ns, 时钟频率= 10MHz
reg clk;
reg rst;
initial begin
clk = 0;
rst = 1;
#1260;
rst = 0;
end
always clk = #50 ~clk;
//2. 给出待测模块需要的输入参数
reg [7:0] aa;
reg [7:0] bb;
reg [7:0] cc;
reg [7:0] dd;
reg [2:0] opcode;
initial begin
aa = 8'd30;
bb = 8'd40;
cc = 8'd70;
dd = 8'd80;
opcode = 3'd0;
//
forever begin
#180;
opcode = opcode + 1;
if($time > 30000)
$finish;
end
end
//3. 测试组合逻辑实现的模块
wire [7:0] out_wire0;
wire [7:0] out_wire1;
selectio_from_multi_wire dut_0(
.a(aa),
.b(bb),
.c(cc),
.d(dd),
.opcode(opcode),
.out0(out_wire0),
.out1(out_wire1)
);
//4. 测试时序逻辑实现的模块
wire [7:0] out_reg;
selectio_from_multi_reg dut_1(
.clk(clk),
.rst(rst),
.a(aa),
.b(bb),
.c(cc),
.d(dd),
.opcode(opcode),
.sum(out_reg)
);
endmodule
新建一个名为selection_from_multi_test
的Vivado工程;
将上面的设计模块selectio_from_multi_wire()
和selectio_from_multi_reg()
的源码分别保存为Verilog文件并添加到Vivado工程,注意添加文件时选择Add or Create design sources
,因为这两个文件是生成实际硬件模块的可综合设计模块;
将上面selection_from_multi_testbench()
仿真模块源码的内容保存为Verilog文件并添加到Vivado工程, 注意添加文件时注意选择Add or Create simulation sources
,因为这个文件是仿真文件,并不会生成实际的硬件模块;
点击Vivado的【Run Simulation】开始仿真。
仿真波形的查看方法此处不再赘述,请大家自行熟悉。