VHDL面试问题-检测数字是否可以除以5而无余数


24

我看到了有关VHDL的一个不错的面试问题-构建一个可以接收数字并检测是否可以将其除以5而没有余数的系统。我试图用状态机解决这个问题(我想他们不希望您使用modrem),而我确实取得了初步的成功(像5、10、15这样的数字以及像20、40、80这样的数字都可以工作),其他数字(例如130、75等)对我来说却失败了。

我将展示我的状态机,但这是一团糟(不是代码,是绘图),而且就像我说的那样,甚至无法正常工作。

基本上,我想做的是写下可被5整除的二进制数,并构建一个适用于它们的状态机。

如果您能向我展示如何解决此问题以及面对此类问题时如何思考,我将非常高兴。

谢谢!


您的意思是(可综合的)硬件实现,而不仅仅是用于测试整数文字是否可被5整除的代码(例如,对于testbench)。
smci

@smci我实际上是在请求状态机的原理图/绘图,但是该状态机的代码不会受到伤害。戴夫·特威德(Dave Tweed)完美地回答了这个问题。
伊兰(Eran)

然后我将其重命名为“ VHDL面试问题-cct以检测是否...”
smci

egreg math.stackexchange.com/a/2569882/213607在此处做出的回答可能会为一些更并行的方法提供一些启发。
mathreadler

Answers:


37

实际上,以串行方式执行余数运算非常容易。关键的假设是,如果数据是串行的,则数据以MSB优先。您只需要N个状态就可以计算出余数N。从“ 0”状态开始,如果最后一位(无论有多少位都结束)以“ 0”状态结束,则余数为零。

原理图

模拟此电路 –使用CircuitLab创建的原理图

想一想,如果您唯一需要跟踪的是余数,您将如何进行长除法:

process (clk)
begin
  if rising_edge(clk) then
    if reset = 1 then
      state <= 0;
    else
      if (state & din) >= N then
        state <= (state & din) - N;
      else
        state <= state & din;
      end if;
    end if;
  end if;
end process;

6
哇,我看到了它的工作原理,但是您可以解释一下如何提出状态机?起点是什么?我从来没有见过这样做,我只是想知道如何提出逻辑是什么?
zoder

7
对于N = 5的特定情况,状态图就是从VHDL代码获得的结果。换句话说,如果状态表示当前余数,则下一个状态是将状态左移一位,然后将输入位加到该位上,然后在必要时减去5所得到的状态。
戴夫·特威德

3
如果这很漂亮,那么如果有人在采访中亲自提出来,我将给我留下深刻的印象。然后,我很乐意请他们评论与每个时钟周期仅使用rem运算符处理完整向量相比,合成结果有何不同。
卡斯珀罗

8
@zoder状态是残基mod 5;0箭头指向2n mod 5,1箭头指向(2n + 1) mod 5
hobbs

2
你可以添加的声明statedinN你的代码?
mkrieger1

15

如果数据是LSB优先的,您还可以设计状态机:

DFA的图形表示,如附录中此答案末尾所述。

这种确定性有限自动机(DFA)的存在直接源自另一个答案,该答案描述了MSB优先的DFA。由于DFA接受的语言是常规语言,并且已知常规语言会在逆转时关闭(例如参见here),因此必须有一个DFA接受以下语言:

L={w{0,1}| reverse(w)10 is divisible by 5}

施工

  1. Dave Tweed的答案中复制MSB优先的DFA 。我为此使用了自动机工具JFLAP

  2. 将显式转​​换算法应用于DFA逆转,例如,如CS.SE:设计DFA及其逆转中所述
    您可以在此答案的旧版本中看到此步骤的结果(未精简)。




  3. q0q1

实际上,生成的自动机给出了正确的答案:

具有两列“输入”和“结果”的表,列出了各种数字导致“接受”还是“拒绝”。


Arev5=(Q,Σ,δ,q0,F)Q={q0,q1,q2,q3,q4}Σ={0,1}F={q0}δ

δ(q0,0)=q0,δ(q0,1)=q1δ(q1,0)=q4,δ(q1,1)=q3δ(q2,0)=q1,δ(q2,1)=q2δ(q3,0)=q2,δ(q3,1)=q4δ(q4,0)=q3,δ(q4,1)=q0


