【0基础学会Verilog】006. Verilog语言的多分支选择结构

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

1. 多分支结构的C语言函数示例

//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,dint opcode, 依据opcode的不同值选择将a,b,c或d赋给函数的输出out,也就是实现一个多路输入-单路输出的选择模块,也叫多路复用器。

2. 用组合逻辑实现多分支选择结构

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

3. 用时序逻辑实现多分支选择结构

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

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

  • 新建一个名为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】开始仿真。

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

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