HIROBIRO

HIROBIRO

金銭的・精神的自由を目指すブログ。

FPGAでTD4を作成する5(全体統合)


CPUの創りかた」という本に載っている4bitCPU「TD4」をFPGAで作成します。
各パーツごとに作成して最後に全体を統合します。
前回までの記事で個々のパーツを作成したので今回は統合させて完成を目指します。


前回の記事はこちらです。
self-reliance.hatenablog.jp

TD4の作成について


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


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


f:id:hirokun1735:20190103213506j:plain

I/Oファイルの作成

Basys2の各スイッチやLEDなどの入出力機能を使用するにはI/Oファイルを作成する必要があります。
TD4を作成するには入力として発振回路、Pushスイッチ、スライドスイッチを使用し、出力はLEDのみです。
下記のコードで「クロックスイッチ」とありますが、これはPushスイッチです。
詳細なコードは前回の記事に書きましたが、ボタンを押すたびにクロックが1パルス入力されます。

#100MHz RC発振回路
NET "CLK"  	LOC = B8   ;

#Pushスイッチ
NET "RST"  	LOC = G12  ;

#LED
NET "LED[0]"  	LOC = M5   ;
NET "LED[1]"  	LOC = M11  ;
NET "LED[2]"  	LOC = P7   ;
NET "LED[3]"  	LOC = P6   ;

#クロックスイッチ
NET "CLK_SW"  		LOC = M4   ;
#スライドスイッチ
NET "SL_SW[0]"  		LOC = P11   ;
NET "SL_SW[1]"  		LOC = L3   ;
NET "SL_SW[2]"  		LOC = K3   ;
NET "SL_SW[3]"  		LOC = B4   ;

TD4のトップモジュールの作成

TD4に必要な機能は個々のパーツで全て完成しているため、あとはこれらを一つに統合するだけです。
ALUを作成したときと同様に階層設計にします。
トップモジュールの中に個々のパーツを下位モジュールとして組み込みます。

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

entity TD4_top is
	Port ( CLK : in  STD_LOGIC;
          RST : in  STD_LOGIC;
          CLK_SW : in  STD_LOGIC;
          SL_SW : in  STD_LOGIC_VECTOR(3 downto 0);
			 LED : out  STD_LOGIC_VECTOR(3 downto 0));
end TD4_top;

architecture RTL of TD4_top is

component register4
    Port ( CLK : in  STD_LOGIC;
           RST : in  STD_LOGIC;
           LOAD : in  STD_LOGIC_VECTOR(3 downto 0);
           IN_DATA : in  STD_LOGIC_VECTOR(3 downto 0);
           OUT_A : out  STD_LOGIC_VECTOR(3 downto 0);
           OUT_B : out  STD_LOGIC_VECTOR(3 downto 0);
           OUT_LD : out  STD_LOGIC_VECTOR(3 downto 0);
           ADDRESS : out  STD_LOGIC_VECTOR(3 downto 0));
end component;

component data_selector
    Port ( IN_A : in  STD_LOGIC_VECTOR(3 downto 0);
           IN_B : in  STD_LOGIC_VECTOR(3 downto 0);
           IN_SW : in  STD_LOGIC_VECTOR(3 downto 0);
           SEL_A : in  STD_LOGIC;
           SEL_B : in  STD_LOGIC;
           OUT_Y : out  STD_LOGIC_VECTOR(3 downto 0));
end component;

component ALU
    Port ( CLK : in  STD_LOGIC;
           RST : in  STD_LOGIC;
           IN_Y : in  STD_LOGIC_VECTOR(3 downto 0);
           ROM_DATA : in  STD_LOGIC_VECTOR(3 downto 0);
           OUT_DATA : out  STD_LOGIC_VECTOR(3 downto 0);
           C_FLAG : out  STD_LOGIC);
end component;

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;

component rom_16byte 
    Port ( ADDRESS : in  STD_LOGIC_VECTOR(3 downto 0);
           ROM_DATA : out  STD_LOGIC_VECTOR(3 downto 0);
			  OP_CODE : out  STD_LOGIC_VECTOR(3 downto 0));
end component;

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

	signal clk_o : STD_LOGIC;
	signal load : STD_LOGIC_VECTOR(3 downto 0);
	signal data_AluToRegister : STD_LOGIC_VECTOR(3 downto 0);
	signal a_register : STD_LOGIC_VECTOR(3 downto 0);
	signal b_register : STD_LOGIC_VECTOR(3 downto 0);
	signal address : STD_LOGIC_VECTOR(3 downto 0);
	signal sel_a : STD_LOGIC;
	signal sel_b : STD_LOGIC;
	signal data_SelecterToAlu : STD_LOGIC_VECTOR(3 downto 0);
	signal rom_data : STD_LOGIC_VECTOR(3 downto 0);
	signal op_code : STD_LOGIC_VECTOR(3 downto 0);
	signal c_flag : STD_LOGIC;

