【问题标题】:Simple VHDL testbench procedure for sending serial bytes?用于发送串行字节的简单 VHDL 测试台程序?
【发布时间】:2020-02-28 17:07:23
【问题描述】:

我正在尝试在我的设计中移除 bitbashing,并使用一个程序从 DUT 外部发送测试信号。序列化消息的格式是起始位为“0”,字节为 MSB 在前,停止位为“1”。线路在“1”处空闲。我认为我在使用要在过程和主进程之间传递的数据类型时遇到问题。

这是我的程序(波特率是串行时钟周期的时间常数):

  procedure send_midi_byte (
    signal byte_in  : in  std_logic_vector;
    signal midi_out : out std_logic) is
  begin
    midi_out <= '0';
    wait for baudrate;
    for i in 7 to 0 loop
      midi_out <= byte_in(i);
      wait for baudrate;
    end loop;
    midi_out <= '1';
    wait for baudrate;
  end send_midi_byte;

这就是我如何调用它来发送几个字节(byte_slv 是一个 8 元素 std_logic_vector):

    byte_slv <= x"90";
    send_midi_byte(byte_slv, midi_in_int);

我尝试了几种不同的方法,这是唯一一种不会出错的方法,但当然它不会起作用,因为过程中的非阻塞分配意味着我的串行信号将只是“1”波特率指定的时间长度。

如何正确编写这个程序?

【问题讨论】:

  • 修复循环范围并在尝试任何其他方法之前重试。

标签: vhdl fpga hdl


【解决方案1】:

除了 Jim 的建议之外,请先修复循环范围并重试,然后再尝试其他任何操作。 7 to 0 是一个空范围; 0 to 77 downto 0 都不是。

非阻塞(关于分配)在 VHDL 中是一个没有意义的术语,但在一个过程中的循环内的信号分配绝对没问题。这是一个推迟的任务,计划在wait for baudrate 开始时进行。当然,它会等待指定的时间,并在完成后开始下一个循环迭代。

【讨论】:

  • 虽然问题中的用法不对,但我认为说非阻塞在VHDL中是一个没有意义的术语是不正确的。您当然可以使用 VHDL 来实现一些重要的东西。例如,VUnit 必须非常清楚地记录哪些函数是阻塞的,哪些是非阻塞的。
  • @Harry 编辑澄清。但是功能肯定不能阻塞吗? (虽然程序当然可以)。
【解决方案2】:

我尝试了几种不同的方法,这是唯一一种不会出错的方法,但当然它不会起作用,因为过程中的非阻塞分配意味着我的串行信号将只是“1”波特率指定的时间长度。

你在这里断言的前提是不正确的。过程调用失败的原因是循环参数规范提供了一个空范围(循环参数先求值,一个循环语句执行0次或多次)。

如何正确编写这个程序?

过程send_midi_byte 中包含修复的最小、完整和可验证示例:

library ieee;
use ieee.std_logic_1164.all;

entity cmoc_cmoc is
end entity;

architecture mcve of cmoc_cmoc is
    constant baudrate:  time := 26.925 us;  -- 38.400 baud
    procedure send_midi_byte (
        signal byte_in:   in  std_logic_vector;
        signal midi_out:  out std_logic
    ) is
        alias in_byte: std_logic_vector (7 downto 0) is byte_in;  -- ADDED
    begin
        midi_out <= '0';
        wait for baudrate;
        for i in in_byte'range loop        -- WAS 7 to 0
            midi_out <= in_byte(i); -- WAS byte_in(i);
            wait for baudrate;
        end loop;
        midi_out <= '1';
        wait for baudrate;
    end procedure send_midi_byte;
    signal some_byte:   std_logic_vector (7 downto 0) := x"41";
    signal midi_out:    std_logic := '1';
    type baud is (IDLE,START, BD0, BD1, BD2, BD3, BD4, BD5, BD6, BD7, STOP);
    signal baud_cnt:    baud;
begin
PROCEDURE_CALL:
    process 
    begin
        wait for baudrate;  -- SHOW IDLE on midi_out;
        send_midi_byte(some_byte, midi_out);  -- added second parameter
        wait;
    end process;
BAUD_CTR:
    process
    begin
        if baud_cnt = IDLE then
            wait until midi_out = '0';
        end if;
        loop
            baud_cnt <= baud'RIGHTOF(baud_cnt);
            wait for 0 ns;
            report "baud(" & baud'image(baud_cnt) &
                   ") midi_out = "  & std_ulogic'image(midi_out);
            wait for baudrate;
            if baud_cnt = STOP then
                baud_cnt <= IDLE;
                exit;
            end if;
        end loop;
        wait;
    end process;
end architecture;

注意byte_in 的类没有在过程声明中提供。对于模式为in 的子程序参数,默认类为constant

已保留 MSB 第一顺序以匹配您的问题文本。 (UART 先发送 LSB)。

BAUD_CTR 进程是用来演示传输波特率的点缀。

