第2章と第3章では,極めてシンプルな構成のCPU設計について解説しました.
この設計では,タイミング系が単純になる方針を優先させたため,メモリ(ROMとRAM)を中心に 実用的ではない構成(回路記述)が含まれています.
例えば,フェッチブロックの"fetch.vhd"では,プログラムのROM(PROM)のデータをVHDLの中で記述しています. しかし,回路系とROMのデータは完全に分離し,外部ファイルからROMデータを組み込むのが一般的な方法です.
VHDLの開発ツールは,FPGAを製品化しているいくつかのメーカーから無償で提供されています. それらのシステムには,”メガファンクション”等の名称で,ROMやRAMをはじめとする高度な機能をもつ ライブラリが数多く用意されています. このライブラリを使用することにより,外部ファイルからROMデータを組込むことが可能となります.
一方のRAMについては,前回の設計ではレジスタと同様に複数のD型フリップフロップを用いました. すなわち,ライトバックブロックの出力をデコードブロックで選択する構成となっています. しかし,メモリサイズが増えると,"signal"文で指定する内部信号の数が指数関数的に増えてしまい, 実用的ではありません. このRAMには,VHDLの配列(ARRAY)表現を用いることにします.
このような内容を中心に,本章ではより実用的なCPUの設計法について解説します.
2.新しいCPUの構成
下の図に,新たに設計するCPUのブロック構成と各部の信号名を示します.
PROMに「メガファンクション」,RAMに配列を用いている点が,これまでの設計と異なります.
「メガファンクション」は,ALTERA社が提供するVHDL開発ツール「QuatusⅡ」が提供するライブラリであり, PROMのデータをmif形式の外部ファイルからロードすることが可能です. PROMのブロックでは,2種類のクロック(CLK_WB_DLY,CLK_FT)を用いている点に注意して下さい. その理由については,本章の3.3で詳しく説明します.
また,RAM部はVHDLの配列(ARRAY)を用いて記述しています. これにより,従来2つのコンポーネントにより記述していたRAMの回路は,1つのコンポーネント(ram)に統合されます.
![]() |
3.メガファンクションを用いたROMの構成
本節では,メガファンクションを用いたROMの記述法について解説します.
このメガファンクションは,VHDLの開発ツールに依存する内容を多く含んでおり,今回は,ALTERA社が無償で提供している”QuartusⅡ”(Web Edition)を用いた例を示します.
そのソースコードは,メガファンクション・ウィザードを起動することにより,自動的に生成されます.下の例は,ALTERA社のFLEX10KEというデバイスを用いた場合のもので,他のデバイスでは 動作しませんので,注意が必要です.
なお,他の開発ツールを用いた場合,別の記述法が必要になりますので,注意して下さい.
3.1 ROMの記述例
ALTERA社の開発ツール”QuartusⅡ”(Web Edition)のメガファンクションを用いて,ROMの回路とデータを記述することができます.
メガファンクション・ウィザードを立ち上げ,ROMの仕様(アドレスやデータサイズ等)を設定することにより,以下のようなソースコードが自動的に出力されます.その具体的な方法は,上記システムのHELPを参照して下さい.
prom.vhd
-- magafunction wizard: %LPM_ROM%
-- GENERATION: STANDARD
-- VERSION: WM1.0
-- MODULE: lpm_rom
-- (以下略)
LIBRARY ieee;
USE ieee.std_logic_1164.all;
LIBRARY lpm;
USE lpm.lpm_components.all;
ENTITY prom IS
| PORT ( | |||||
| address | : | IN | STD_LOGIC_VECTOR(3 DOWNTO 0); | ||
| inclock | : | IN | STD_LOGIC; | ||
| outclock | : | IN | STD_LOGIC; | ||
| q | : | OUT | STD_LOGIC_VECTOR(14 DOWNTO 0) | ||
| ); | |||||
| END prom; | |||||
| ARCHITECTURE SYN OF prom IS | ||||||
| SIGNAL sub_wire0 : STD_LOGIC_VECTOR(14 DOWNTO 0); | ||||||
| COMPONENT lpm_rom | ||||||
| GENERIC ( | ||||||
| intended_device_family | : | STRING; | ||||
| (略) | ||||||
| lpm_type | : | STRING; | ||||
| ); | ||||||
| PORT ( | ||||||
| outclock | : | IN | STD_LOGIC; | |||
| address | : | IN | STD_LOGIC_VECTOR(3 DOWNTO 0); | |||
| inclock | : | IN | STD_LOGIC; | |||
| q | : | OUT | STD_LOGIC_VECTOR(14 DOWNTO 0) | |||
| ); | ||||||
| END COMPONENT; | ||||||
| BEGIN | ||
| q <= sub_wire0(14 DOWNTO 0); | ||
| lpm_rom_component : lpm_rom | ||
| GENERIC MAP( | ||
| intended_device_family => "FLEX10KE", | ||
| lpm_width => 15, | ||
| lpm_widthad => 4, | ||
| lpm_address_control => "REGISTERED", | ||
| lpm_outdata => "REGISTERED", | ||
| lpm_file => "prom.mif", | ||
| lpm_type => "LPM_ROM" | ||
| ) | ||
| PORT MAP ( | ||
| outclock => outclock, | ||
| address => address, | ||
| inclock => inclock, | ||
| q => sub_wire0 | ||
| ); | ||
| END SYN; | ||
-- (以下略)
3.2 ROMデータの記述例
ROMのデータは,以下に示すファイル"prom.mif"を用いて記述します.
-- prom.mif
-- 15bit RISC processor
-- cpu15h.vhd
-- Y.Izawa
-- H18.4.10
depth = 16;
width = 15;
address_radix = HEX;
data_radix = BIN;
content
| begin | ||||||||
| [00..0F] | : | 000000000000000; | ||||||
| 00 | : | 100000000000000; | -- ldl | Reg0 | 0 | |||
| 01 | : | 100000100000001; | -- ldl | Reg1 | 1 | |||
| 02 | : | 100001000000000; | -- ldl | Reg2 | 0 | |||
| 03 | : | 100001100001010; | -- ldl | Reg3 | 10 | |||
| 04 | : | 000101000100000; | -- add | Reg2 | Reg1 | |||
| 05 | : | 000100001000000; | -- add | Reg0 | Reg2 | |||
| 06 | : | 101001001100000; | -- cmp | Reg2 | Reg3 | |||
| 07 | : | 101100000001001; | -- je | 9 | ||||
| 08 | : | 110000000000100; | -- jmp | 4 | ||||
| 09 | : | 111000001000000; | -- st | Reg0 | 64 | |||
| 0A | : | 111100000000000; | -- hlt | |||||
| 0B | : | 000000000000000; | -- nop | |||||
| 0C | : | 000000000000000; | -- nop | |||||
| 0D | : | 000000000000000; | -- nop | |||||
| 0E | : | 000000000000000; | -- nop | |||||
| 0F | : | 000000000000000; | -- nop | |||||
| end; | ||||||||
3.3 メガファンクション”ROM”のクロック生成
メガファンクションを用いる際に注意すべき事項があります.
”QuartusⅡ”(Web Edition)のメガファンクションROMの場合,アドレスの書き込みクロックと,データの読み込みクロックの2種類が必要になります.これらを同じクロックで実行させると,アドレス入力後さらに1クロック遅延してROMのデータが出力されてしまい,CPUは正常に動作しません.フェッチのフェーズで,アドレス入力とデータの読み出しを一括して行うためには,CLK_FTの立ち上りより,クロックの半周期前で立ち上る,もう1つのクロックを生成する必要があります.
これを実現するため,位相が1つ前にあるクロックCLK_WBを遅延させます.
以下に示す”clk_dly.vhd”は,入力DINを基本クロックの半周期だけ遅延してQOUTとして出力する回路です.
半周期遅延させるため,基本クロックCLKの立ち下りエッジを用いている点に注意して下さい.
clk_dly.vhd
-- clk_dly.vhd
-- Y.Izawa
-- H18.4.10
library IEEE;
use IEEE.std_logic_1164.all;
| entity clk_dly is | |||||
| port ( | |||||
| CLK | : | in | std_logic; | ||
| DIN | : | in | std_logic; | ||
| QOUT | : | out | std_logic | ||
| ); | |||||
| end clk_dly; | |||||
| begin | ||
| process(CLK) | ||
| begin | ||
| if (CLK'event and CLK = '0') then | ||
| QOUT <= DIN; | ||
| end if; | ||
| end process; | ||
| end RTL; | ||
4. RAMの配列(ARRAY)による表現
先に述べたように,RAMは配列を用いて,効率的に記述します.
配列を用いた場合,そのインデックスがRAMのアドレスに対応します. また,フェッチブロックのアドレス出力を直接RAMのアドレスに入力し,RAMへの書き込みは, ライトバックのフェーズで行います.これにより,コンポーネント間の信号の受け渡しが単純化され,大容量化にも対応できます.配列を用いた一般的なRAMの構成による記述例を以下に示します. なお,書き込み時以外は常に読み出しモードとなり,アドレスを与えればRAMのアクセス時間の後に データが出力されることに注意して下さい.
ram.vhd
-- ram.vhd
-- Y.Izawa
-- H18.4.3
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
| entity ram is | |||||
| port ( | |||||
| CLK_WB | : | in | std_logic; | ||
| RAM_WEN | : | in | std_logic; | ||
| ADDR | : | in | std_logic_vector (6 downto 0); | ||
| DATA_IN | : | in | std_logic_vector (15 downto 0); | ||
| DATA_OUT | : | out | std_logic_vector (15 downto 0); | ||
| IO65_IN | : | in | std_logic_vector (15 downto 0); | ||
| IO64_OUT | : | out | std_logic_vector (15 downto 0) | ||
| ); | |||||
| end ram; | |||||
subtype RAMWORD is std_logic_vector(15 downto 0);
type RAMARRAY is array (0 to 7) of RAMWORD;
signal RAMDATA : RAMARRAY;
signal ADR_IN : integer range 0 to 65;
| begin | ||||||
| ADR_IN <= conv_integer(ADDR); | ||||||
| process(CLK_WB) | ||||||
| begin | ||||||
| if(CLK_WB'event and CLK_WB = '1') then | ||||||
| if(RAM_WEN = '1') then | ||||||
| if(ADDR(6) = '0') then | ||||||
| RAMDATA(ADR_IN) <= DATA_IN; | ||||||
| elsif(ADR_IN = 64) then | ||||||
| IO64_OUT <= DATA_IN; | ||||||
| end if; | ||||||
| end if; | ||||||
| end if; | ||||||
| end process; | ||||||
| process(RAM_WEN, ADR_IN) | ||||||
| begin | ||||||
| if(RAM_WEN = '0') then | ||||||
| if(ADDR(6) = '0') then | ||||||
| DATA_OUT <= RAMDATA(ADR_IN); | ||||||
| elsif(ADR_IN = 65) then | ||||||
| DATA_OUT <= IO65_IN; | ||||||
| else | ||||||
| DATA_OUT <= (others => '0'); | ||||||
| end if; | ||||||
| end if; | ||||||
| end process; | ||||||
| end BEHAVIOR; | ||||||
5. 新構成CPUの記述例
前節のROMやRAMを用いると,以下に示すソースコードのように記述することができます.
前章までの記述に対し,どのように変更されているか,注意して眺めて下さい.
5.1 新構成CPUのソースコード(cpu15h.vhd)
-- cpu15h.vhd
-- Y.Izawa
-- H18.4.10
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
entity cpu15h is
| port ( | |||||
| CLK | : | in | std_logic; | ||
| RESET | : | in | std_logic; | ||
| IO65_IN | : | in | std_logic_vector (15 downto 0); | ||
| IO64_OUT | : | out | std_logic_vector (15 downto 0) | ||
| ); | |||||
architecture RTL of cpu15h is
| component clk_gen | |||
| (略) | |||
| end component; | |||
| component clk_dly | |||||||||||||||||||||||||||||||||||||
| port | |||||||||||||||||||||||||||||||||||||
| ( | |||||||||||||||||||||||||||||||||||||
| CLK | : | in | std_logic; | ||||||||||||||||||||||||||||||||||
| DIN | : | in | std_logic; | ||||||||||||||||||||||||||||||||||
| QOUT | : | out | std_logic | ||||||||||||||||||||||||||||||||||
| ); | |||||||||||||||||||||||||||||||||||||
| end component; | |||||||||||||||||||||||||||||||||||||
| component prom | |||||||
| port | |||||||
| ( | |||||||
| address | : | in | std_logic_vector(3 downto 0); | ||||
| inclock | : | in | std_logic; | ||||
| outclock | : | in | std_logic; | ||||
| q | : | out | std_logic_vector(14 downto 0) | ||||
| ); | |||||||
| end component; | |||||||
| component decode | |||
| (略) | |||
| end component; | |||
| component reg_dc | |||
| (略) | |||
| end component; | |||
| component exec | ||||
| (略) | ||||
| end component; | ||||
| component reg_wb | |||
| (略) | |||
| end component | |||
| component ram | ||||||
| port ( | ||||||
| CLK_WB | : | in | std_logic; | |||
| RAM_WEN | : | in | std_logic; | |||
| ADDR | : | in | std_logic_vector (6 downto 0); | |||
| DATA_IN | : | in | std_logic_vector (15 downto 0); | |||
| DATA_OUT | : | out | std_logic_vector (15 downto 0); | |||
| IO65_IN | : | in | std_logic_vector (15 downto 0); | |||
| IO64_OUT | : | out | std_logic_vector (15 downto 0) | |||
| ); | ||||||
| end component; | ||||||
| signal | CLK_FT | : | std_logic; | |
| signal | CLK_DC | : | std_logic; | |
| signal | CLK_EX | : | std_logic; | |
| signal | CLK_WB | : | std_logic; | |
| signal | CLK_WB_DLY | : | std_logic; | |
| signal | P_COUNT | : | std_logic_vector (7 downto 0); | |
| signal | PROM_OUT | : | std_logic_vector (14 downto 0); | |
| signal | OP_CODE | : | std_logic_vector (3 downto 0); | |
| signal | OP_DATA | : | std_logic_vector (7 downto 0); | |
| signal | N_REG_A | : | std_logic_vector (2 downto 0); | |
| signal | N_REG_B | : | std_logic_vector (2 downto 0); | |
| signal | REG_IN | : | std_logic_vector (15 downto 0); | |
| signal | REG_A | : | std_logic_vector (15 downto 0); | |
| signal | REG_B | : | std_logic_vector (15 downto 0); | |
| signal | REG_WEN | : | std_logic; | |
| signal | REG_0 | : | std_logic_vector (15 downto 0); | |
| signal | REG_1 | : | std_logic_vector (15 downto 0); | |
| signal | REG_2 | : | std_logic_vector (15 downto 0); | |
| signal | REG_3 | : | std_logic_vector (15 downto 0); | |
| signal | REG_4 | : | std_logic_vector (15 downto 0); | |
| signal | REG_5 | : | std_logic_vector (15 downto 0); | |
| signal | REG_6 | : | std_logic_vector (15 downto 0); | |
| signal | REG_7 | : | std_logic_vector (15 downto 0); | |
| signal | RAM_IN | : | std_logic_vector (15 downto 0); | |
| signal | RAM_OUT | : | std_logic_vector (15 downto 0); | |
| signal | RAM_WEN | : | std_logic; |
| begin | |||||
| C1 | : | clk_gen port map(CLK, CLK_FT, CLK_DC, CLK_EX, CLK_WB); | |||
| C2 | : | clk_dly port map(CLK, CLK_WB, CLK_WB_DLY); | |||
| C3 | : | prom port map(P_COUNT(3 downto 0), CLK_WB_DLY, CLK_FT, | |||
| PROM_OUT); | |||||
| C4 | : | decode port map(CLK_DC, PROM_OUT, OP_CODE, OP_DATA); | |||
| C5 | : | reg_dc port map(CLK_DC, REG_0, REG_1, REG_2, REG_3, | |||
| REG_4, REG_5, REG_6, REG_7, | |||||
| PROM_OUT(10 downto 8), N_REG_A, REG_A); | |||||
| C6 | : | reg_dc port map(CLK_DC, REG_0, REG_1, REG_2, REG_3, | |||
| REG_4, REG_5, REG_6, REG_7, | |||||
| PROM_OUT(7 downto 5), N_REG_B, REG_B); | |||||
| C7 | : | exec port map(CLK_EX, RESET, OP_CODE, P_COUNT, | |||
| REG_A, REG_B, OP_DATA, RAM_OUT, P_COUNT, | |||||
| REG_IN, RAM_IN, REG_WEN, RAM_WEN); | |||||
| C8 | : | reg_wb port map(CLK_WB, N_REG_A, REG_IN, REG_WEN, | |||
| REG_0, REG_1, REG_2, REG_3, | |||||
| REG_4, REG_5, REG_6, REG_7); | |||||
| C9 | : | ram port map(CLK_WB, RAM_WEN, OP_DATA(6 downto 0), | |||
| RAM_IN, RAM_OUT, IO65_IN, IO64_OUT); | |||||
| end RTL; | |||||
6. まとめ
本章では,より実用的なCPUを実現するため,メモリすなわちROMとRAMの設計法について解説してきました.設計したVHDLをFPGA上で動作させるため,今回はメモリサイズの上限を ROM,RAMともに256ワードとしました.
しかし,実用的なプログラムを動作させるためには,このメモリサイズでは不足するでしょう.その場合は,命令コードの見直しが必要です.
例えば,ROMのメモリサイズについては,簡単に 8倍の 2048ワードに拡張することが可能です.命令コードの表より明らかなように,Jump関連の命令では,15bit のうち 11~8 の 3bit を使用していません.このため, プログラムカウンタ(PC)で表されるアドレスを 3bit 追加し,11bit に拡張することができます.
一方,RAMのメモリサイズについては,一工夫必要です.メモリとレジスタ間の命令は,ロード (LD) とストア (ST) で,8個のレジスタをオペランドの 11~8 の 3bit で指定します.この部分で,ロード とストア命令のレジスタを,例えば Reg0 に固定してしまえば,ROM
と同じようにアドレスは 11bitとなり, 8倍の 2048 ワードに拡張することが可能です.
11bit でも足りないという場合は,15bit という命令コード長の見直しが必要となりますが,RAM の場合は,アドレスの上位bit を別命令もしくは,特定のレジスタで指定する方法もあります.また,インテルの x86 シリーズのように,セグメントレジスタを新たに設け,オペランドとの和でアドレスを決定する方法も考えられます.
また,性能が足りない用途では,高速化手法を導入することも可能です.例えば,各ブロックのタイミングを互いにオーバラップさせる「パイプライン処理」を実現することも可能です.すなわち,フェッチ,デコード,実行,ライトバックの4つの処理を,ベルトコンベアによる流れ作業により高速化する手法で,理想的には見かけ上1クロックで1つの命令を実行することが可能です.この「パイプライン処理」については,次の第5章で紹介します.
また,アセンブリ言語によるプログラムを開発するツールも用意する必要があるでしょう.これらについては,第6章で説明します.

No comments:
Post a Comment