シンプルな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)
begin
if (RESET = '1') then PC_OUT <= "00000000" ;
elsif ( CLK_EX'event and CLK_EX = '1' ) then
case 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 を設計することをお勧めします.
設計して動作したときの喜びは他に替えがたいものであり,そこで得た自信や経験は,
将来 組み込みシステム 設計等で生かされることでしょう.


=> 「シンプルなCPUを作ってみよう」 トップページに戻る.