对于一个 UART,输入字节在左侧以 MSB 形式给出并首先传输 LSB,别名范围可以反转,或者循环参数可以使用 `REVERSE_RANGE。

报告声明:

ghdl -r cmoc_cmoc --wave=cmoc_cmoc.ghw
cmoc_cmoc.vhdl:45:13:@26925ns:(report note): baud(start) midi_out = '0'
cmoc_cmoc.vhdl:45:13:@53850ns:(report note): baud(bd0) midi_out = '0'
cmoc_cmoc.vhdl:45:13:@80775ns:(report note): baud(bd1) midi_out = '1'
cmoc_cmoc.vhdl:45:13:@107700ns:(report note): baud(bd2) midi_out = '0'
cmoc_cmoc.vhdl:45:13:@134625ns:(report note): baud(bd3) midi_out = '0'
cmoc_cmoc.vhdl:45:13:@161550ns:(report note): baud(bd4) midi_out = '0'
cmoc_cmoc.vhdl:45:13:@188475ns:(report note): baud(bd5) midi_out = '0'
cmoc_cmoc.vhdl:45:13:@215400ns:(report note): baud(bd6) midi_out = '0'
cmoc_cmoc.vhdl:45:13:@242325ns:(report note): baud(bd7) midi_out = '1'
cmoc_cmoc.vhdl:45:13:@269250ns:(report note): baud(stop) midi_out = '1'

还有一个波形显示baud_cnt

【讨论】:

  • 这很好,解决了这个问题。很高兴知道“X 到 Y”给出了一个空范围。
  • X 到 Y 仅在 X 较大时才给出空范围。
  • 解决方案有问题,因为它总是需要两个步骤。第 1 步:分配给信号。第二步:调用子程序
  • @JimLewis - 您正在谴责缺少minimal reproducible example(此处),并且本可以要求提供更多信息(来自问题作者),而不是从整块布中制造出两难境地。这里对多字节消息的唯一要求是在调用过程时对信号 byte_in 的分配是有效的。这里的停止位(过程中的最后一个wait for baudrate;)可以在 signal 分配请求新的 byte_in(在停止位期间不使用)值。信号甚至可以是消息的索引。这一切都取决于问题。
  • @user1155120 我的主张是,对 byte_in 使用信号类参数是一种糟糕的编码风格。如果 byte_in 信号是在与调用相同的进程中驱动的,那么它只是额外的忙碌工作(正如我所展示的) - 虽然它没有错 - 它严重限制了任何进一步的抽象(如我所展示的)。如果 byte_in 信号是从一个单独的进程驱动的,你将如何防止损坏 midi_out?很简单,您不会使用信号进行异步消息处理 - 相反,我通常使用共享变量和 FIFO(但这超出了 OP 问题的范围)
【解决方案3】:

问题 你有两个错误。首先,Brian 指出,你的范围是错误的。表达范围的一种简单方法是使用 'range.这显示在下面的代码中。

此外,您希望将 byte_in 参数的类更改为常量,如下代码所示。

解决方案

  procedure send_midi_byte (
    constant byte_in  : in  std_logic_vector;
    signal   midi_out : out std_logic
  ) is
  begin
    midi_out <= '0';
    wait for baudrate;
    for i in byte_in'range loop
      midi_out <= byte_in(i);
      wait for baudrate;
    end loop;
    midi_out <= '1';
    wait for baudrate;
  end send_midi_byte;

那么为什么要保持不变呢?常量意味着参数在子程序调用的生命周期内是静态的。常量参数允许任何产生值的东西(文字、表达式,甚至信号或变量名),例如:

send_midi_byte(x"90", midi_in_int);
for i in 0 to 10 loop
  send_midi_byte(x"90" + i, midi_in_int);
end loop;

您已经熟悉这一点,因为所有语言运算符(作为函数实现)都有常量分类参数(这是默认值,在标准包中通常不使用它)。

参数指南
对于输入:
在对象需要更新时使用信号 - 例如使用'事件,或在等待语句中使用对象。通常这意味着对象来自架构(例如 DUT)。
在需要传递文字值时使用常量

对于输出:
在将值传递给架构(例如 DUT)时使用信号
使用变量 将值传回本地进程时。

为什么参数应该是常数
在您的示例中,它将使用信号,但是,使用模型将始终是,1)分配给信号,然后 2)调用子程序:

byte_slv <= x"90";
send_midi_byte(byte_slv, midi_in_int);

这可能会或可能不会让您烦恼到足以成为问题的程度。对于其他过程,信号参数的更新可能确实以一个增量周期为时已晚。这也很容易在子程序中解决。

OTOH,一旦你想从另一个过程调用你的 send_midi_byte 过程,它就会变得更加困难或者根本不起作用。在下面的过程中,我们尝试通过向 midi 发送递增字节来进行测试:

procedure send_inc_midi_bytes (
  constant first_byte_in : in std_logic_vector;
  constant number_bytes  : in integer ;
  signal midi_out  : std_logic
) is 
   -- signal byte_in : std_logic_vector(7 downto 0); -- want to do this
begin
  for i in 0 to number_bytes - 1 loop
    byte_in <= byte_in + i ; 
    send_midi_byte(byte_in, midi_out);
  end loop ;
end procedure send_inc_midi_bytes ; 

我知道修复范围可以解决您的确切问题,但有一天,您可能希望做更多事情 - 那时,您会希望有人建议您使用常量而不是信号。

【讨论】:

  • 感谢有关传递参数的信息,但现在我相信至少对于我的 for 循环来说,我又回到了并发语句的问题上。仿真现在将 midi_in_int 设置为 0 用于波特率时间,设置回 1 用于波特率时间,然后复位。就好像 for 循环不存在一样。与那个结构有什么关系?
  • 注意 OP 的循环参数规范的方向提供了一个 null 范围
  • 常量表示参数在子程序的生命周期内是静态的。 在子程序调用的生命周期内。参数值是动态阐述的。另请注意,in 模式参数的默认类是常量。你的答案是找错了树,原来的帖子没有提供minimal reproducible example。请注意,在循环退出之前,顺序进程调用不会完成。
  • @user1155120 感谢您的更正。需要注意的是,子程序只有在被调用时才会详细说明,所以对于 VHDL 人来说,生命周期只能指子程序调用,但是,鉴于 Verilog/SysteVerilog,我按照您的建议将子程序的生命周期更改为子程序调用。还应该注意的是,范围为 NULL 的循环不是错误 - 它只是不迭代,因此循环将立即退出,或者可能永远不会进入。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-11-05
  • 1970-01-01
  • 2015-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多