【答疑汇总】3月答疑汇总

413 阅读9分钟

问题1:localparam 和 parameter 有什么区别? 模块里的 localparam 可用 parameter 替代吗?

parameter 和 localparam 的区别:

(一)可覆盖性:

  • parameter:可以在模块的实例化时被覆盖。这意味着可以在模块外部改变parameter的值,从而允许同一个模块在不同的情况下使用不同的参数值。

  • localparam:不可以被覆盖。localparam定义的是局部参数,它的值在定义时就已经确定,且在模块内部和外部都不能被修改。这使得localparam非常适合定义那些不应该被外部改变的常量。

(二)意图表达:

  • 使用parameter表明设计者允许在模块的实例化时修改参数值,这为模块的可配置性提供了灵活性。

  • 使用localparam则表明这个参数是模块内部实现的一部分,不应该被外部干预,这有助于保持模块的封装性和避免意外的修改。

(三)维护性:

  • parameter的可覆盖特性可能会导致维护上的困难,因为模块的行为可能会因为外部的参数覆盖而改变,这在大型项目中可能导致问题难以追踪。

  • localparam由于其不可覆盖的特性,可以提供更稳定的行为,减少由于参数误修改带来的问题。

(四)使用场景:

  • parameter通常用于那些需要根据不同应用场景调整的参数,例如数据宽度、缓冲区大小或计数器的最大值。

  • localparam适用于定义内部使用的固定值,例如状态机的状态编码、内部计算的中间值等。

模块里的localparam可用parameter替代吗?

  • 在Verilog中,localparam可以被parameter替代,但这样做可能会改变模块的意图和使用方式,导致一些问题:

    • 封装性破坏:localparam通常用于内部参数,这些参数是模块实现的一部分,不应该被外部修改。使用parameter替代可能会破坏模块的封装性,因为它允许外部修改这些本应内部的值。

    • 意图不明确:localparam清楚地表明了这些值是局部常量,不应该被外部修改。使用parameter可能会让其他开发者或用户误以为这些参数是可以或应该被修改的,这可能会导致混淆或错误的使用。

    • 维护难度:如果parameter被外部覆盖,可能会导致模块的行为不符合原始设计的预期,增加了维护和调试的难度。

总结:

如果参数是模块内部的实现细节,不应该被外部修改,那么应该使用localparam。如果参数是设计为可配置的,希望用户能够根据不同的应用场景来调整它们,那么应该使用parameter。

问题2:关于敏感列表,i_rst 上升沿触发,高电平有效应该怎么写?

时序逻辑(如触发器)应该只由一个时钟信号控制,而i_rst可以作为一个异步或同步的控制信号。如果你想要实现一个带有异步复位的触发器,你可以这样写:

always @(posedge i_clk or posedge i_rst) begin
    if (i_rst) begin
        // 异步复位逻辑
    end else begin
        // 正常时钟边沿触发的逻辑
    end
end

如果你的设计意图是使用同步复位,那么复位逻辑应该只在时钟的上升沿触发时被考虑,如下所示:

always @(posedge i_clk) begin
    if (i_rst) begin
        // 同步复位逻辑
    end else begin
        // 正常时钟边沿触发的逻辑
    end
end

在这种情况下,复位逻辑将会在每个时钟周期检查,但只有在时钟的上升沿时才会执行。

问题3:模块中的 enable 信号是必要的吗?有啥作用啊?

在RTL模块中,enable信号是必要的,因为它控制着模块是否开始对输入数据data_in进行处理的过程。enable信号的作用可以从以下几个方面来理解:

  • 灵活的数据流控制:enable信号提供了一种机制,允许外部控制逻辑决定何时允许该模块开始工作。这在实际应用中非常有用,因为可能需要根据特定的条件或系统的其他部分的状态来启动。

  • 避免不必要的操作:如果没有enable信号,编码器将不断地对输入数据进行处理,无论外部系统是否准备好接收处理过的数据。这可能导致不必要的功耗和可能的数据冲突。

  • 同步多个模块:在一个复杂的系统中,可能有多个模块需要协同工作。enable信号可以用来同步这些模块的操作,确保数据在正确的时间被处理和传输。

总之,enable信号为RTL模块提供了一个启动和停止编码过程的控制点,使得模块可以更加灵活和高效地在一个大型系统中工作。

问题4:RTL模块内的函数可以放在顶层模块里,然后由子模块调用吗?(这样就不用把这个函数定义写两遍了)

在Verilog中,函数通常定义在模块内部,因为Verilog的函数和任务(task)是不支持跨模块调用的。这意味着你不能在一个模块中定义一个函数,然后在另一个模块中直接调用它。每个模块都需要有自己的函数定义。

