HIROBIRO

HIROBIRO

精神的自由を目指すブログ。最近はプログラミングの記事多め。

FPGAでTD4を作成する4(デコーダ、クロック作成)

「CPUの創りかた」という本に載っている4bitCPU「TD4」をFPGAで作成します。
各パーツごとに作成して最後に全体を統合します。
4回目となる今回はデコーダとクロックを作成します。


前回の記事はこちらです。
www.hirobiro-life.com

TD4の作成について


【開発環境】
使用ボード:Basys2 (Xilinx)
ツール:ISE Project Navigator 14.7
シミュレーション:Isim
言語:VHDL


TD4の全体構成をブロック図で表したのが下図です。
今回はFPGAで作りますが、構成は「CPUの創りかた」に合わせていきます。
ちなみに矢印の色が青とオレンジの2色ありますが、色の違いに特に意味はなく、単に見やすくしただけです。


f:id:hirokun1735:20190103213506j:plain

デコーダの機能

デコーダはROMに格納されている命令を指示に変換する回路です。
全部で13ある命令それぞれについて、対応するオペレーションコードや加算器のキャリーフラグを入力とし、データセレクタのセレクト信号やレジスタのLoad信号を出力します。
「CPUの創りかた」ではOR回路4個と3入力NAND回路3個で実現しています。

デコーダのコード

FPGAで作成するにあたって、忠実にOR回路×4とNAND回路×3で作ることもできますが、今回は入出力の真理値表を元にそのままコード化しようと思います。
回路規模は大きくなってしまいますが、コードの可読性は良くなるので忘れたころに見返しても理解しやすいですし、拡張などもしやすいです。
各命令の処理は入出力の真理値表を元にwhen文を使って場合分けしています。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity decorder is
    Port ( OP_CODE : in  STD_LOGIC_VECTOR(3 downto 0);
           C_FLAG : in  STD_LOGIC;
           LOAD : out  STD_LOGIC_VECTOR(3 downto 0);
           SEL_A : out  STD_LOGIC;
			  SEL_B : out  STD_LOGIC);
end decorder;

architecture RTL of decorder is

	signal selecter : STD_LOGIC_VECTOR(1 downto 0);

begin

	SEL_A <= selecter(0);
	SEL_B <= selecter(1);

	--OP_CODE(OP3,OP2,OP1,OP0) 
	--selecter(B,A) LOAD(LOAD3,LOAD2,LODAD1,LOAD0) 
	process (OP_CODE, C_FLAG) begin
		if (OP_CODE = "1110") then
			if (C_FLAG = '0') then
				--JNC(C=0)
				selecter <= "11"; LOAD <= "0111";
			elsif (C_FLAG = '1') then
				--JNC(C=1)
				selecter <= "11"; LOAD <= "1111";
			else
				selecter <= "11"; LOAD <= "1111";
			end if;
		else
			case OP_CODE is
				--ADD A,Im
				when "0000" => selecter <= "00"; LOAD <= "1110";
				--MOV A,B
				when "0001" => selecter <= "01"; LOAD <= "1110";
				--IN A
				when "0010" => selecter <= "10"; LOAD <= "1110";
				--MOV A,Im
				when "0011" => selecter <= "11"; LOAD <= "1110";
				--MOV B,A
				when "0100" => selecter <= "00"; LOAD <= "1101";
				--ADD B,Im
				when "0101" => selecter <= "01"; LOAD <= "1101";
				--IN B
				when "0110" => selecter <= "10"; LOAD <= "1101";
				--MOV B,Im
				when "0111" => selecter <= "11"; LOAD <= "1101";
				--OUT B
				when "1001" => selecter <= "01"; LOAD <= "1011";
				--OUT Im
				when "1011" => selecter <= "11"; LOAD <= "1011";
				--JMP
				when "1111" => selecter <= "11"; LOAD <= "0111";
				
				when others => selecter <= "11"; LOAD <= "1111";
			end case;
		end if;
	end process;

end RTL;

デコーダのシミュレーション

テストベンチのコードが以下になります。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
 
ENTITY decorder_tb IS
END decorder_tb;
 
ARCHITECTURE behavior OF decorder_tb IS 
 
    -- Component Declaration for the Unit Under Test (UUT)
 
    COMPONENT decorder
    Port ( OP_CODE : in  STD_LOGIC_VECTOR(3 downto 0);
           C_FLAG : in  STD_LOGIC;
           LOAD : out  STD_LOGIC_VECTOR(3 downto 0);
           SEL_A : out  STD_LOGIC;
			  SEL_B : out  STD_LOGIC);
    END COMPONENT;
    

   --Inputs
   signal OP_CODE : std_logic_vector(3 downto 0) := (others => '0');
   signal C_FLAG : std_logic := '0';

 	--Outputs
   signal LOAD : std_logic_vector(3 downto 0);
   signal SEL_A : std_logic;
	signal SEL_B : std_logic;
 
BEGIN
 
	-- Instantiate the Unit Under Test (UUT)
   uut: decorder PORT MAP (
          OP_CODE => OP_CODE,
          C_FLAG => C_FLAG,
          LOAD => LOAD,
          SEL_A => SEL_A,
			 SEL_B => SEL_B
        );

   stim_proc: process
   begin		
      OP_CODE <= "1110";
		C_FLAG <= '0';
      wait for 100 ns;
		C_FLAG <= '1';
		wait for 100 ns;
		
		OP_CODE <= "0000";
		wait for 100 ns;
		
		for i in 0 to 15 loop
			OP_CODE <= OP_CODE + 1;
			wait for 100 ns;
		end loop;

      wait;
   end process;

