VHDLインタビューの質問-数値を5で割り切れるかどうかを検出する


24

VHDLのいいインタビューの質問を見ました-数字を受け取り、残りが5で割れるかどうかを検出するシステムを構築します。私はステートマシンでそれを解決しようとしました(modまたはremを使用したくないと思います)そして私は最初の成功をしましたが(5、10、15のような数、20、40、80のような数が働いた)、130、75などの他の数字は失敗しました。

ステートマシンを表示しますが、それは完全な混乱(コードではなく、図面です)であり、先ほど言ったように、動作していません。

基本的に、私がやろうとしたことは、5で割り切れる2進数で書き留め、それらのために機能する状態マシンを構築することです。

この問題を解決する方法と、このような問題に直面したときの考え方を教えていただければ幸いです。

ありがとうございました!


整数リテラルが5で割り切れるかどうかをテストするためのコードだけではなく、(合成可能な)ハードウェア実装を意味します(テストベンチなど)。
smci

@smci私は実際に状態マシンの回路図/描画を要求していましたが、その状態マシンのコードは傷つきませんでした。Dave Tweedは質問に完璧に答えました。
エラン

次に、タイトルを変更します*「VHDLインタビューの質問-cctが検出された場合...」
smci

ここでegreg math.stackexchange.com/a/2569882/213607で回答すると、より並列的なアプローチのヒントが得られるかもしれません。
mathreadler

回答:


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コードから得られるものです。つまり、状態が現在の剰余を表す場合、次の状態は、状態を1ビット左にシフトし、それに入力ビットを追加し、必要に応じて5を減算したときに得られるものです。
デイブツイード

3
これが美しい場合、誰かがインタビューで自分でこれを思いついたら、私は本当に感銘を受けるでしょう。そして、rem演算子を使用してクロックサイクルごとに完全なベクトルを処理する場合と比較して、合成結果がどのように異なるかについてコメントをお願いします。
キャスパー

8
@zoder状態はmod 5の剰余です。0の矢印はを指し2n mod 5、1の矢印はを指し(2n + 1) mod 5ます。
ホッブズ

2
あなたはの宣言を追加することができstatedinと、Nあなたのコードに?
mkrieger1

15

データがLSBファーストの場合、ステートマシンを設計することもできます。

付録のこの回答の最後に記載されているDFAのグラフィック表現。

このような決定論的有限オートマトン(DFA)の存在は、MSBファーストのDFAを説明する他の回答から直接続きます。DFAで受け入れられる言語は通常のものであり、通常の言語は逆引きで閉じられることが知られているため(例えば、こちらを参照)、次の言語を受け入れるDFAが必要です。

ます。L={w{01}| w10 で割り切れる 5}

建設

  1. Dave Tweedの回答からMSBファーストDFAをコピーします。そのためにオートマトンツールJFLAPを使用しました。

  2. たとえば、CS.SE:DFAの設計とその逆で説明されているように、DFA反転に明示的な変換アルゴリズムを適用ます。この回答の古いリビジョン
    で、このステップの(最小化されていない)結果を見ることができます。

  3. 結果のDFAを最小化します。残念ながら、この機能は最新のJFLAPバージョンでは少しバグが多いため、手作業で最小化することに辞任しました。
    繰り返しますが、それらには多くのアルゴリズムとソースがあります。私はtutorialspoint.comの「DFA Minimization」で説明されているものを使用しました。

    (実際に、DFAを見て十分に訓練されている場合、q 0q 1は、ポイント2で得られたDFAの同等の状態であることが直接わかります。 !)q0q1

実際、結果のオートマトンは正しい答えを提供します。

さまざまな数が「Accept」または「Reject」になるかどうかをリストする2つの列「Input」および「Result」を持つテーブル。


Arev5=QΣδq0FQ={q0q1q2q3q4}Σ={01}F={q0}δ

δq00=q0δq01=q1δq10=q4δq11=q3δq20=q1δq21=q2δq30=q2δq31=q4δq40=q3δq41=q0


DFAの反転が困難な場合は、方程式を逆にすることもできます。new_state= state * 2 + inputの代わりに、(new_state-input)/ 2 = stateを使用してから、stateとnew_stateを入れ替えます。新しい方程式のDFAは、LSBファーストの問題を解決するはずです。
エヤル

Q3とQ4にラベルが付いているのに、その逆ではないのはなぜですか?ラベルq3とq4を交換すると、マシンは「半分(mod 5)&入力ビットを追加」というアルゴリズムを実装します。
ロージーF

2
@RosieF:「半分(mod 5)」というフレーズは、おそらく離散数学に慣れていない人のために、もう少し説明を使用できます。この文脈での分割は3/2(MOD 5)(+ 5 3)/ 2、すなわち4であろうように、塩基の複数が、均一に数分周を行うために必要とされるものは何でも追加することを伴う
supercat

7