begin

	U0 : register4 port map(clk_o, rst, load, data_AluToRegister, a_register, b_register, led, address);
	U1 : data_selector port map(a_register, b_register, sl_sw, sel_a, sel_b, data_SelecterToAlu);
	U2 : ALU port map(clk_o, rst, data_SelecterToAlu, rom_data, data_AluToRegister, c_flag);
	U3 : decorder port map(op_code, c_flag, load, sel_a, sel_b);
	U4 : rom_16byte port map(address, rom_data, op_code);
	U5 : key_chatter port map(clk, rst, clk_sw, clk_o);

end RTL;

TD4のトップモジュールのシミュレーション

テストベンチのコードは以下になります。
ちなみにスイッチのチャタリング対策として、実際はクロックのスイッチを押し続けた時間が100ms以上(クロックの立ち上がり10000回分)にならないと他のパーツにクロック信号が出力されない仕組みにしています。
しかしシミュレーション上ではクロックの立ち上がり2回分でクロック信号が出力されるようにしました。

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
 
ENTITY TD4_top_tb IS
END TD4_top_tb;
 
ARCHITECTURE behavior OF TD4_top_tb IS 
 
    -- Component Declaration for the Unit Under Test (UUT)
 
    COMPONENT TD4_top
    PORT(
         CLK : IN  std_logic;
         RST : IN  std_logic;
         CLK_SW : IN  std_logic;
         SL_SW : IN  std_logic_vector(3 downto 0);
         LED : OUT  std_logic_vector(3 downto 0)
        );
    END COMPONENT;
	
	component register4
    Port ( CLK : in  STD_LOGIC;
           RST : in  STD_LOGIC;
           LOAD : in  STD_LOGIC_VECTOR(3 downto 0);
           IN_DATA : in  STD_LOGIC_VECTOR(3 downto 0);
           OUT_A : out  STD_LOGIC_VECTOR(3 downto 0);
           OUT_B : out  STD_LOGIC_VECTOR(3 downto 0);
           OUT_LD : out  STD_LOGIC_VECTOR(3 downto 0);
           ADDRESS : out  STD_LOGIC_VECTOR(3 downto 0));
end component;

component data_selector
    Port ( IN_A : in  STD_LOGIC_VECTOR(3 downto 0);
           IN_B : in  STD_LOGIC_VECTOR(3 downto 0);
           IN_SW : in  STD_LOGIC_VECTOR(3 downto 0);
           SEL_A : in  STD_LOGIC;
           SEL_B : in  STD_LOGIC;
           OUT_Y : out  STD_LOGIC_VECTOR(3 downto 0));
end component;

component ALU
    Port ( CLK : in  STD_LOGIC;
           RST : in  STD_LOGIC;
           IN_Y : in  STD_LOGIC_VECTOR(3 downto 0);
           ROM_DATA : in  STD_LOGIC_VECTOR(3 downto 0);
           OUT_DATA : out  STD_LOGIC_VECTOR(3 downto 0);
           C_FLAG : out  STD_LOGIC);
end component;

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;

component rom_16byte 
    Port ( ADDRESS : in  STD_LOGIC_VECTOR(3 downto 0);
           ROM_DATA : out  STD_LOGIC_VECTOR(3 downto 0);
			  OP_CODE : out  STD_LOGIC_VECTOR(3 downto 0));
end component;

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

   signal CLK : std_logic := '0';
   signal RST : std_logic := '0';
   signal CLK_SW : std_logic := '0';
	signal clk_o : STD_LOGIC;
	signal address : STD_LOGIC_VECTOR(3 downto 0);
	signal rom_data : STD_LOGIC_VECTOR(3 downto 0);
	signal op_code : STD_LOGIC_VECTOR(3 downto 0);
	signal load : STD_LOGIC_VECTOR(3 downto 0);
	signal sel_a : STD_LOGIC;
	signal sel_b : STD_LOGIC;
	signal data_SelecterToAlu : STD_LOGIC_VECTOR(3 downto 0);
	signal data_AluToRegister : STD_LOGIC_VECTOR(3 downto 0);
	signal c_flag : STD_LOGIC;
	signal a_register : STD_LOGIC_VECTOR(3 downto 0);
	signal b_register : STD_LOGIC_VECTOR(3 downto 0);
   signal SL_SW : std_logic_vector(3 downto 0) := (others => '0');

 	--Outputs
   signal LED : std_logic_vector(3 downto 0);

   -- Clock period definitions
   constant CLK_period : time := 100 ns;
   constant CLK_SW_period : time := 300 ns;
 