如果您在逆转DFA时遇到困难,也可以逆转方程式:代替new_state = state * 2 +输入,可以使用(new_state-input)/ 2 = state,然后交换state和new_state。新方程式的DFA应该解决LSB优先的问题。
Eyal

为什么第3季度和第4季度都这样标记,反之亦然?交换标签q3和q4,机器将执行算法“减半(模5)并添加输入位”。
Rosie F

2
@RosieF:对于不熟悉离散数学的人,短语“ halve(mod 5)”可能使用更多解释。司在这方面需要加入任何碱的倍数将需要使号码匀,所以3/2(MOD 5)将(3 + 5)/ 2,即4
supercat

7

提出(首先是MSB)状态机的一种方法如下:

  1. 到目前为止收到的号码是N。假设您知道其余部分M = N mod 5

  2. 有新的东西来了,现在有新的价值N' = N*2 + b

  3. 然后是新的余数M' = (N*2 + b) mod 5 = (M*2 + b) mod 5

这很容易手工制成表格:

    M b | M'
------------------
    0 0 | 0
    1 0 | 2
    2 0 | 4
    3 0 | 1个
    4 0 | 3
    0 1 | 1个
    1 1 | 3
    2 1 | 0
    3 1 | 2
    4 1 | 4

这与Dave Tweed的答案中的状态机匹配。


5

有人希望面试的问题是关于如何解决问题的,而不是VHDL或Verilog的来龙去脉。一旦有了算法,语言细节就很简单。

S=0S(2S+d) mod 5 SS,dS=0,,4

S=0,k=0S(S+2kd) mod 5,kk+1k24=1 mod 5小号小号+2ķd 模 5ķķ+1个 模 4小号ķd小号ķ小号=04ķ=03


3

根据VHDL的编写目的,您可能需要采用一种将其描述为直接组合计算的方法。接收数字可能意味着整个数字将在一个时钟周期内处于寄存器中。

例如,您可以记下每个位代表的值的mod 5,将它们加在一起,然后重复该过程,直到剩下少于5的值为止。要么对所有归约步骤进行组合实现,或在少数几个周期内重用该逻辑。

但是,如果您使用VHDL rem运算符,则可能是正确的答案。假设公司拥有不错的综合工具,那将为您提供相当有效的实现-可能比状态机解决方案要大得多的面积,但要具有完整的吞吐量,因此每次计算可能要消耗大量能量。这种选择将花费最少的时间来实施,因此对雇主来说可能是最少的钱!

公平地说,这可能不是他们正在寻找的关于此类问题的答案-但这也是展示任何实际设计经验的机会。


3

如果数字以大于一位的大块表示,则使用一些并行计算来计算残差模数15可能会有所帮助,前提是如果残差为零,则计算得出的结果可能恰好是15。一种简单的计算mod-15残差的方法是,观察到对于N> = 1的任何值,将最左边的4N位添加到超出该数量的部分中,将得出与原始mod 15一致的值。可以根据可用资源以多种不同方式细分问题。

例如,如果一个以32位值开头,则可以将其视为八个4位值。可以将它们成对地相加,以产生四个5位值,这些值又可以组合成两个6位值或一个7位值。将该7位值的高3位与低4位相加,将得到一个最大为21的5位值。这样,通过观察最终值是否为5,可以确定原始值是否为5的倍数。 0、5、10、15或20之一。


...或者您可以在整个过程中使用4位加法器,只需确保每个进位结点成为以后电路中加法器的进位点即可。三层加法后,您将得到一个4位结果和四个尚未使用的进位。将三个进位与最后4位加法并行相加,并将它们的总和加到结果中,最后一个进位作为进位。这样最多可产生19个,因此您以后不需要匹配20个。
Henning Makholm '17

@HenningMakholm:有很多安排加法器以产生所需结果的方法。在给定情况下哪种方法更好,则可能取决于特定项目的路由或资源利用问题。另一个技巧是使用进位保存加法器,但要利用以下事实:移位后的输出的最高位可能会移到底部。因此,一层可以将8个输入变成6,然后6变成4,然后4变成3,3变成2。每层的一个输出将是AND门,而另一个是XOR门,因此传播时间降到了1。一对4位值...
supercat

...一个唯一的进位链是四个异或门。至于使输出低于19还是更好,或者最好检查20作为可能的残差,这可能取决于资源的可用性和利用率。给定一个不超过30的数字,将上下半字节相加将得出的值最多为15(16 + 14-> 1 + 14或0 + 15-> 0 + 15),但添加显式值检查(20、25、30)中的部分或全部可能会更便宜。
超级猫

