【问题标题】:Struggling with waiting for transfer completion with VHDL努力等待 VHDL 传输完成
【发布时间】:2013-01-03 16:37:36
【问题描述】:

我需要进行持续的 SPI 通信以从我拥有的双通道 ADC 读取值,并且为此编写了一个状态机。但是,它似乎没有进入读取第二个通道的状态,我不知道为什么。这是 VHDL...

SPI_read: process (mclk)
                                                        --command bits: Start.Single.Ch.MSBF....
    constant query_x: unsigned(ADC_datawidth-1 downto 0) := "11010000000000000";    -- Query ADC Ch0 ( inclinometer x-axis)
    constant query_y: unsigned(ADC_datawidth-1 downto 0) := "11110000000000000";    -- Query ADC Ch1 ( inclinometer y-axis)

begin

    if rising_edge(mclk) then

        -- when SPI is not busy, change state and latch Rx data from last communication
        if (SPI_busy = '0') then

            case SPI_action is
                when SETUP => 
                    SPI_pol <= '0'; -- Clk low when not active
                    SPI_pha <= 1;       -- First edge is half an SCLK period after CS activated
                    SPI_action <= READ_X;
                when READ_X =>
                    SPI_Tx_buf <= query_x; -- Load in command
                    y_data <= "00000" & SPI_Rx_buf(11 downto 1);
                    SPI_send <= '1';
                    SPI_action <= READ_Y;
                when READ_Y =>
                    SPI_Tx_buf <= query_y; -- Load in command
                    x_data <= "00000" & SPI_Rx_buf(11 downto 1);
                    SPI_send <= '1';
                    SPI_action <= READ_X;
            end case;

        else
            SPI_send <= '0'; -- Deassert send pin
        end if;

    end if;

end process SPI_read;

命令被发送到 Tx 缓冲区,最后接收到的数据的值被写入一个信号,该信号输出到一些七段显示器。需要一个来自 SPI_send 的脉冲来启动传输,启动时,SPI_busy 设置为高电平,直到传输完成。

现在它只会通过 SPI 发送 query_x,我可以知道这一点,因为我可以在示波器上看到它。然而,有趣的是,它向两个显示器输出相同的值,这让我认为它仍在进入它的 READ_Y 状态,但没有改变它输出的 Tx 数据。

我已经盯着这段代码看了好几个小时了,但我想不通。有时一双新鲜的眼睛会让生活更轻松,所以如果你发现了什么,请告诉我。 另外,我非常愿意接受更好的处理方法的建议,我只是在学习 VHDL,所以我什至不确定我是否以正确的方式做事!

【问题讨论】:

  • 我没有看到 tx 部分的代码和状态声明。一个建议是坚持基本的一流程状态机模板。使busyX 和busyY 状态最简单。
  • 另外,当您学习 HDL 仿真时,它是您的朋友,它使此类调试问题变得非常容易。
  • 对不起,应该说SPI主控是一个组件,它在发送/接收时返回SPI_busy。这只是我的顶层设计中的一个过程,因此不想包含太多可能无关紧要的内容。 SPI_action 以“SETUP”状态开始,然后一旦运行就不会返回那里。不过,我不确定我理解你最后一句话的意思。
  • 你真的应该模拟这个设计,它会告诉你每个信号在一个交易周期中的状态。我看不出代码本身有任何明显错误,但这可能是关于 spi_writer 模块如何工作的问题或错误假设,或者只是一个错误。即从SPI_send 变高到busy 变高需要多少个时钟周期?是否所有东西都在同一个时钟域中运行?
  • 我自己编写了SPI组件并完全模拟了它。但你是对的,我现在应该模拟它。但是,是的,一切都超出了主时钟。我只是不确定这是否是“等待完成”类型行为的正确方法。我要做的就是在 SPI_send 上发送一个脉冲,该脉冲需要比传输周期短,然后等待 SPI_busy 被取消断言。不过,我很难在任何地方找到任何关于这种行为的文章。

标签: vhdl fpga spi


【解决方案1】:

将我的 cmets 总结为一个答案。

使用您的 SPI 主组件模拟此进程/模块。

您的方法通常是正确的,但我建议您稍微重新构建您的状态机,以便在每个 spi 事务(wait_x,wait_y)之间放置明确的等待状态,并且可能在模块之间进行更强大的握手,即留在 read_x直到busy变高,然后停留在wait_x直到busy变低。

看起来 send 被断言了两个周期,并且您在每个周期都通过 read_x 和 read_y 进行转换。

从这个程序中提取 http://wavedrom.googlecode.com/svn/trunk/editor.html 与此来源:

{ "signal" : [
  { "name": "clk",           "wave": "P........" },
  { "name": "busy",          "wave": "0..1.|0..1"},
  { "name": "SPI_Action",    "wave": "====.|.==.",   "data": ["SETUP", "READ_X", "READ_Y", "READ_X", "READ_Y", "READ_X", "READ_Y", ] },
  { "name": "SPI_send",      "wave": "0.1.0|.1.0",   "data": ["0", "1", "Load", "Start","WaitA"] },
  { "name": "SPI_Tx_buf",    "wave": "x.===|..==", "data": ["query_x","query_y","query_x","query_y","query_x"],},
]}

【讨论】:

    【解决方案2】:

    您正在按照正确的基本思路进行思考,但是您的状态机有很多不完全正确的事情 - 正如其他答案所说 - 这些在模拟中很容易发现。

    例如

            when READ_X =>
                SPI_Tx_buf <= query_x; -- Load in command
                y_data <= "00000" & SPI_Rx_buf(11 downto 1);
                SPI_send <= '1';
                SPI_action <= READ_Y;
    

    现在,如果 SPI_Busy 在第二个周期内保持低电平,这显然不会停留在状态 READ_X,而是直接转换到 READ_Y,这可能不是您想要的。

    更正常的状态机会将状态视为最外层,并对每个状态的输入信号进行不同的解释:

       case SPI_Action is             
          when READ_X => 
                         if SPI_Busy = '0' then    -- Wait here if busy
                            SPI_Tx_buf <= query_x; -- Load in command
                            y_data <= "00000" & SPI_Rx_buf(11 downto 1);
                            SPI_send <= '1';
                            SPI_action <= READING_X;
                         end if;
          when READING_X =>
                         if SPI_Busy = '1' then   -- command accepted
                            SPI_action <= READ_Y; -- ready to send next one
                         end if;
          when READ_Y => 
                         if SPI_Busy = '0' then    -- Wait here if busy
    

    可以看到,这个版本将 Busy 信号视为握手,并且仅在 Busy 改变状态时进行。我敢肯定,如果您愿意,您可以使您的方法(如果最外层)有效,但您必须自己弄清楚如何应用握手原则。

    也没有读取 X 或 Y 的“空闲”状态;该 SM 将尽可能快地交替读取 X 和 Y。通常您会同时读取它们,然后返回空闲状态,直到某个其他开始信号命令您离开空闲并执行一组新的读取。

    您还可以使用“当其他人”子句使状态机更加健壮。如果您保证涵盖所有定义的状态,这不是必须的,但它可以使维护更容易。另一方面,如果没有这样的子句,编译器会让你知道任何未发现的状态,以防出错。

    有一个神话,即“当其他人”子句必不可少,综合工具会从“当其他人”子句生成更安全但不太理想的状态机。然而,有合成属性或命令行选项来控制合成工具如何生成状态机。

    【讨论】:

    • 这很有帮助,再次感谢 Brian!这正是我不知道该怎么做的事情,我从来没有想过有单独的状态只是为了等待再次改变状态。
    【解决方案3】:

    我必须同意 David 提出的问题并添加一些注释。

    你的代码有几处不是很好,首先,你的状态列表中必须有一个"when others =>"。 除了 SPI_send,您的所有信号似乎都没有默认值。将状态机放在 if 语句中也不是一个好主意。如果你必须这样做,那么你必须在这两种情况下设置所有信号,否则你最终会得到锁存器而不是触发器。一种简单的方法是将代码请求时的所有信号设置为默认值,然后在需要时更改它们。

    这样综合工具就知道如何处理它们并且你得到正确的。

    嗯,这是来自 Siemence 的 VHDL-for-synthesis 文档:

    如果一个信号或变量没有在所有可能的情况下赋值 *if 语句的分支,推断出闩锁*。如果意图是 不推断锁存器,则必须为信号或变量分配一个 在语句的所有分支中显式地赋值。

    您可以在以下网址找到 PDF 格式的指南:http://web.ewu.edu/groups/technology/Claudio/ee360/Lectures/vhdl-for-synthesis.pdf

    【讨论】:

    • 我所有的信号在声明时实际上都是最初设置的,可能只是不清楚,因为我没有包括其余的设计。另外,如果没有其他人,那么“当其他人”的意义何在?我已经使用我为 SPI_action 设置的类型(诚然也没有显示,抱歉)非常明确地定义了状态,并且它们都包含在 case 语句中。编译器也完全没有抱怨。
    • 嗯,那是为了模拟,我的意思是在过程内部,每个信号的初始状态必须是已知的,或者信号必须在所有条件下设置成一个值,天气有if-else 语句或 case 语句。
    猜你喜欢
    • 2018-07-17
    • 1970-01-01
    • 2018-06-01
    • 2018-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-20
    • 1970-01-01
    相关资源
    最近更新 更多