在计算机硬件开发里,调试可是个关键活。Verilog 作为硬件描述语言,有俩系统任务 $display 和 $monitor,在调试中特别有用。接下来咱就好好唠唠它们在调试里的应用技巧。

一、$display 和 $monitor 基础介绍

1.1 $display 系统任务

$display 就像是个大喇叭,能在仿真的时候把你想要的信息给喊出来。它一般在需要显示某些特定时刻信息的时候用。比如说,你想知道某个信号在某个时间点的值,就可以用 $display。

下面是个简单的 Verilog 示例:

// Verilog 技术栈
module display_example;
  reg [3:0] data;  // 定义一个 4 位的寄存器变量 data

  initial begin
    data = 4'b0000;  // 初始化 data 的值为 0
    $display("Initial value of data: %b", data);  // 显示 data 的初始值,%b 表示以二进制形式输出
    #10;  // 延时 10 个时间单位
    data = 4'b1111;  // 将 data 的值更新为 15
    $display("Value of data after 10 time units: %b", data);  // 显示更新后 data 的值
  end
endmodule

在这个例子里,$display 会把 data 的初始值和 10 个时间单位后的新值都显示出来。

1.2 $monitor 系统任务

$monitor 就像是个小跟班,会一直盯着你指定的信号。只要这些信号的值有变化,它就马上把新的值给报出来。

下面是 $monitor 的示例:

// Verilog 技术栈
module monitor_example;
  reg [3:0] data;  // 定义一个 4 位的寄存器变量 data

  initial begin
    data = 4'b0000;  // 初始化 data 的值为 0
    $monitor("Time: %0t, data: %b", $time, data);  // 监控 data 的值,%0t 表示当前时间
    #10;  // 延时 10 个时间单位
    data = 4'b1111;  // 将 data 的值更新为 15
    #10;  // 再延时 10 个时间单位
    data = 4'b1010;  // 将 data 的值更新为 10
  end
endmodule

在这个例子中,$monitor 会在 data 的值每次发生变化时,显示当前时间和 data 的新值。

二、应用场景

2.1 $display 的应用场景

2.1.1 特定时刻信息查看

在仿真过程中,有时候你就想知道某个信号在特定时刻的值。比如,在一个复杂的状态机里,你想知道状态转换那一瞬间某个控制信号的值,这时候就可以用 $display。

// Verilog 技术栈
module state_machine_display;
  reg [1:0] state;  // 定义 2 位的状态寄存器
  reg clk;  // 定义时钟信号

  initial begin
    clk = 0;  // 初始化时钟信号为 0
    state = 2'b00;  // 初始化状态为 0
    forever #5 clk = ~clk;  // 产生时钟信号,周期为 10 个时间单位
  end

  always @(posedge clk) begin
    case (state)
      2'b00: begin
        state = 2'b01;  // 状态转换
        $display("At time %0t, state changed from 00 to 01", $time);  // 显示状态转换信息
      end
      2'b01: begin
        state = 2'b10;
        $display("At time %0t, state changed from 01 to 10", $time);
      end
      2'b10: begin
        state = 2'b11;
        $display("At time %0t, state changed from 10 to 11", $time);
      end
      2'b11: begin
        state = 2'b00;
        $display("At time %0t, state changed from 11 to 00", $time);
      end
    endcase
  end
endmodule

在这个状态机的例子中,$display 会在每个状态转换的时刻把信息显示出来,方便你查看状态机的运行情况。

2.1.2 调试信息输出

当你在调试一个复杂的模块时,可能需要输出一些中间结果或者调试信息。这时候,$display 就派上用场了。

// Verilog 技术栈
module adder_debug;
  reg [3:0] a, b;  // 定义两个 4 位的寄存器变量
  wire [4:0] sum;  // 定义一个 5 位的线网变量,用于存储加法结果

  assign sum = a + b;  // 实现加法运算

  initial begin
    a = 4'b0010;  // 初始化 a 的值为 2
    b = 4'b0100;  // 初始化 b 的值为 4
    $display("Input values: a = %b, b = %b", a, b);  // 显示输入值
    #10;  // 延时 10 个时间单位
    $display("Output value: sum = %b", sum);  // 显示加法结果
  end
endmodule

在这个加法器的例子中,$display 会先显示输入值,再显示加法结果,帮助你确认加法器是否正常工作。

2.2 $monitor 的应用场景

2.2.1 信号变化监控

当你需要实时监控某个信号或者一组信号的变化时,$monitor 就非常合适。比如,监控一个计数器的计数值,只要计数值有变化,$monitor 就会马上显示出来。