2

我不记得我的VHDL,但这是最初想到的想法的草图:

2的第一个幂的最后一位数字(以10为底)是1、2、4、8、6、2,...,并且循环重复。因此,2的幂的余数模5是1、2、4、3,...。

使用它,我们可以从LSB移入一些位,并且每当1看到一个位时就累积与该位置相对应的余数mod 5 。也进行累积模5,足以检查最后的和是否为零。


1

我们可以从此处的答案中使用该想法,即在以4为底的情况下,只有在交替数字和为的情况下,我们才能得出数字可以被5整除的结论。因此,我们

  1. 将数字2按2分组
  2. 将奇数相加,再减去偶数2位块。
  3. 如果结果在几位的两个补码区域中,例如[-4,3](假设我们使用两个补码就容易检查),那么我们就完成了,只有在总和为0,这是一个非常简单的逻辑表达式要检查(基本上只是一个很大的数,也不在所有结果位上,不是吗?)
  4. 否则,我们将迭代新的(短得多的数字)。

让我们尝试数字166 =(10)(10)(01)(10):2,2,1,2

2-2 + 1-2 = -1

这是绝对值<= 3而不是0,为什么我们可以在一次迭代中得出166不被5均分的结论。

可能是小内存在门速度/ nr方面比迭代更便宜/更好。当然,可以预先计算出最差的结果(在允许输入的情况下,最大可能的结果)并相应地计划设计。


1

MSB方法肯定更容易,但是我设法制作了LSB状态图而无需生成MSB解决方案……这花了我几个小时。事实证明,它与@ComFreek显示的内容等效,只是注释不同。

我们将跟踪两个数字。首先,我们将跟踪以模5(“ SUM”)为单位的运行总和。其次,我们将跟踪要移入的下一个2的幂的值,以5为模(“ NEXT”)。我将在顶部用“ SUM”的可能值以及在它们下面的相应“ NEXT”值代表每个状态。

我们将从“ SUM”模5为0的情况开始:

初始

请注意,状态看起来像:
3,2,4,1
1,4,3,2

等价于:
1,3,4,2
2,1,3,4

因为两个状态都表示:
SUM = 1且NEXT = 4 OR
SUM = 2且NEXT = 3 OR
SUM = 3且NEXT = 2 OR
SUM = 4且NEXT = 1。

好的,现在我们需要开发其他状态,因为大多数访问者不会对只有一个状态的状态图印象深刻。每个状态都有两个转换时,我们就完成了。

每当转换到新状态时,“ NEXT”中的每个数字都会加倍,然后取模5。对于“ SUM”,请遵循以下规则:

  • 如果沿0过渡,则第一行将保留其值。
  • 如果沿1过渡,则每一列都是旧状态的“ SUM” +“ NEXT”模5。

因此,让我们从输入位为1时的过渡开始。

全1

好吧,现在我们填写零。仅添加了一个状态,因此我们将继续并填写其过渡。

完成

瞧!我们有一个状态机可以先接受LSB,而不必生成MSB解决方案。


1

以上所有内容似乎都太复杂了!有一种简单的数学方法可以检测二进制整数是否可被五整除。首先,您还记得如何用普通的十进制算术“淘汰”吗?十进制整数的余数模9与其数字总和的余数模9相同。之所以可行,是因为9比数字底数少1。

有一个类似的过程,即“淘汰十一”,其中备用数字的符号设置为负。之所以可行,是因为11比数字底数大1。

因此,如果我们要“淘汰五位”,则可以用四进制数表示整数。然后,我们从最低的一对数字开始作为初始总和,然后从下一对数字中减去它以获得下一个总和。以这种方式遍历候选整数之后,如果原始整数可被5整除,则最终总和将为零或被5整除。

示例70:01 00 01 10-> 01 00 -1-> 01 01-> 00,可被5整除示例49:11 00 01-> 11 -1-> 1 00-> 1,非可被5整除

请注意,对于累积差异的符号以及有携带的情况,您必须携带额外的位。

另一种可行的方法是,简单地添加十六进制数字以得到模15的残差。当然,您需要最后的逻辑步骤来识别零,五和十这三个可接受的结果。