(MSBファースト)ステートマシンを作成する1つの方法は次のとおりです。

  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=0S2S+d mod 5 SSdS=04

S=0k=0SS+2kd mod 5kk+1k24=1 mod 5SS+2kd mod 5kk+1 mod 4SkdSkS=04k=03


3

VHDLが何のために書かれているかに応じて、それを直接の組み合わせ計算として記述するアプローチをとることができます。数値を受信すると、1クロックサイクルの間、数値全体がレジスタに格納されます。

たとえば、各ビットが表す値のmod 5を書き留め、これらを足し合わせて、5未満の値が残るまでプロセスを繰り返します。または、いくつかの少数のサイクルでロジックを再利用します。

しかし、VHDL rem演算子を使用する場合、それが正しい答えかもしれません。同社には適切な合成ツールがあると仮定すると、かなり効率的な実装が可能になります-おそらくステートマシンソリューションよりも少し多くの領域がありますが、フルスループットであり、おそらく計算あたりのエネルギーは良好です。これは、実装に要する時間が最も短く、したがって雇用主にとっておそらく最も費用がかからないオプションです!

公平を期すために、それはおそらく彼らがそのような質問で探している答えではありません-しかし、それはまた、実際の設計経験を披露する機会でもあります。


3

数が1ビットよりも大きいチャンクで表される場合、剰余がゼロの場合に計算が15になることを条件に、いくつかの並列計算を使用して剰余mod 15を計算すると役立ちます。mod-15剰余を計算する簡単な方法は、N> = 1の任意の値に対して、左端の4Nビットをそれを超える数の部分に追加すると、元のmod 15に一致する値が得られることを観察することです。使用可能なリソースに応じて、さまざまな方法で問題を細分化できます。

たとえば、32ビット値で始まる場合、8つの4ビット値として扱うことができます。これらをペアで加算して4つの5ビット値を生成し、それらを2つの6ビット値または1つの7ビット値に結合できます。その7ビット値の上位3ビットを下位4ビットに追加すると、最大で21の5ビット値が得られます。したがって、元の値が5の倍数であるかどうかは、最終値が0、5、10、15、または20のいずれか。


...または、4ビット加算器全体を使用し、各キャリーアウトが回路の後半の加算器のキャリーインになることを確認してください。3層の追加の後、単一の4ビット結果と4つの未使用キャリーがあります。最後の4ビット加算と並行して3つのキャリーを一緒に加算し、最後のキャリーをキャリーインとして結果に加算します。これにより最大で19が得られるため、その後20で一致させる必要はありません。
ヘニングマクホルム

@HenningMakholm:加算器を配置して目的の結果を得るには多くの方法があります。特定の状況でどちらのアプローチが優れているかは、プロジェクト固有のルーティングまたはリソース使用率の問題に依存する可能性があります。もう1つの方法は、キャリーセーブ加算器を使用することですが、シフトされた出力の最上位ビットが最下位に移動する可能性があるという事実を利用します。したがって、1つのレイヤーは8つの入力を6、次に6を4、次に4を3、3を2に変えることができます。各レイヤーの1つの出力は単純にANDゲートで、もう1つのXORゲートです。 ... 4ビット値のペア
supercat

...唯一のキャリーチェーンは、4つのxorゲートのチェーンです。出力を19未満にする方が良いかどうか、または可能性のある残余として20をチェックする方が良いかどうかは、おそらくリソースの可用性と使用率に依存します。30以下の数値を指定すると、上限と下限のニブルを追加すると、最大で15(16 + 14-> 1 + 14、または0 + 15-> 0 + 15)の値が得られますが、 (20、25、30)の一部またはすべてのチェックの方が安くなる場合があります。
supercat

2

VHDLを思い出せませんが、最初に思いついたアイデアのスケッチを次に示します。

2の最初の累乗の最後の桁(10を底とする)は1、2、4、8、6、2、...であり、サイクルが繰り返されます。したがって、2のべき乗の剰余mod 5は1、2、4、3、...です。

それを使用して、LSBからビットをシフトし、1ビットが見られるたびに位置に対応する剰余mod 5を累積できます。累積mod 5も行います。最後に合計がゼロかどうかを確認するだけで十分です。


1

ここの答えからのアイデアを使用できます。基数4では、数字が交互の数字の合計が5である場合にのみ5で割り切れるということを導き出すことができます。したがって、私たちは

  1. 数字2を2でグループ化します
  2. 奇数を合計し、偶数の2ビットブロックを減算します。
  3. 結果が例えば[-4,3]のように数ビットの2補数領域にある場合(2つの補数を使用すると仮定すると簡単に確認できます)、終了し、元の数を5で除算できます。 summationは0です。これは、チェックするための非常に単純な論理式です(基本的には、単に大きいだけでなく、結果のすべてのビットについても同じです)。
  4. それ以外の場合は、新しい(はるかに短い数)を繰り返します。

166 =(10)(10)(01)(10):2,2,1,2で試してみましょう