// Verilog 技术栈
module counter_monitor;
  reg clk;  // 定义时钟信号
  reg [3:0] counter;  // 定义 4 位的计数器

  initial begin
    clk = 0;  // 初始化时钟信号为 0
    counter = 4'b0000;  // 初始化计数器为 0
    $monitor("Time: %0t, Counter value: %b", $time, counter);  // 监控计数器的值
    forever #5 clk = ~clk;  // 产生时钟信号,周期为 10 个时间单位
  end

  always @(posedge clk) begin
    counter = counter + 1;  // 计数器加 1
  end
endmodule

在这个计数器的例子中,$monitor 会一直监控计数器的值,只要计数器的值发生变化,就会显示当前时间和新的计数值。

2.2.2 多信号关联监控

有时候,你需要同时监控多个信号,并且查看它们之间的关联。比如,在一个数据传输模块中,你想监控数据信号、使能信号和时钟信号之间的关系,$monitor 就可以一次性把这些信号的值都显示出来。

// Verilog 技术栈
module data_transfer_monitor;
  reg clk;  // 定义时钟信号
  reg en;  // 定义使能信号
  reg [3:0] data;  // 定义 4 位的数据信号

  initial begin
    clk = 0;  // 初始化时钟信号为 0
    en = 0;  // 初始化使能信号为 0
    data = 4'b0000;  // 初始化数据信号为 0
    $monitor("Time: %0t, clk: %b, en: %b, data: %b", $time, clk, en, data);  // 监控多个信号的值
    forever #5 clk = ~clk;  // 产生时钟信号,周期为 10 个时间单位
  end

  always @(posedge clk) begin
    if (en) begin
      data = data + 1;  // 当使能信号有效时,数据加 1
    end
  end

  initial begin
    #10 en = 1;  // 在 10 个时间单位后使能信号有效
    #20 en = 0;  // 在 30 个时间单位后使能信号无效
  end
endmodule

在这个数据传输模块的例子中,$monitor 会同时监控时钟信号、使能信号和数据信号的值,并显示它们的变化情况,这样你就可以清楚地看到这些信号之间的关联。

三、技术优缺点

3.1 $display 的优缺点

3.1.1 优点

  • 使用灵活:你可以在代码里任何你想的地方使用 $display,想在哪输出信息就可以在哪输出。
  • 特定时刻输出:能精准地在你指定的时刻输出信息,对于调试时查看特定时间点的信号值很有帮助。

3.1.2 缺点

  • 不能实时监控:它不会自动跟踪信号的变化,你必须手动在合适的地方添加 $display 语句。
  • 代码冗余:如果需要在多个地方显示信息,可能会让代码变得冗长,看起来不简洁。

3.2 $monitor 的优缺点

3.2.1 优点

  • 实时监控:只要监控的信号值有变化,它就会马上显示出来,能让你及时了解信号的动态。
  • 方便多信号监控:可以同时监控多个信号,轻松查看它们之间的关系。

3.2.2 缺点

  • 资源消耗:一直监控信号会占用一定的系统资源,如果监控的信号很多,可能会影响仿真的速度。
  • 无法特定时刻输出:它是根据信号的变化来输出信息的,没办法精确地只在特定时刻输出。

四、注意事项

4.1 $display 注意事项

  • 格式控制:使用 $display 时,要注意格式控制符的使用。比如 %b 表示二进制输出,%d 表示十进制输出等。如果格式控制符用错了,显示的结果可能就不是你想要的。
  • 时间准确性:如果想输出特定时刻的信息,要确保延时语句和 $display 语句的顺序正确,不然输出的时间可能就会不准确。

4.2 $monitor 注意事项

  • 信号选择:不要监控太多无关的信号,否则会浪费系统资源,还会让输出的信息变得混乱。
  • 仿真性能:在大规模的仿真中,要注意 $monitor 对仿真性能的影响。如果发现仿真速度变慢,可以考虑减少监控的信号数量或者使用其他调试方法。

五、文章总结

$display 和 $monitor 都是 Verilog 里很实用的系统任务,在硬件调试中能发挥重要作用。$display 适合在特定时刻输出信息,使用灵活但不能实时监控;$monitor 则擅长实时监控信号的变化,方便查看多信号之间的关联,但会消耗一定的系统资源。在实际调试过程中,我们要根据具体的需求来选择使用哪个系统任务,同时注意它们的使用注意事项,这样才能更高效地完成调试工作。