FPGAでTD4を作成する5(全体統合)
「CPUの創りかた」という本に載っている4bitCPU「TD4」をFPGAで作成します。
各パーツごとに作成して最後に全体を統合します。
前回までの記事で個々のパーツを作成したので今回は統合させて完成を目指します。
前回の記事はこちらです。
self-reliance.hatenablog.jp
TD4の作成について
TD4の全体構成をブロック図で表したのが下図です。
今回はFPGAで作りますが、構成は「CPUの創りかた」に合わせていきます。
ちなみに矢印の色が青とオレンジの2色ありますが、色の違いに特に意味はなく、単に見やすくしただけです。
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つのみ記述しています。
リセットスイッチについてですが、プログラムを起動した際に最初リセットスイッチを押して初期値を書き込んでから、クロックスイッチを押してプログラムカウンタを進めていくという仕様にしています。
ここで再度TD4全体のブロック図を載せます。
シミュレーションの結果とブロック図を見ながら意図した通りに動いているか確認します。
TD4の動作の流れ
動作の流れをざっくり箇条書きすると以下のようになります。
② クロックのスイッチ入力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の点灯状態が変化します。
実際のTD4では1Hzのクロックで動作でき、3分15秒を測定してくれます。
しかし今回作ったTD4は手動クロックであるため1秒1回のペースでクロックスイッチを200回近く押す必要があります。
時間計測はともかく、クロックスイッチを押すとLEDの点灯状態が変わることが確認できました。