第11回: 実習(6): FPGAへのCPUの実装(1)

前回概略を設計したCPUを、順番に作っていくことにします。 まず今回は、CPUの骨格とメモリとをつくってみます。

全体構成

全体の動作は、クロックclkに同期して(=clkの立ち上がりで)行います。 若干天下り的ですが、全体構成を以下のようにすることにしてみます。

まずsw_clkを含め、回路(インスタンス)の接続関係を図で描いて整理しておきましょう。

プロジェクトの作成

まずは実習(1)の手順を参考に、新規にプロジェクトcpu_topを つくっておきます。 そしてここに、順にmem, cpu等を新規作成で追加していくことになります。

メモリmemの設計

まずは一番単純なメモリmemから設計してみます。 今回はメモリは読み出し専用ですから、入力として与えるアドレスaddrに応じて、 そこの値dataを出力する回路、ということで、 デコーダと同様の回路となります。

例えばテスト用に次のような「プログラム」を保持するメモリを設計してみましょう。
アドレス内容命令
00000 0001mov 1, r0
10010 0010add r0, 2, r0
20110 0010jmp 2
第10回資料の命令の定義を適宜参照のこと。

※ヒント: 例えばaddr=0を与えられたら、data="0000 0001"を出力する回路、ということになる

以下、ひな形:

entity mem is
  port( addr: in std_logic_vector(3 downto 0);
        data: out std_logic_vector(7 downto 0));
end mem;

architecture Behavioral of mem is
begin
...
end Behavioral;

CPUの骨格の設計

CPU本体は、以下のような入出力をもつことになります。 このほか、デバッグや内部動作の確認用に、次のような出力を 設けておくとよいでしょう。 これらのエンティティ記述で入出力を定義したVHDLファイルの骨格を まずは作っておきましょう。

(1日目はこのあたりまでを目安にしておくとよいでしょう)


CPUの詳細設計



CPUの内部変数の定義

CPU内部で持つべきレジスタ(信号、変数)は、以下のようなものがあります。 このうち、制御回路ステートマシンの状態変数stは、次のように定義します。 まずst=0で命令読み出し等の動作を行い、次のクロックの立ち上がりでst=1に 映ってレジスタへの書き込みを行い、またプログラムカウンタの更新も行い、 次のクロックの立ち上がりで再びst=0に戻って、次の命令の実行へ移ります。

またst=1でr0またはr1へ書き込まれる値を、st=0のときにresに決めておく、 というルールを決めておきます。

命令の実行準備(st=0)

メモリから読み出したdataから切り出した命令部opに応じて、 加算器に与える値が決まります。 例えばop="0000" (mov imm, r0)ならば、 st=0のときにres=immとし、st=1でresをr0へ代入すればよい、ということになります。 以下同様に、各命令に対して、resの値と、resを代入する先、を整理しておきましょう。
op 命令の内容 res resを代入する先
0000mov imm, r0 immr0
0001mov imm, r1 immr1
0010add r0, imm, r0 r0 + immr0
0011
...

レジスタ書き込み等(st=1)

st=1のときは、st=0で決まったresの値を、opに応じた代入先(r0またはr1)へ 代入することになります。 (ただしjmp/jz命令のときはr0/r1は変更しないので何も行わない)

またプログラムカウンタ(PC)の更新は、jmp/jz命令では分岐先のアドレス (つまりimmの値)に、それ以外の命令では+1を行う、ことになります。 つまり、op(とjz命令ではZフラグの値)に応じて、 pcの値を更新すればよいことになります。 先ほどと同様に、pcの値の更新の内容を整理しておきましょう。
op命令の内容次のpcの値
0000mov imm, r0pc + 1
0001mov imm, r1pc + 1
.........
0110jmp immimm
.........
これとあわせて、次に実行される命令のために Zフラグの値も決定する必要があります。 (演算結果や代入するimmの値が0ならばZ=1とする)

CPUの設計

以上の動作をもとに、CPU本体を設計してみましょう。

以下、ひな形:

 
entity cpu is
  port( clk, rst: in std_logic;
        addr: out std_logic_vector(3 downto 0);
        data: in std_logic_vector(7 downto 0);
        r0, r1: out std_logic_vector(3 downto 0);
        z : out std_logic
        );
end cpu;

architecture Behavioral of cpu is
  signal op, imm, pc, res : std_logic_vector(3 downto 0);
  signal r0_reg, r1_reg : std_logic_vector(3 downto 0); -- r0/r1 variable
  signal z_reg, st : std_logic; -- z flag variable