示例70:4 6-> A,因此70被5整除(但不能被15整除)示例49:3 1-> 4,因此70不被5整除。

请注意,尽管在计算机逻辑中最容易实现2 +/- 1的幂,但是您可以使用不同的数字基来构造许多除数测试。

用十进制算术,我的最爱之一是我对残渣mod 7的测试。请注意,100比7的倍数大2,因此将数字成对分组(以100为基数),并从单位中添加数百个TWICE。在这里,我们从左到右工作...

例如:98 76-> 2 72-> 76,因此9876不能被7整除。它是6 mod7。例如:03 45 67-> 51 67-> 1 69-> 71 1个模组7。

当然,对于二进制,只需取八进制数字之和(3位一组)。

抱歉,我希望我是Verilog的专家,但是在这个人生阶段我可以提供所有的算法。请参阅Ron Doerfler的“航位推算”,以了解许多此类技巧。


我想知道我们的加拿大堂兄弟会不会有一些特殊的算法。由于他们取缔了加拿大币,因此所有价格均四舍五入至最接近的0.05美元。
richard1941

1

一个VHDL面试问题应该导致一些VHDL代码。

我曾经有一次发现戴夫·特威德(Dave Tweed)状态转换表的实现的ghdl llvm后端错误,其中ghdl的作者将函数的实现提炼为17行:

type remains is (r0, r1, r2, r3, r4); -- remainder values

    function mod5 (dividend: bit_vector) return boolean is
        type remain_array is array (NBITS downto 0) of remains;
        type branch is array (remains, bit) of remains;
        constant br_table:  branch := ( r0 => ('0' => r0, '1' => r1),
                                        r1 => ('0' => r2, '1' => r3),
                                        r2 => ('0' => r4, '1' => r0),
                                        r3 => ('0' => r1, '1' => r2),
                                        r4 => ('0' => r3, '1' => r4)
                                      );
        variable  remaind:    remains := r0;
        variable tbit:        bit_vector (NBITS - 1 downto 0) := dividend;
    begin
        for i in dividend'length - 1 downto 0 loop
            remaind := br_table(remaind,tbit(i));
        end loop;
        return remaind = r0;
end function;

关联的测试用例非常小,可以简化调试,并使用与VHDL兼容的状态名称(枚举类型仍然保留):

dave_tweed.png (由Dia创建)

这里的想法是该函数(甚至是一个27行的VHDL程序示例)足够简短,可以在面试期间编写VHDL答案。无需担心破坏需要知识和技能证明的面试问题,被调查者在被质疑时将为实施辩护。

(今天早些时候在提交1f5df6e中修复了llvm后端错误。)

值得注意的事情之一是状态转换表还告诉我们,当从被除数中减去5时,商位将变为由较低剩余值的状态(或r4的两个转换)表示的“ 1”。可以将其编码在单独的表(或看起来很麻烦的记录类型的表)中。历史上,我们是在图形硬件中执行此操作的,该硬件处理的水平屏幕分辨率为5像素的倍数。

这样做使我们产生了商和余数的div / mod5:

library ieee;
use ieee.std_logic_1164.all;

entity divmod5 is
    generic (
        NBITS:  natural := 13 
    );
    port (
        clk:        in  std_logic;
        dividend:   in  std_logic_vector (NBITS - 1 downto 0);
        load:       in  std_logic;
        quotient:   out std_logic_vector (NBITS - 3 downto 0);
        remainder:  out std_logic_vector (2 downto 0);
        remzero:    out std_logic
    );
end entity;

architecture foo of divmod5 is
    type remains is (r0, r1, r2, r3, r4); -- remainder values
    type remain_array is array (NBITS downto 0) of remains;
    signal remaindr:    remain_array := (others => r0);
    signal dividendreg: std_logic_vector (NBITS - 1 downto 0);
    signal quot:        std_logic_vector (NBITS - 3 downto 0);
begin

