例えばテスト用に次のような「プログラム」を保持するメモリを設計してみましょう。
アドレス | 内容 | 命令 |
0 | 0000 0001 | mov 1, r0 |
1 | 0010 0010 | add r0, 2, r0 |
2 | 0110 0010 | jmp 2 |
※ヒント: 例えば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;
(1日目はこのあたりまでを目安にしておくとよいでしょう)
またst=1でr0またはr1へ書き込まれる値を、st=0のときにresに決めておく、 というルールを決めておきます。
op | 命令の内容 | res | resを代入する先 |
0000 | mov imm, r0 | imm | r0 |
0001 | mov imm, r1 | imm | r1 |
0010 | add r0, imm, r0 | r0 + imm | r0 |
0011 | |||
... |
またプログラムカウンタ(PC)の更新は、jmp/jz命令では分岐先のアドレス (つまりimmの値)に、それ以外の命令では+1を行う、ことになります。 つまり、op(とjz命令ではZフラグの値)に応じて、 pcの値を更新すればよいことになります。 先ほどと同様に、pcの値の更新の内容を整理しておきましょう。
op | 命令の内容 | 次のpcの値 |
0000 | mov imm, r0 | pc + 1 |
0001 | mov imm, r1 | pc + 1 |
... | ... | ... |
0110 | jmp imm | imm |
... | ... | ... |
以下、ひな形:
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;
またこのほか、ピン定義ファイルとして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;
プログラム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この他にも、例えば除算の実行などのいろいろなプログラムを記述して 実行させてみましょう。