begin
  op <= data(7 downto 4);  -- op from mem's data
  imm <= data(3 downto 0); -- imm from mem's data
  addr <= ... -- address for mem
  r0 <= r0_reg; -- r0 output
  r1 <= r1_reg; -- r1 output
  z <= z_reg;   -- z output

  process (rst, clk) begin
    if (rst = '1') then
      -- reset variables
      pc <= "0000"; z_reg <= '0'; st <= '0'; r0_reg <= "0000"; r1_reg <= "0000";
    elsif (clk'event and clk = '1') then
      if (st = '0') then
        -- state 0 : fetch, register updadate preparation

        -- setting res
        case op is
          when "0000" => res <= imm;
          ...
          when others => null;
        end case;

        -- st update
        st <= '1';
      else
        -- state 1 : register & PC & Z update
        -- PC update
        if (op = "0110") then pc ... -- jmp
        elsif (op = "0111" and ...) then pc <= imm; -- jz
        else pc <= pc + 1;
        end if;

        -- register update
        case op is
          when "0000" => r0_reg <= res;
          when "0001" => r1_reg <= res;
          ...
          when others => null;                         
        end case;

        -- Z flag update
        z_reg <= '0';
        case op is
          when "0000" => if (res = "0000") then z_reg <= '1'; end if;
          ...
          when others => null;                         
        end case;

        -- st update
        st <= '0';
      end if;
    end if;
  end process;
end Behavioral;

全体設計

最後にこれまでに設計した要素回路を統合してCPUとメモリを含む 全体cpu_topを設計し、をつくってみましょう。 (もちろんcpu_topにはスイッチやLEDなどがつきます) 入出力の構成は、例えば7セグメントLED表示回路のseg7.vhdと スイッチPSW(0), PSW(1)を交互に押すことでクロック信号を生成する sw_clk.vhdを使い、 次のようにするとわかりやすいでしょうか。 (もちろん他の構成でも構いません) ※この他にも、例えば内部状態stをLED-Bにつないで確認することも できます(ただしcpuの出力にstを追加する必要あり)。

またこのほか、ピン定義ファイルとしてsub3_cpu.ucfをプロジェクトに追加しておきます。

以下、cpu_topのひな型(ほぼこの通り使えばOKのはず)。

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

entity cpu_top is
  port (
       CLK50M : in  STD_LOGIC;
       SA : out STD_LOGIC_VECTOR(3 downto 0);
       SG : out STD_LOGIC_VECTOR(7 downto 0);
       SW : in STD_LOGIC_VECTOR(5 downto 0);
       LED : out std_logic_vector(1 downto 0)
    );
end cpu_top;

architecture Behavioral of cpu_top is
  component sw_clk
    port (
      ia, ib: in std_logic;
      clk : out std_logic
      );
  end component;

  component cpu
    port( clk, rst: in std_logic;
        addr: out std_logic_vector(3 downto 0);
        data: in std_logic_vector(7 downto 0);
        r0, r1: out std_logic_vector(3 downto 0);
		  z : out std_logic
        );
  end component;

  component seg7
    Port ( clk : in  STD_LOGIC;
           rst : in  STD_LOGIC;
           d0 : in  STD_LOGIC_VECTOR (3 downto 0);
           d1 : in  STD_LOGIC_VECTOR (3 downto 0);
           d2 : in  STD_LOGIC_VECTOR (3 downto 0);
           d3 : in  STD_LOGIC_VECTOR (3 downto 0);
           sa : out  STD_LOGIC_VECTOR (3 downto 0);
           sg : out  STD_LOGIC_VECTOR (7 downto 0));
  end component;

  component mem is
    port( addr: in std_logic_vector(3 downto 0);
          data: out std_logic_vector(7 downto 0));
  end component;

  signal ia, ib, clk_cpu, rst, z : std_logic;
  signal addr, r0, r1 : std_logic_vector(3 downto 0);
  signal data : std_logic_vector(7 downto 0);
begin
  ia <= SW(4); ib <= SW(5); -- sw_clk's input
  rst <= SW(2);
  LED(0) <= clk_cpu;
  LED(1) <= z;
  icpu : cpu port map(clk_cpu, rst, addr, data, r0, r1);
  iseg7 : seg7 port map(CLK50M, rst, r0, r1, addr, data(7 downto 4), SA, SG);
  isw_clk : sw_clk port map(ia, ib, clk_cpu);
  imem : mem port map(addr, data);
end Behavioral;

(2日目はこのあたりまでを目安にしておくとよいでしょう) (補足) 新規ファイルをつくるとき、必要な冒頭の定義(includeのようなもの)が入らない場合があるようです。冒頭のuse文の付近に次の2行がない場合は、追記しておいてください。

use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

プログラムの動作

mem.vhdにいろいろなプログラムを書き込み、それを動作させて 正しい結果が得られるかを確認してみましょう。

プログラム1: レジスタへの値の代入

addr data
0    00000001 : mov 1, r0
1    00010010 : mov 2, r1
2    01100010 : jmp 2

プログラム2: 乗算(3×4=12)

addr data
0    00000000 : mov 0, r0
1    00010100 : mov 4, r1
2    00100011 : add r0, 3, r0
3    01011111 : add r1, 15, r1
4    01110110 : jz 6
5    01100010 : jmp 2
6    01100110 : jmp 6
この他にも、例えば除算の実行などのいろいろなプログラムを記述して 実行させてみましょう。

CPUの機能拡張

ここまでで設計したCPUは、非常に基本的な機能しかありません。 しかしHDLの機能拡張が容易である特長を生かして、 機能を拡張させてみましょう。 例えば、以下のような拡張が考えられます。

レポート

今回設計したCPU、またはそれを独自に拡張したCPUをFPGA上で動作させ、 そこでプログラムを実行させて、その実行結果などをまとめる。
戻る