parallel:
    for i in NBITS - 1 downto 0 generate
        type branch is array (remains, bit) of remains;
        -- Dave Tweeds state transition table:
        constant br_table:  branch := ( r0 => ('0' => r0, '1' => r1),
                                        r1 => ('0' => r2, '1' => r3),
                                        r2 => ('0' => r4, '1' => r0),
                                        r3 => ('0' => r1, '1' => r2),
                                        r4 => ('0' => r3, '1' => r4)
                                      );

        type qt is array (remains, bit) of std_ulogic;
    -- Generate quotient bits from Dave Tweeds state machine using q_table.
    -- A '1' when a remainder goes to a lower remainder or for both branches
    -- of r4. A '0' for all other branches.

        constant q_table:   qt :=     ( r0 => (others => '0'),
                                        r1 => (others => '0'),
                                        r2 => ('0' => '0', '1' => '1'),
                                        r3 => (others => '1'),
                                        r4 => (others => '1')
                                      );
        signal tbit:    bit;
    begin
        tbit <= to_bit(dividendreg(i));
        remaindr(i) <= br_table(remaindr(i + 1),tbit);
do_quotient:
        if i < quot'length generate   
            quot(i) <= q_table(remaindr(i + 1),tbit);
        end generate;
    end generate;

dividend_reg:
    process (clk)
    begin
        if rising_edge(clk) then
            if load = '1' then
                dividendreg <= dividend;
            end if;
        end if;
    end process;

quotient_reg:
    process (clk)
    begin
        if rising_edge (clk) then
            quotient <=  quot;
        end if;
    end process;

remainders:
    process (clk)
    begin
        if rising_edge(clk) then 
            remzero <= '0';
            case remaindr(0) is
                when r0 =>
                    remainder <= "000";
                    remzero <= '1';
                when r1 =>
                    remainder <= "001";
                when r2 =>
                    remainder <= "010";
                when r3 =>
                    remainder <= "011";
                when r4 =>
                    remainder <= "100";
            end case;
        end if;
    end process;

end architecture;

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity divmod5_tb is
end entity;

architecture foo of divmod5_tb is
    constant NBITS:    integer range 0 to 13 := 8;
    signal clk:        std_logic := '0';
    signal dividend:   std_logic_vector (NBITS - 1 downto 0);
    signal load:       std_logic := '0';

    signal quotient:   std_logic_vector (NBITS - 3 downto 0);
    signal remainder:  std_logic_vector (2 downto 0);
    signal remzero:    std_logic;
    signal psample:    std_ulogic;
    signal sample:     std_ulogic;
    signal done:       boolean;
begin
DUT:
    entity work.divmod5
        generic map  (NBITS)
        port map (
            clk => clk,
            dividend => dividend,
            load => load,
            quotient => quotient,
            remainder => remainder,
            remzero => remzero
        );
CLOCK:
    process
    begin
        wait for 5 ns;
        clk <= not clk;
        if done'delayed(30 ns) then
            wait;
        end if;
    end process;
STIMULI:
    process
    begin
        for i in 0 to 2 ** NBITS - 1 loop
            wait for 10 ns;
            dividend <= std_logic_vector(to_unsigned(i,NBITS));
            wait for 10 ns;
            load <= '1';
            wait for 10 ns;
            load <= '0';
        end loop;
        wait for 15 ns;
        done <= true;
        wait;
    end process;

SAMPLER:
    process (clk)
    begin
        if rising_edge(clk) then
            psample <= load;
            sample <= psample after 4 ns;
        end if;
    end process;

MONITOR:
    process (sample)
        variable i:     integer;
        variable div5:  integer;
        variable rem5:  integer;
    begin
        if rising_edge (sample) then
            i := to_integer(unsigned(dividend));
            div5 := i / 5;
            assert div5 = unsigned(quotient)
                report LF & HT &
                    "i = " & integer'image(i) &
                    " div 5 expected " & integer'image(div5) & 
                    " got " & integer'image(to_integer(unsigned(quotient)))
                SEVERITY ERROR;
            rem5 := i mod 5;
            assert rem5 = unsigned(remainder)
                report LF & HT &
                    "i = " & integer'image(i) &
                    " rem 5 expected " & integer'image(rem5) & 
                    " got " & integer'image(to_integer(unsigned(remainder)))
                SEVERITY ERROR;
        end if;
    end process;

end architecture;

这里通过一条generate语句实现,内部的generate语句产生商位。keepdr数组提供状态转换跟踪:

divmod5_tb.png

全部没有算术运算。

也可以在过程中实现而无需所有寄存器利用模式输出的参数。那将接近面试的最少行数。

时钟时序执行将需要位计数器和流控制(JK触发器和几个门)。

时间/复杂度之间的权衡取决于您在面试中还可能需要捍卫的股息规模。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.