読者です 読者をやめる 読者になる 読者になる

kivantium活動日記

プログラムを使っていろいろやります

VHDLを使ったTD4(もどき)の製作

FPGA

Papilio One 500Kで遊ぶ その1の続きです。VHDLでTD4もどきを書くところまでやりました。

VHDL開発環境のインストール

http://cellspe.matrix.jp/parallella/inst_ise.htmlに従ってISEをインストールしました。注意としては、ライセンス登録が終わったらrunise.shの内容を

#!/bin/bash
. /opt/Xilinx/14.7/ISE_DS/settings64.sh
ise

に書き換えないとProject Managerが起動しないというところくらいです。

インストールが終わったらPapilio platform - Getting Started WebPack VHDLを見ながらLED点滅プログラムを動かします。

TD4(もどき)の設計

ここで今回作るCPUの設計を考えます。TD4の基本構成はこんな感じです。
f:id:kivantium:20150408135018p:plain:w400

恐ろしく適当な図ですね……。プログラム・カウンタの指定に従ってROMがALUに制御信号を送り、ALUは制御信号に従ってレジスタの値を書き換えるということを表したつもりなのですが伝わらないですね……。詳しく知りたい人はCPUの創りかたを買ってくださいということで。

命令セットは次のようにになっています。

  • Mov A, Im (0011XXXX): XXXXをAレジスタに書き込む
  • Mov B, Im (0111XXXX): XXXXをBレジスタに書き込む
  • Mov A, B (00010000): Aレジスタの値をBレジスタに書き込む
  • Mov B, A (01000000): Bレジスタの値をAレジスタに書き込む
  • Jmp Im (1111XXXX): プログラム・カウンタの値をXXXXに変更する
  • Jnc Im (1110XXXX): キャリーフラグが立っていないときにプログラムカウンタの値をXXXXに変更する
  • Add A, Im (0000XXXX): AレジスタにXXXXを足す
  • Add B, Im (0101XXXX): BレジスタにXXXXを足す
  • Out Im (1011XXXX): XXXXを出力する
  • Out B (10010000): Bレジスタの値を出力する

ただでさえ少ないTD4の命令セットから入力命令などを除いたのでさらに少なくなっています。これはキラーアプリであるラーメンタイマーが動けばいいだろうという考えにしたからです。

元ネタではROMをDIPスイッチで実装しているのですが、それも面倒なのでVHDLに直接値を書き込んでいます。とことん怠惰な設計です。

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

entity LEDblink is
  Port ( A : out  STD_LOGIC_VECTOR (15 downto 0);
         B : out  STD_LOGIC_VECTOR (15 downto 0);
         C : out  STD_LOGIC_VECTOR (15 downto 0);
         clk : in STD_LOGIC);
end LEDblink;

architecture Behavioral of LEDblink is
  signal counter : STD_LOGIC_VECTOR(19 downto 0) := (others => '0');
  signal PC : STD_LOGIC_VECTOR(3 downto 0) := (others => '0');
  signal regA : STD_LOGIC_VECTOR(3 downto 0) := (others => '0');
  signal regB : STD_LOGIC_VECTOR(3 downto 0) := (others => '0');
  signal carry : STD_LOGIC := '0';
  signal temp : STD_LOGIC_VECTOR(4 downto 0); 
  subtype operation is std_logic_vector (7 downto 0);
  type MEMORY is array (0 to 15) of operation;
  constant RAM : MEMORY := (
    "10110111",
    "00000001",
    "11100001",
    "00000001",
    "11100011",
    "10110110",
    "00000001",
    "11100110",
    "00000001",
    "11101000",
    "10110000",
    "10110100",
    "00000001",
    "11101010",
    "10111000",
    "11110000"
  );
begin
  process(clk)begin
    if rising_edge(clk) then
      counter <= counter+'1';
        if counter = "00000000000000000000" then
           case RAM(conv_integer(PC))(7 downto 4) is
             when "0011" =>
                 regA <= RAM(conv_integer(PC))(3 downto 0);
                 carry <= '0';
                 PC <= PC+'1';
              when "0111" =>
                regB <= RAM(conv_integer(PC))(3 downto 0);
                carry <= '0';
                PC <= PC+'1';
              when "0001" => 
                regB <= regA;
                carry <= '0';
                PC <= PC+'1';
              when "0100" =>
                regA <= regB;
                carry <= '0';
                PC <= PC+'1';
              when "1111" => 
                PC <= RAM(conv_integer(PC))(3 downto 0);
                carry <= '0';
              when "1110" =>
                 if carry = '0' then 
                  PC <= RAM(conv_integer(PC))(3 downto 0);
                else
                  PC <= PC + '1';
                 end if;
                 carry <= '0';
              when "0000" => 
                temp <= ('0'&regA) + ('0' & RAM(conv_integer(PC))(3 downto 0));
                regA <= temp(3 downto 0);
                carry <= temp(4);
                PC <= PC+'1';
              when "0101" =>
                temp <= ('0' & regB) + ('0' & RAM(conv_integer(PC))(3 downto 0));
                regB <= temp(3 downto 0);
                carry <= temp(4);
                PC <= PC+'1';
          when "1011" =>
                A <= "000000000000"&RAM(conv_integer(PC))(3 downto 0);
                carry <= '0';
                PC <= PC+'1';
          when "1001" =>
                A <= "000000000000"&regB;
                carry <= '0';
                PC <= PC+'1';                  
          when others =>
                null;
          end case;
         end if;
    end if;
  end process;
  B <= "0000000000000000";
  C <= "0000000000000000";
end Behavioral;

VHDLソースコードはこんな感じです。VHDLは完全に初めてなのでcarryやPCの書き方がものすごく頭が悪いことになっていますね……。しばらく前に書いたので当時何を考えていたのか忘れてしまいました。コメントも書いてないし。

結果

とにかくこれを書き込むとラーメンタイマーが動作しました。

以上です。次回はもう少しまともなCPUをつくります。