シンプルなCPUを作ってみよう (その3)
−VHDLを用いたコンポーネントの設計−
信州大学工学部 井澤裕司
1.はじめに
前章では, CPUの上の階層(レイア) の設計を行いました.
CPUを構成する要素はすべて, コンポーネント で定義し,それらの接続関係を 内部信号(signal) を用いて表現します.
本章では,そのコンポーネントの設計を VHDL を用いて行います.
いずれのコンポーネントも,極めて単純な構成となっています.
CPU全体の中での役割や,その動作が具体的にイメージできるよう,しっかり学習して下さい.
2.各コンポーネントの設計
以下に,CPUのブロック構成と,各部の信号名を示します.
これらは, 4つのブロック , 8種類の部品(コンポーネント) から構成されています.
第2章で述べたように,すべてのコンポーネントは,以下のような構成となっています.
これらを, VHDL で記述すると,以下のようになります.
ここで,
'xxx' はコンポーネントの名称, 'CLK' はそのクロックです.
architecture RTL of xxx is begin process (CLK) begin if (CLK'event and CLK = '1') then -- 以下,組合せ回路のような表現 component 出力1 <= component 入力の関数 : : component 出力M <= component 入力の関数 -- 組合せ回路の終り enf if; end process; end RTL;
コンポーネントの中で,最も重要なものは 実行ブロック(ALU) です.
第1章では,この機能をC言語を用いて以下のように記述しました.
void main( void ) { 変数の定義 : pc = 0; flag_eq = 0; do{ ir = rom(pc); pc++; switch (opcode(ir)){ // 以下, 各命令コードに対応する処理 case MOV : reg[nRegA(ir)] = reg[nRegB(ir)]; break; case ADD : reg[nRegA(ir)] = reg[nRegA(ir)] + reg[nRegB(ir)]; break; : case JMP : pc = op_addr(ir); break; : default : break ; // 処理の終り } } while (opcode(ir) != HLT); } これを, VHDL で記述すると,以下のようになります.
プログラムカウンタ ( pc )の扱いが,若干異なりますが,基本的には同じような処理をしており,
簡単にそれらの相互変換ができることがわかります.
architecture RTL of xxx is begin process (CLK) begin if (CLK'event and CLK = '1') then case OP_CODE is when "0000" => -- MOV REG_IN <= REG_B; : PC_OUT <= PC_IN + 1; when "0001" => -- ADD REG_IN <= REG_A + REG_B; : PC_OUT <= PC_IN + 1; : when "1100" => -- JMP PC_OUT <= OP_DATA; : when others => end case ; enf if; end process; end RTL;
以下,各ブロックのコンポーネントの詳細について説明します.
3. クロックブロック
クロックブロックは,1つのコンポーネントから構成されています.
入 力 基本クロック CLK 出 力 @フェッチ,Aデコード,B実行,Cライトバックの各ブロック
に供給する4つのクロック信号
CLK_FT , CLK_DC , CLK_EX , CLK_WBクロック 基本クロック CLK
以下に,そのソースコードを示します.clk_gen.vhd
-- clk_gen.vhd
-- Y.Izawa
-- H18.3.27
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
entity clk_gen is port ( CLK : in std_logic ; CLK_FT : out std_logic ; CLK_DC : out std_logic ; CLK_EX : out std_logic ; CLK_WB : out std_logic ); end clk_gen; architecture RTL of clk_gen is
signal COUNT : std_logic_vector (1 downto 0);
begin process (CLK) begin if (CLK'event and CLK = '1') then COUNT <= COUNT + 1; case COUNT is when "00" => CLK_FT <= '1'; CLK_DC <= '0'; CLK_EX <= '0'; CLK_WB <= '0'; when "01" => CLK_FT <= '0'; CLK_DC <= '1'; CLK_EX <= '0'; CLK_WB <= '0'; when "10" => CLK_FT <= '0'; CLK_DC <= '0'; CLK_EX <= '1'; CLK_WB <= '0'; when "11" => CLK_FT <= '0'; CLK_DC <= '0'; CLK_EX <= '0'; CLK_WB <= '1'; when others => end case ; end if ; end process ; end RTL;
4. FETCHブロック
フェッチブロックは,1つのコンポーネントから構成されています.
入 力 プログラムカウンタ(PC)の出力 P_COUNT (8bit) 出 力 カウンタをアドレスとするProgram ROMのデータ PROM_OUT (15bit) クロック 第1相のフェッチクロック CLK_FT 以下に,そのソースコードを示します.
fetch.vhd
-- fetch.vhd
-- Y.Izawa
-- H18.3.27
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
entity fetch is port ( CLK_FT : in std_logic ; P_COUNT : in std_logic_vector (7 downto 0); PROM_OUT : out std_logic_vector (14 downto 0) ); end fetch; architecture BEHAVIOR of fetch is
subtype WORD is std_logic_vector (14 downto 0);
type MEMORY is array (0 to 15) of WORD;
constant MEM : MEMORY := (
"100000000000000", -- ldl Reg0 0 "100000100000001", -- ldl Reg1 1 "100001000000000", -- ldl Reg2 0 "100001100001010", -- ldl Reg3 10 "000101000100000", -- add Reg2 Reg1 "000100001000000", -- add Reg0 Reg2 "101001001100000", -- cmp Reg2 Reg3 "101100000001001", -- je 9 "110000000000100", -- jmp 4 "111000001000000", -- st Reg0 64 "111100000000000", -- hlt "000000000000000", -- nop "000000000000000", -- nop "000000000000000", -- nop "000000000000000", -- nop "000000000000000" -- nop ) ;
begin READ_OP: process (CLK_FT) begin if (CLK_FT'event and CLK_FT = '1') then PROM_OUT <= MEM(conv_integer(P_COUNT(7 downto 0))); end if; end process ; end BEHAVIOR;
5. DECODEブロック
デコードブロックは,3つのコンポーネントから構成されています.5.1 decode.vhd
以下,それらの入出力とソースコードを順に示します.
入 力 ラッチされたProgram ROMのデータ出力 PROM_OUT (15bit) 出 力 オペコード OP_CODE (4bit) およびデータ(アドレス)のオペランド OP_DATA (8bit) クロック 第2相のデコードクロック CLK_DC
-- decode.vhd
-- Y.Izawa
-- H18.3.27
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
entity decode is port ( CLK_DC : in std_logic ; PROM_OUT : in std_logic_vector (14 downto 0); OP_CODE : out std_logic_vector (3 downto 0); OP_DATA : out std_logic_vector (7 downto 0) ); end decode; architecture RTL of decode is
begin process (CLK_DC) begin if (CLK_DC'event and CLK_DC = '1') then OP_CODE <= PROM_OUT(14 downto 11); OP_DATA <= PROM_OUT(7 downto 0); end if; end process ; end RTL;
5.2 reg_dc.vhd
入 力 Program ROMが示すレジスタA,Bの番号 N_REG_IN (3bit)
8個のレジスタ出力 REG_0〜7 (16bit)出 力 レジスタA,Bの番号 N_REG_OUT (3bit)
レジスタA,Bの番号 に対応する レジスタのデータ REG_OUT (16bit)クロック 第2相のデコードクロック CLK_DC
-- reg_dc.vhd
-- Y.Izawa
-- H18.3.27
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
entity reg_dc is port ( CLK_DC : in std_logic; REG_0 : in std_logic_vector (15 downto 0); REG_1 : in std_logic_vector (15 downto 0); REG_2 : in std_logic_vector (15 downto 0); REG_3 : in std_logic_vector (15 downto 0); REG_4 : in std_logic_vector (15 downto 0); REG_5 : in std_logic_vector (15 downto 0); REG_6 : in std_logic_vector (15 downto 0); REG_7 : in std_logic_vector (15 downto 0); N_REG_IN : in std_logic_vector (2 downto 0); N_REG_OUT : out std_logic_vector (2 downto 0); REG_OUT : out std_logic_vector (15 downto 0) ); end reg_dc; architecture RTL of reg_dc is
begin process (CLK_DC) begin if (CLK_DC'event and CLK_DC = '1') then case N_REG_IN is when "000" => REG_OUT <= REG_0; when "001" => REG_OUT <= REG_1; when "010" => REG_OUT <= REG_2; when "011" => REG_OUT <= REG_3; when "100" => REG_OUT <= REG_4; when "101" => REG_OUT <= REG_5; when "110" => REG_OUT <= REG_6; when "111" => REG_OUT <= REG_7; when others => end case ; N_REG_OUT <= N_REG_IN; end if ; end process ; end RTL;
5.3 ram_dc.vhd
入 力 Program ROMが示すRAMのアドレス RAM_ADDR (8bit)
8個のRAM出力 RAM_0〜7 (16bit)
メモリマップドI/Oの入力データ IO65_IN (16bit)出 力 アドレスで指定された RAMのデータ RAM_OUT (16bit) クロック 第2相のデコードクロック CLK_DC
-- ram_dc.vhd
-- Y.Izawa
-- H18.3.27
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
entity ram_dc is port ( CLK_DC : in std_logic; RAM_0 : in std_logic_vector (15 downto 0); RAM_1 : in std_logic_vector (15 downto 0); RAM_2 : in std_logic_vector (15 downto 0); RAM_3 : in std_logic_vector (15 downto 0); RAM_4 : in std_logic_vector (15 downto 0); RAM_5 : in std_logic_vector (15 downto 0); RAM_6 : in std_logic_vector (15 downto 0); RAM_7 : in std_logic_vector (15 downto 0); IO65_IN : in std_logic_vector (15 downto 0); RAM_ADDR : in std_logic_vector (7 downto 0); RAM_OUT : out std_logic_vector (15 downto 0) ); end ram_dc; architecture RTL of ram_dc is
begin process (CLK_DC) begin if (CLK_DC'event and CLK_DC = '1') then case RAM_ADDR is when "00000000" => RAM_OUT <= RAM_0; when "00000001" => RAM_OUT <= RAM_1; when "00000010" => RAM_OUT <= RAM_2; when "00000011" => RAM_OUT <= RAM_3; when "00000100" => RAM_OUT <= RAM_4; when "00000101" => RAM_OUT <= RAM_5; when "00000110" => RAM_OUT <= RAM_6; when "00000111" => RAM_OUT <= RAM_7; when "01000001" => RAM_OUT <= IO65_IN; when others => end case ; end if ; end process ; end RTL;
6. EXECブロック
実行(EXEC)ブロックは,1つのコンポーネントから構成されています.
入 力 システムリセット入力 RESET
プログラムカウンタ(PC) PC_IN (8bit)
オペコード OP_CODE (4bit) およびデータ(アドレス)のオペランド OP_DATA (8bit)
レジスタ指定オペランド(3bit) に対応するレジスタのデータ REG_A (16bit), REG_B (16bit)
アドレスオペランド(8bit) に対応するRAMのデータ RAM_OUT (16bit)出 力 プログラムカウンタ(PC) PC_OUT (8bit)
レジスタの演算結果 REG_IN (16bit)
レジスタへの書き込み許可信号 REG_WEN
アドレスオペランド(8bit) に対応するRAMのデータ RAM_IN (16bit)
RAMへの書き込み許可信号 RAM_WEN
クロック 第3相の実行クロック CLK_EX
以下に,そのソースコードを示します.
exec.vhd
-- exec.vhd
-- Y.Izawa
-- H18.3.27
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
entity exec is port ( CLK_EX : in std_logic; RESET : in std_logic; OP_CODE : in std_logic_vector (3 downto 0); PC_IN : in std_logic_vector (7 downto 0); REG_A : in std_logic_vector (15 downto 0); REG_B : in std_logic_vector (15 downto 0); OP_DATA : in std_logic_vector (7 downto 0); RAM_OUT : in std_logic_vector (15 downto 0); PC_OUT : out std_logic_vector (7 downto 0); REG_IN : out std_logic_vector (15 downto 0); RAM_IN : out std_logic_vector (15 downto 0); REG_WEN : out std_logic ; RAM_WEN : out std_logic ); end exec; architecture RTL of exec is
signal CMP_FLAG : std_logic;
begin process (CLK_EX, RESET)
beginif (RESET = '1') then PC_OUT <= "00000000" ;
elsif ( CLK_EX'event and CLK_EX = '1' ) thencase OP_CODE is when "0000" => -- MOV REG_IN <= REG_B;
REG_WEN <= '1';
RAM_WEN <= '0';
PC_OUT <= PC_IN + 1;
when "0001" =>
-- ADD REG_IN <= REG_A + REG_B;
REG_WEN <= '1';
RAM_WEN <= '0';
PC_OUT <= PC_IN + 1;
when "0010" =>
-- SUB REG_IN <= REG_A - REG_B;
REG_WEN <= '1';
RAM_WEN <= '0';
PC_OUT <= PC_IN + 1;
when "0011" =>
-- AND REG_IN <= REG_A and REG_B;
REG_WEN <= '1';
RAM_WEN <= '0';
PC_OUT <= PC_IN + 1;when "0100" =>
-- OR REG_IN <= REG_A or REG_B;
REG_WEN <= '1';
RAM_WEN <= '0';
PC_OUT <= PC_IN + 1;
when "0101" =>
-- SL REG_IN <= REG_A(14 downto 0) & '0';
REG_WEN <= '1';
RAM_WEN <= '0';
PC_OUT <= PC_IN + 1;
when "0110" =>
-- SR REG_IN <= '0' & REG_A(15 downto 1);
REG_WEN <= '1';
RAM_WEN <= '0';
PC_OUT <= PC_IN + 1;
when "0111" =>
-- SRA REG_IN <= REG_A(15) & REG_A(15 downto 1);
REG_WEN <= '1';
RAM_WEN <= '0';
PC_OUT <= PC_IN + 1;
when "1000" =>
-- LDL REG_IN <= REG_A(15 downto 8) & OP_DATA;
REG_WEN <= '1';
RAM_WEN <= '0';
PC_OUT <= PC_IN + 1;
when "1001" =>
-- LDH REG_IN <= OP_DATA & REG_A(7 downto 0);
REG_WEN <= '1';
RAM_WEN <= '0';
PC_OUT <= PC_IN + 1;
when "1010" =>
-- CMP if (REG_A = REG_B) then
CMP_FLAG <= '1';
else
CMP_FLAG <= '0';
end if
REG_WEN <= '0';
RAM_WEN <= '0';
PC_OUT <= PC_IN + 1;when "1011" =>
-- JE REG_WEN <= '0';
RAM_WEN <= '0';
if (CMP_FLAG = '1') then
PC_OUT <= OP_DATA ;
else
PC_OUT <= PC_IN + 1;
end if ;
when "1100" =>
-- JMP REG_WEN <= '0';
RAM_WEN <= '0';
PC_OUT <= OP_DATA ;when "1101" =>
-- LD REG_IN <= RAM_OUT;
REG_WEN <= '1';
RAM_WEN <= '0';
PC_OUT <= PC_IN + 1;when "1110" =>
-- ST RAM_IN <= REG_A;
REG_WEN <= '0';
RAM_WEN <= '1';
PC_OUT <= PC_IN + 1;when "1111" =>
-- HLT when others => end case ; end if ; end process ; end RTL;
7. ライトバックブロック
ライトバックブロック は,2つの コンポーネント から構成されています.
以下,それらの ソースコード を示します.
7.1 reg_wb.vhd
入 力 レジスタオペランド(3 bit) で指定したレジスタの番号 N_REG (3 bit)
レジスタの演算結果 REG_IN (16 bit)
レジスタへの書き込み許可信号 REG_WEN
出 力 8 個のレジスタのデータ REG_0〜7 (16 bit) クロック 第4相のライトバッククロック CLK_WB
-- reg_wb.vhd
-- Y.Izawa
-- H18.3.27
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
entity reg_wb is port ( CLK_WB : in std_logic ; N_REG : in std_logic_vector (2 downto 0); REG_IN : in std_logic_vector (15 downto 0); REG_WEN : in std_logic; REG_0WB : out std_logic_vector (15 downto 0); REG_1WB : out std_logic_vector (15 downto 0); REG_2WB : out std_logic_vector (15 downto 0); REG_3WB : out std_logic_vector (15 downto 0); REG_4WB : out std_logic_vector (15 downto 0); REG_5WB : out std_logic_vector (15 downto 0); REG_6WB : out std_logic_vector (15 downto 0); REG_7WB : out std_logic_vector (15 downto 0) ); end reg_wb; architecture RTL of reg_wb is
begin process (CLK_WB) begin if (CLK_WB'event and CLK_WB = '1') then if (REG_WEN = '1') then case N_REG is when "000" => REG_0WB <= REG_IN; -- REG_0 when "001" => REG_1WB <= REG_IN; -- REG_1 when "010" => REG_2WB <= REG_IN; -- REG_2 when "011" => REG_3WB <= REG_IN; -- REG_3 when "100" => REG_4WB <= REG_IN; -- REG_4 when "101" => REG_5WB <= REG_IN; -- REG_5 when "110" => REG_6WB <= REG_IN; -- REG_6 when "111" => REG_7WB <= REG_IN; -- REG_7 when others => end case ; end if ; end if ; end process ; end RTL;
7.2 ram_wb.vhd
入 力 アドレスオペランド(8 bit) で指定したRAMアドレス RAM_ADDR (8 bit)
選択された RAM のデータ RAM_IN (16 bit)
RAM への書き込み許可信号 RAM_WEN
出 力 RAM のデータ RAM_0〜7 (16 bit) (8種類)
メモリマップドI/Oの出力データ IO64_OUT (16 bit)クロック 第4相のライトバッククロック CLK_WB
-- ram_wb.vhd
-- Y.Izawa
-- H18.3.27
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
entity ram_wb is port ( CLK_WB : in std_logic ; RAM_ADDR : in std_logic_vector (7 downto 0); RAM_IN : in std_logic_vector (15 downto 0); RAM_WEN : in std_logic; RAM_0 : out std_logic_vector (15 downto 0); RAM_1 : out std_logic_vector (15 downto 0); RAM_2 : out std_logic_vector (15 downto 0); RAM_3 : out std_logic_vector (15 downto 0); RAM_4 : out std_logic_vector (15 downto 0); RAM_5 : out std_logic_vector (15 downto 0); RAM_6 : out std_logic_vector (15 downto 0); RAM_7 : out std_logic_vector (15 downto 0); IO64_OUT : out std_logic_vector (15 downto 0) ); end ram_wb; architecture RTL of ram_wb is
begin process (CLK_WB) begin if (CLK_WB'event and CLK_WB = '1') then if (RAM_WEN = '1') then case RAM_ADDR is when "00000000" => RAM_0 <= RAM_IN; -- RAM_0 when "00000001" => RAM_1 <= RAM_IN; -- RAM_1 when "00000010" => RAM_2 <= RAM_IN; -- RAM_2 when "00000011" => RAM_3 <= RAM_IN; -- RAM_3 when "00000100" => RAM_4 <= RAM_IN; -- RAM_4 when "00000101" => RAM_5 <= RAM_IN; -- RAM_5 when "00000110" => RAM_6 <= RAM_IN; -- RAM_6 when "00000111" => RAM_7 <= RAM_IN; -- RAM_7 when "01000000" => IO64_OUT <= RAM_IN; -- IO_64 when others => end case ; end if ; end if ; end process ; end RTL;
以上, VHDLコンポーネント の内容について,紹介しました.
不明な点が少しでも残っている場合は, 第1章 と 第2章 に戻って,
C言語の 「CPUシミュレータ」 や, 上位階層のVHDL のソースコードをもう一度注意深く読み返して下さい.
8. まとめ
このコンテンツでは,シンプルな CPUの設計法 について解説してきました.
説明の内容は,理解できましたでしょうか?
誤りや分かり難い点について,率直に指摘していただけると有難いです.
また,不明な点は遠慮なく質問して下さい.
繰り返し述べているように,今回は 説明の容易さ や 部品の共用化 を図るため,メモリを中心に
回路の大幅な 簡略化 を行っています.
例えば,フェッチブロックの PROM の内容を,VHDLの中で記述していますが,外部の ファイル から
読み込むのが一般的な方法です.
また, RAM については,レジスタと同じ構成とするため,複数の D型フリップフロップ を用いました.
すなわち,ライトバックブロックの出力をデコードブロックで選択する構成となっており,
メモリサイズが増えると 対応できません.
一般には, VHDLの配列(ARRAY) や,FPGA開発ツールの メガファンクション 等の機能を使用します.
配列 を用いた場合,そのインデックスが RAMのアドレス に対応します.
また,フェッチブロックのアドレス出力を直接 RAMのアドレス に入力し,RAMへの書き込みは,
ライトバックのフェーズで行います.
これにより,コンポーネント間の信号の受け渡しが 単純化 され, 大容量化 にも対応できます.
それらの詳細については, 第4章 で説明します.
一方, CPUの高速化手法 を導入することもできます.
例えば,各ブロックのタイミングを調整することにより, 「パイプライン処理」 が実現可能です.
このような本格的な CPUの設計法 については, 第5章 で説明します.
本資料を活用して, オリジナルのCPU を設計することをお勧めします.
設計して動作したときの喜びは他に替えがたいものであり,そこで得た自信や経験は,
将来 組み込みシステム の 設計等で生かされることでしょう.