BEGIN
 
	-- Instantiate the Unit Under Test (UUT)
   uut: TD4_top PORT MAP (
          CLK => CLK,
          RST => RST,
          CLK_SW => CLK_SW,
          SL_SW => SL_SW,
          LED => LED
        );
	
	U0 : register4 port map(clk_o, rst, load, data_AluToRegister, a_register, b_register, led, address);
	U1 : data_selector port map(a_register, b_register, sl_sw, sel_a, sel_b, data_SelecterToAlu);
	U2 : ALU port map(clk_o, rst, data_SelecterToAlu, rom_data, data_AluToRegister, c_flag);
	U3 : decorder port map(op_code, c_flag, load, sel_a, sel_b);
	U4 : rom_16byte port map(address, rom_data, op_code);
	U5 : key_chatter port map(clk, rst, clk_sw, clk_o);

   -- Clock process definitions
   CLK_process :process
   begin
		CLK <= '0';
		wait for CLK_period/2;
		CLK <= '1';
		wait for CLK_period/2;
   end process; 

   -- Stimulus process
   stim_proc: process
   begin		
      wait for 10 ns;	
		CLK_SW <= '1';
      wait for CLK_SW_period/2;
		CLK_SW <= '0';
		wait for CLK_SW_period/2;
   end process;
	
	process
	begin
		wait for 100 ns;
		RST <= '1';
		wait for 200 ns;
		RST <= '0';
		wait;
	end process;

END;


テストベンチのコードではBasys2に内蔵している発振回路のクロック信号、Pushスイッチでのクロック入力、リセットスイッチの入力の3つのみ記述しています。
リセットスイッチについてですが、プログラムを起動した際に最初リセットスイッチを押して初期値を書き込んでから、クロックスイッチを押してプログラムカウンタを進めていくという仕様にしています。


f:id:hirokun1735:20190112161041j:plain


ここで再度TD4全体のブロック図を載せます。
シミュレーションの結果とブロック図を見ながら意図した通りに動いているか確認します。


f:id:hirokun1735:20190103213506j:plain

TD4の動作の流れ

動作の流れをざっくり箇条書きすると以下のようになります。


① 最初にリセット入力(rst)が入ると各信号に初期値を代入
② クロックのスイッチ入力clk_swがclkの立ち上がり2回分だけ入力されるとレジスタやALUにクロック(clk_o)が出力
③ プログラムカウンタから出力される信号(address)でROMのアドレスを指定
④ 指定されたアドレスに格納されているデータを出力(rom_dataとop_code)
デコーダは入力されたop_codeを元に各レジスタにLoad信号を出力し、データセレクタにセレクト信号(sel_a, sel_b)を出力
⑥ ALUはデータセレクタから入力される信号とrom_dataを加算してレジスタに結果を出力
⑦ Load信号で指定された(対応するビットが0となる)レジスタはALUから送られた入力を出力する
⑧ 次のクロックでプログラムカウンタを+1する(ジャンプ命令の場合は指定されたアドレスを入力)
⑨ ③に戻る


プログラムをBasys2にダウンロードして実機で確認しました。
一つ注意点ですが、このままダウンロードしようとすると回路で使用していない箇所があるというWarningが発生します。
この原因はTD4で動かすプログラムをROMのコード上に直接書いてしまっているためです。


スライドスイッチを使用するプログラムで無ければ当然スライドスイッチに関する記述は不要であるためWarningで教えてくれるのです。
こういうWarningであれば無視して構いません。


以下は「CPUの創りかた」に載っているラーメンタイマーのプログラムを起動したときの様子です。
このプログラムは約3分を測定するというアプリですが、残り時間によってLEDの点灯状態が変化します。


f:id:hirokun1735:20190112171509j:plain

実際のTD4では1Hzのクロックで動作でき、3分15秒を測定してくれます。
しかし今回作ったTD4は手動クロックであるため1秒1回のペースでクロックスイッチを200回近く押す必要があります。
時間計測はともかく、クロックスイッチを押すとLEDの点灯状態が変わることが確認できました。

まとめ

今回はこれまで作った個々のパーツを統合してTD4を完成させました。
実機でもうまく動作できることが確認できました。
実際のTD4の場合はうまく動作しないときはテスターを用いて細かくチェックする必要がありますが、FPGAを使用した場合はソフト上でシミュレーションできますし修正も容易なのでデバッグしやすいです。