然而,如果你想要避免在每个模块中重复相同的函数代码,你可以考虑以下几种方法:

  1. 包含文件:你可以创建一个包含文件(例如:functions.vh),在这个文件中定义你的函数。然后在每个需要使用这个函数的模块中通过`include语句包含这个文件。以奇偶校验函数为例:

    // functions.vh
    
    function automatic calc_parity(input [15:0] data);
        integer i;
        begin
            calc_parity = 0;
            for (i = 0; i < 16; i = i + 1) begin
                calc_parity = calc_parity ^ data[i];
            end
        end
    endfunction
    

    然后在每个模块中使用:

    `include "functions.vh"
    
    module test_1(
        // ...
    );
        // ...
    endmodule
    
    `include "functions.vh"
    
    module test_2(
        // ...
    );
        // ...
    endmodule
    
  2. 参数化模块:如果函数的逻辑与模块的参数有关,你可以创建一个参数化模块,将函数作为模块的一部分,然后在需要的地方实例化这个模块。

  3. 生成代码:使用一些外部的脚本或工具来生成重复的代码部分。

需要注意的是,使用 include文件是一种常见的做法,可以帮助你减少代码重复,但是它也可能使得代码的组织变得不那么清晰,因为它引入了一个全局的依赖。故原设计中未采用这种方式。在使用`include时,你还需确保包含文件中的内容不会意外地影响到包含它的模块的内部变量和逻辑。

问题5: `timescale 1ns / 1ps 这个时间刻度是每一个模块都需要写的吗?顶层和子模块都需要写吗?

timescale指令在Verilog中用于指定模拟的时间单位和时间精度。这个指令是可选的,但它对于确保模拟行为的一致性非常重要,特别是当涉及到延迟和时间建模时。

在Verilog中,timescale指令的作用范围是从它被声明的地方开始,一直到下一个 timescale指令或文件的结束。如果在一个文件中声明了 timescale,它将适用于该文件中的所有模块,除非在某个模块之前有新的 timescale指令被声明。

对于复杂的设计,通常在顶层文件中声明一次 timescale,这样所有的子模块都会继承这个设置,确保整个设计使用相同的时间单位和精度。如果子模块中没有声明新的 timescale,它们将使用顶层文件中的设置。

然而,如果你的设计中包含了多个文件,并且这些文件可能会被独立编译或者来自不同的库,那么在每个文件的开始处声明 timescale是一个好习惯。这样可以确保每个模块都有明确的时间单位和精度,即使它们被独立地编译或者与其他文件一起编译。

总结一下:

  • 如果你的所有模块都在同一个文件中,你只需要在文件的开始处声明一次`timescale。

  • 如果你的设计包含多个文件,最好在每个文件的开始处声明`timescale,以确保在不同编译环境下都有一致的时间单位和精度。

  • 如果你确定子模块将始终与顶层模块一起编译,并且你想要保持设计的简洁性,你可以只在顶层文件中声明`timescale,并省略子模块中的声明。

问题6: function 和 task 有啥区别?

在Verilog中,functiontask都是用来封装代码块以实现特定功能的结构,但它们在使用和特性上有一些关键的区别。以下是functiontask的主要区别:

  1. 返回值:

    • function:必须返回一个值,并且只能有一个输出,即函数的返回值。
    • task:可以没有返回值,也可以通过输出参数返回多个值。
  2. 执行时间:

    • function:在仿真中,函数调用被认为是在零时间内完成的,即它们不消耗仿真时间。
    • task:可以包含时间控制语句,如#delay,因此它们的执行可以跨越多个时间单位。
  3. 调用方式:

    • function:由于函数不消耗时间,它们不能调用task,也不能包含任何阻塞赋值或时间控制语句。
    • task:可以调用其他taskfunction,并且可以包含阻塞和非阻塞赋值语句。
  4. 并发性:

    • function:不能包含fork...join并发语句。
    • task:可以包含fork...join语句来实现并发行为。
  5. 可重入性:

    • function:通常是可重入的,意味着它们在同一时间可以被多个并行进程安全地调用。
    • task:可能不是可重入的,特别是当它们包含内部状态或时间控制时。
  6. 作用域:

    • functiontask都可以访问它们外部模块的变量,但是function通常不应该改变这些变量的值,因为它们被设计为计算并返回一个值,而不是执行具有副作用的任务。
  7. 语法:

    • function的定义以function关键字开始,后跟返回类型、函数名、输入参数,以及endfunction关键字结束。
    • task的定义以task关键字开始,后跟任务名、输入、输出和输入/输出参数,以及endtask关键字结束。

在设计复杂的硬件系统时,选择function还是task取决于你需要的功能和上述特性。简单的、不涉及时间延迟的计算通常使用function,而更复杂的、可能需要多个步骤或时间延迟的操作则使用task