END;


最初キャリーフラグを使ったジャンプ命令を確認し、次にオペレーションコードを表す変数に0000から1111まで入れて出力を確認しています。
命令は全13種類なのでオペレーションコードとして設定していないものもありますが、その場合はセレクタ信号に11、Load信号に1111を入れて何もしないNOP命令を出力します。


f:id:hirokun1735:20190110185527j:plain

クロックについて

続いてクロック関連の部品を作成します。
Basys2には100MHzのクロックがあるのでそれをそのまま使用するなり、分周して周波数を落として使うなりしても良いのですが、今回は回路の動きが分かりやすいようにボタンの入力でクロックを出すことにしました。
ちなみに本物のTD4ではスイッチ入力によるクロックと1Hzまたは10Hzの自動クロックが使用できます。


FPGAで作るTD4ではボタン入力のクロックのみにしました。
その際に問題になるのはチャタリング対策です。
本物のTD4では抵抗とコンデンサを使ったローパスフィルタ回路とシュミッドトリガで対応しています。
FPGAボードを使用する場合はアナログ回路をいじるのが面倒なのでソフトウェアでチャタリング対策をしました。


ボタンを押すと最初チャタリングが発生してオンとオフが連続的に出力されますが、その後チャタリングが収まればオン状態のままになります。
オン状態のままでクロックを一定回数カウントしたらパルスを1回出力するという回路を作成します。
ここで出力されたパルスをクロック信号として他の部品に供給します。

クロックのコード

Basys2は元々100MHzのクロックがあり、クロックのパルス幅は10nsです。
人がボタンを一回押して離した場合、どんなに素早くオンオフしても、たいてい数百μs程度の時間は押してしまいます。


そこで100μsの時間クロック信号が入力されたら1パルスを出力する回路を作成しました。
1クロックで10nsなので、100μs / 10ns = 10000より1万カウントしたらパルスを出力します。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity key_chatter is
    Port ( CLK : in  STD_LOGIC;
           RST : in  STD_LOGIC;
           SW_I : in  STD_LOGIC;
           SW_O : out  STD_LOGIC);
end key_chatter;

architecture RTL of key_chatter is
	signal cnt : integer range 0 to 10000;
	constant ct : integer := 9999;
begin

	process(CLK , RST)
	begin
		if(RST = '1')then
			cnt <= 0;
		elsif(rising_edge(CLK))then
			if(SW_I = '1')then
				if(cnt <= ct)then
					cnt <= cnt + 1;
				end if;
			else
				cnt <= 0;
			end if;
		end if;
	end process;

	SW_O <= '1' when cnt = ct else '0';
	
end RTL;


実はこのコードのようにゲート回路を通したゲーテッドクロックを使用するとErrorにはなりませんがWarningが発生します。
あまり推奨される方法ではないかもしれませんが、今回は手動でクロックを入力させたいためこのような構成にしています。

クロックのシミュレーション

実際はクロック1万カウントで1パルス出力ですが、これをそのままシミュレーションしても1万カウントは多すぎて数えるのが大変です。
とりあえずシミュレーションではクロック3カウントで1パルス出力にしました。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
 
ENTITY key_chatter_tb IS
END key_chatter_tb;
 
ARCHITECTURE behavior OF key_chatter_tb IS 
 
    -- Component Declaration for the Unit Under Test (UUT)
 
    COMPONENT key_chatter
    PORT(
         CLK : IN  std_logic;
         RST : IN  std_logic;
         SW_I : IN  std_logic;
         SW_O : OUT  std_logic
        );
    END COMPONENT;
    

   --Inputs
   signal CLK : std_logic := '0';
   signal RST : std_logic := '0';
   signal SW_I : std_logic := '0';

 	--Outputs
   signal SW_O : std_logic;

   -- Clock period definitions
   constant CLK_period : time := 10 ns;
 
BEGIN
 
	-- Instantiate the Unit Under Test (UUT)
   uut: key_chatter PORT MAP (
          CLK => CLK,
          RST => RST,
          SW_I => SW_I,
          SW_O => SW_O
        );

   -- Clock process definitions
   CLK_process :process
   begin
		CLK <= '0';
		wait for CLK_period/2;
		CLK <= '1';
		wait for CLK_period/2;
   end process;
	
	process
	begin
		wait for 2ns;
		SW_I <= '1';
		wait for 15ns;
		SW_I <= '0';
		wait for 10ns;
		SW_I <= '1';
		wait for 30ns;
		SW_I <= '0';
		wait;
	end process;
END;


f:id:hirokun1735:20190110213814j:plain


SW_Iの一つ目のパルスはクロックの3つ分より短いため出力には変化がありません。
二つ目のパルスはクロックの立ち上がり3つ目が発生する長さを満たしているため出力のSW_Oにパルスが発生しています。

まとめ

今回はデコーダ回路とクロック回路を作成しました。
デコーダ回路では入出力の真理値表を使ってそのままwhen文で場合分けしています。
クロック回路はボタン入力でクロックが発生するようにし、ソフトウェアでチャタリング対策をしています。


TD4で用いる部品はこれで完成しました。
次はここまでで作った回路をひとまとめにしてFPGA版TD4を完成させます。


次の記事はこちらです。
www.hirobiro-life.com