2-2 + 1-2 = -1

これは絶対値が3以下で0ではないため、166が5で均等に分割されていないと1回の繰り返しで結論付けることができます。

小さなメモリは、反復するよりもゲートの速度/ nrの点で安く/良いかもしれません。もちろん、最悪の値(許可された入力が与えられると考えられる最大の結果)を事前に計算し、それに応じて設計を計画することができます。


1

MSBアプローチは間違いなく簡単ですが、MSBソリューションを生成することなくLSB状態図を作成することができました...ほんの数時間かかりました。これは、@ ComFreekで示されているものと同等であることが判明し、注釈が異なります。

2つの数値を追跡します。最初に、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

大丈夫、だから今、私たちは特別な州を開発する必要があります。ほとんどのインタビュアーは、州が1つしかない状態図に感動しないからです。すべての状態に2つの遷移ができたら完了です。

新しい状態に移行するたびに、「NEXT」の各数値が2倍になり、次にモジュロ5になります。「SUM」については、次の規則に従います。

  • 0に沿って遷移した場合、一番上の行はその値を保持します。
  • 1に沿って遷移した場合、各列は古い状態の「SUM」+「NEXT」モジュロ5です。

それでは、着信ビットが1のときに遷移を埋めることから始めましょう。

すべて1

さて、ここでゼロを埋めます。追加された状態は1つだけなので、先に進み、その遷移も埋めます。

コンプリート

そして出来上がり!MSBソリューションを生成することなく、LSBを最初に受け入れる状態マシンがあります。


1

上記のすべてはとても複雑に思えます!2進整数が5で割り切れるかどうかを検出する簡単な数学的な方法があります。最初に、通常の10進数演算で「9をキャスト」する方法を覚えていますか?10進整数の9を法とする剰余は、その桁の合計の9を法とする剰余と同じです。これは、9が基数よりも1つ少ないため機能します。

代替桁の符号が負に設定される「11をキャストする」同様のプロセスがあります。これは、11が基数よりも1つ大きいためです。

したがって、「5をキャスト」したい場合は、整数を基数4で表すことができます。次に、最初の合計として最下位の数字のペアから始め、次の数字のペアからそれを減算して、次の合計を取得します。このように候補整数を調べた後、元の整数が5で割り切れる場合、最終的な合計はゼロまたは5で割り切れます。

例70:01 00 01 10-> 01 00 -1-> 01 01-> 00、5で割り切れる例49:11 00 01-> 11 -1-> 1 00-> 1、NOT 5で割り切れる

累積差の符号のために、および持ち運びがある場合のために、余分なビットを運ぶ必要があることに注意してください。

もう1つの方法は、16進数を追加して15を法とする剰余を取得することです。もちろん、0、5、および10の3つの許容可能な結果を​​識別するための最終論理ステップが必要です。

例70:4 6->A。したがって、70は5で割り切れます(15で割り切れません)。例49:3 1-> 4なので、70は5で割り切れません。

コンピューターロジックでは、2 +/- 1のべき乗のものを実装するのが最も簡単ですが、さまざまな数値ベースを使用して多くの可分性テストを構築できることに注意してください。

10進数演算では、私のお気に入りの1つは剰余mod 7のテストです。100は7の倍数よりも2大きいので、数字をペアにグループ化し(基数100で機能)、単位から数百TWICEを加算します。ここでは、左から右に向かって作業しています...

例:98 76-> 2 72-> 76、したがって9876は7で割り切れません。6mod 7です。例:03 45 67-> 51 67-> 1 69-> 71 1 mod 7。

もちろん、バイナリでは、8進数(3ビットのグループ)の合計を取得します。

申し訳ありませんが、私はVerilogの第一人者でありたいのですが、算数だけがこの人生の段階で提供できます。このような多くのトリックについては、Ron Doerflerの「Dead Reckoning」を参照してください。


カナダのいとこに特別なアルゴリズムがあるのではないかと思います。彼らはカナダのペニーを非合法化したので、すべての価格は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の回答を書くのに十分短いということです。知識とスキルの両方の実証を必要とするインタビューの質問を台無しにすることを心配する必要はありません。インタビューを受けた人は質問されたときに実装を守ることが期待されます。

(llvmバックエンドのバグは、本日前のcommit 1f5df6eで修正されています。)

注目すべきことの1つは、被除数から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;

ここでは、商ビットを生成する内部生成ステートメントである生成ステートメントを使用して実装します。remainingdr配列は、状態遷移トレースを提供します。

divmod5_tb.png

すべて算術演算なし。

また、すべてのレジスタがモードアウトのパラメータを利用せずにプロシージャに実装することもできます。これは、インタビューの最小行数に近づきます。

クロックシーケンシャル実装では、ビットカウンターとフロー制御(JKフリップフロップといくつかのゲート)が必要になります。

面接で弁護するために必要となる可能性のある配当額に応じて、時間と複雑さのトレードオフがあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.