【0基础学会Verilog】005. Verilog语言的选择结构

通过前面的博文我们已经学会如何将一个简单的计算多项式的值C语言函数转换为具有相同功能的Verilog模块,并为其编写相应的测试模块,即所谓testbench对其进行仿真,通过对仿真波形的检查可以验证所设计模块的功能是否与C语言函数的功能相同,也就是确保模块功能的正确性。

本篇博文介绍如何将C语言的选择结构转换为Verilog硬件模块。我们知道,C语言的选择结构有两种形式,if-else结构和switch-case结构。一般if-else结构实现双分支,而switch-case结构可以简洁地表达多分支结构。本博文先讨论双分支结构的实现方法,下一篇讨论多分支的情况。通过前面的学习,大家已经了解到同样的功能,Verilog既可以用组合逻辑实现,也可以用时序逻辑实现。本文分别用两种方法实现,方便大家进一步理解组合逻辑和时序逻辑的区别与联系。

1. 双分支结构的C语言函数

//C语言函数实现双分支选择功能
void selection(
    char a,
    char b,
    bool sel,
    char* out
)
{
  if(sel == true)
    *out = a;
  else 
    *out = b;

  return;
}

我们以一个很简单的函数来讲解Verilog实现选择结构的方法。函数selection有三个输入参数char a,bbool sel, 依据sel的不同值选择将a或者b值赋给out。当sel为真时out的值为a,否则out的值为b。

2. 用组合逻辑实现双分支结构

组合逻辑的表达形式有两种,一是用assignwire型变量(或信号)赋值,二是采用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

3. 用时序逻辑实现双分支结构

时序逻辑的同样可以实现上面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

4. 使用Vivado自带仿真器仿真

  • 新建一个名为selection_test的Vivado工程;

  • 将上面设计模块selectio_wire()和selectio_reg()的源码分别保存为文件selectio_wire.vselectio_reg.v,并添加到Vivado工程, 注意添加文件时选择Add or Create design sources,因为这两个文件是生成实际硬件模块的可综合设计模块;

  • 将上面selection_testbench()仿真模块源码的内容保存为文件将上面selection_testbench.v,并添加到Vivado工程, 注意添加文件时注意选择Add or Create simulation sources,因为这个文件是仿真文件,并不会生成实际的硬件模块;

  • 点击Vivado的【Run Simulation】开始仿真。

仿真波形的查看方法此处不再赘述,请大家自行熟悉。

你可能感兴趣的:(0基础学会Verilog,fpga开发,c语言,c++,算法)