VerilogでCPU設計入門
トラ技の付録基板(2006年4月号)で、CPUを作ってみましょう。ところで、このデバイスALTERA MAXU、EPM240T100C5は、240セルしかないので、正攻法では、どうがんばっても載りません。フラッシュも載っているのですが、これをROMにしてしまうと、かなり遅くなるし、使い方が難しいです。このセル数は、常識的には、数個のカウンタで埋まってしまいます。そこで、次のような方針で設計しました。
という方針で設計しました。フラッシュを使用していませんので、トラ技デバイスでは、16ワード位しか載りません。それでも、例えば、次のようなパターンを発生させることができます。(また、ROM容量が許せば、64KWORDまで拡張可能なアーキテクチャになっています。)

また、ビジュアルに内部の状態をブレークポイント付で確認することが出来ます。(例によってGUIは、C#で作成しています。)
これにより、容易にCPUの中身の動きを知ることができます。

MAX2にレイアウト後の遅延シミュレーションでも動作確認しています。(ヒゲは、遅延が一定していないためです。)

<CPUとは、順序機械>
一番単純な順序機械といえば、カウンタです。カウンタは、順番を生成しますので、それにROMをつければ、コントロール付きの順序機械になります。
下図で、カウンタをPC=プログラムカウンタと言うと格好よくなります。さて設計したCPUは、32ビット幅があるので、ROMの幅は32ビットになります。

そのままでは、なにかを判断して処理を変えるということができないので、PCをなんらかの形で制御する必要があります。また、その為には、一時的な記憶素子が必要で
す。記憶素子をレジスタの集まりとしてまとめたものをレジスタファイルと言います。上のレジスタファイルは、同時に読み書きできるポートが3つあります。ライトポートが一つ、リードポートが二つです。また、レジスタ数は、16ビットが4個です。
一番目のゼロレジスタは、RISCでは、定番です。最後のポートレジスタは、このCPUだけの特殊仕様です。(リソース削減のため)
| 名称 | 機能 |
| ゼロ | WriteしてもReadしても0、なので実体は、ただのGND |
| R0 | 汎用レジスタ |
| R1 | 汎用レジスタ |
| ポート | 特殊レジスタ、そのまま、外部ポート出力になる |
HDLソースで見てもらったほうが分かりやすいかもしれません。
module register_file(input clock,write,reset,
input [1:0] A_address,
input [1:0] B_address,
input [1:0] write_address,
input [15:0] alu_data,port_in_data,
output [15:0] out_dataA, out_dataB,port_out_data);
reg [15:0] port_reg,r0,r1;
always @(posedge clock,posedge reset) begin
if (reset) begin
port_reg<=0;
r0<=0;
r1<=0;
end else if (write) begin
case (write_address)
`PORT: port_reg<=alu_data;
`R0: r0<=alu_data;
`R1: r1<=alu_data;
//default r0<=alu_data;
endcase
end
end
assign out_dataA=A_address==`PORT ? port_in_data :
A_address==`R0 ? r0 :
A_address==`R1 ? r1 : 0;
assign out_dataB=B_address==`PORT ? port_reg :
B_address==`R0 ? r0 :
B_address==`R1 ? r1 : 0;
assign port_out_data=port_reg;
endmodule
さて、レジスタファイルの後段は、ALUです。本格的なALUを実装しています。
module alu (input [3:0] command,
input [2:0] jump_cond,
input [15:0] portA,portB,
output reg [15:0] alu_out);
always @* begin
case (command)
`ADD_com : alu_out=portA+portB;
`SUB_com : alu_out=portA-portB;
`SHIFT_L_com: alu_out=portB >>1;
`SHIFT_R_com: alu_out=portB <<1;
`JMP_com :
case (jump_cond)
`Eq : alu_out=portA==portB;
`Not_Eq:alu_out=portA !=portB;
`Always : alu_out=1;
`Always_Not :alu_out=0;
`LT : if (portA < portB) alu_out=1;
else alu_out=0;
`GT : if (portA > portB) alu_out=1;
else alu_out=0;
default: alu_out=0;
endcase
default: alu_out=portA+portB;
endcase
end
endmodule
後は、PCのコントロールです。これは、後述するパターンジェネレータとしての機能が入っているために、ちょっとだけ複雑ですが、たいしたことはありません。
斜め字を無視して読むと、JUMPコマンドのとき、ALU出力のビット0が1だったら、immediate
data の番地をロード、それ以外は、pc<=pc+1 ;つまり、ただのカウンタということです。
このimmediate_data は、32ビット幅のROMの下位16ビットに割り当てています。ですから、ROMの容量さえ許せば、64KWORDまで、どこにでも飛ばすことができます。
//control of PC
always @(posedge clock, posedge reset) begin
if (reset) pc<=0;
else if (command==`JMP_com && alu_data[0]==1'b1) pc<=immediate_data;//Jump if jump condition is met.
else if (command==`SYNC_com) begin//Sync Start Condition
if (sinc_pos_edge_detected) pc<=pc+1;//Exectute Next Address!
else pc<=pc;//Hold until sync pulse is detected.
end else if (loop_flag ) begin//Loop Condition
if (loop_counter==0) pc<=pc+1;//Execute Next Address!
else pc<=pc;//Hold until loop counter becomes zero.
end else pc<=pc+1;//else increment PC
end
基本的には、以上の要素をつなげれば、CPUになります。すごく簡単ですね。勿論1CLKで、1INSTRUCTION処理でMUX2デバイスでも30MHz以上で動作可能です。
さて、ROM出力は、32ビットの内16ビットを飛び先に使いました。
残りのフィールドを含む割付は次のようになっています。
| ビット位置 | ビット幅 | 名称 | 内容 |
| 31:28 | 4 | command | コマンド |
| 27:26 | 2 | A_address | レジスタファイルAポートのアドレス |
| 25:24 | 2 | B_address | レジスタファイルBポートのアドレス |
| 23:22 | 2 | write_address | レジスタファイルライトポートのアドレス |
| 21:19 | 3 | jump_cond | ジャンプ条件 |
| 18:16 | 3 | reserved | |
| 16:16 | 1 | loop_flag | ループ処理するかどうかのフラグ、1でループ処理 |
| 15:0 | 16 | immediate | 即値または、飛び先 |
命令の内容を解凍するデコードという処理はありません。ですので、32ビットという広いビット幅が要るのですが、その分簡単に設計できます。
このようにROMの出力を制御線に使うやり方をマイクロコード方式と呼びます。このCPUは、マイクロコード自体が、機械語命令になっています。
このCPUがNativeで、もっている命令は、次です。
`define ADD_com 4'b0000 `define LOAD_IM_com 4'b0001 `define SUB_com 4'b0010 `define SHIFT_L_com 4'b0011 `define SHIFT_R_com 4'b0100 `define JMP_com 4'b0101 `define SYNC_com 4'b0110
よくアセンブラで見かけるMOV命令がありません。これは、RISCの定番である、ゼロレジスタとの加算で実現しています。
ソースでは、マクロ定義で、ビットフィールドを32ビットのROMコードに変換しています。
`define MOV( r0, r1) {Add,r0,ZR, r1,Eq,Reserved,Zero_Im}
上のコードの意味は、
r1 <=r0 +0
になります。
同様に、マクロで命令を定義することができます。アセンブラでよく見かけるNOPは、どう実装したらよいでしょうか?実は、ALL 0は、NOPにしています。
その意味は、
ゼロレジスタ<=ゼロレジスタ+ゼロレジスタ
です。
parameter [1:0] Port=`PORT,//IO PORT
R0= `R0,
R1= `R1,
ZR= `ZR;//ZERO REGISTER
parameter [15:0] Zero_Im=16'h0000;
parameter [3:0] Load_Im=`LOAD_IM_com,
Add=`ADD_com,
Sub=`SUB_com,
Shift_L=`SHIFT_L_com,
Shift_R=`SHIFT_R_com,
Jmp=`JMP_com,
Sync=`SYNC_com;
parameter [2:0] Eq=`Eq,
Not_Eq=`Not_Eq,
Always=`Always,
Always_Not=`Always_Not,
LT=`LT,
GT=`GT,
LTE=`LTE,
GTE=`GTE;
parameter [2:0] Reserved=3'b000,
Loop_Enabled=3'b001;
`define MOV( r0, r1) {Add,r0,ZR, r1,Eq,Reserved,Zero_Im}
`define CLR( r0) { Add,ZR,ZR,r0,Eq,Reserved,Zero_Im}
`define JUMP(num) {Jmp,ZR,ZR,ZR,Always,Reserved,16'd num}
`define LOAD_IM(r0,num) {Load_Im,Port,ZR,r0,Eq,Reserved,16'd num}
`define ADD(r0,r1,r2) { Add,r0,r1,r2,Eq,Reserved,Zero_Im}
`define SUB(r0,r1,r2) { Sub,r0,r1,r2,Eq,Reserved,Zero_Im}
`define JUMP_IF_LESS_THAN(r0,r1,num) {Jmp,r0,r1,ZR,LT,Reserved,16'd num}
`define JUMP_IF_GREATER_THAN(r0,r1,num) {Jmp,r0,r1,ZR,GT,Reserved,16'd num}
`define JUMP_IF_EQ(r0,r1,num) {Jmp,r0,r1,ZR,Eq,Reserved,16'd num}
`define SHIFT_L(r0,r1) {Shift_L,ZR,r0,r1,Eq,Reserved,Zero_Im}
`define SHIFT_R(r0,r1) {Shift_R,ZR,r0,r1,Eq,Reserved,Zero_Im}
`define SYNC {Sync,ZR,ZR,ZR,Eq,Reserved,Zero_Im}
ここまで、読んでくれてありがとうございます。そうです。アセンブラはないのです。
ROMコードは、例えば、上で定義したマクロを使って次のように書きます。
これぞ、究極の「コンピュータの原理を学ぶ」 かもしれません。 こういう命令が欲しいというのがあったら、上でマクロ定義してしまえば、よい訳です。
マクロ展開は、Veritakオプションで、preout.v として見れますので、思い通りに展開されているかチェックしてみてもよいでしょう。
function [31:0]romdata;
input [4:0] address;
case (address) //Commad A_addr, B_addr, Write_addr, Jmp_cond,Reserved, IM
0: romdata =`LOAD_IM(R0,1);
1: romdata =`CLR(Port);
2: romdata =`Add_with_loop(Port,R0,100);
3: romdata =`CLR(Port);
4: romdata =`LOAD_IM(R0,2);
5 : romdata =`Add_with_loop(Port,R0,50);
6: romdata =`CLR(Port);
7: romdata =`LOAD_IM(R0,4);
8 : romdata =`Add_with_loop(Port,R0,25);
9 : romdata =`LOAD_IM(Port,75);
10: romdata =`Add_with_loop(Port,ZR,25);
11: romdata =`LOAD_IM(Port,51);
12: romdata =`SYNC;
13: romdata =`LOAD_IM(R1,44000);
14: romdata =`JUMP_IF_LESS_THAN(R1,Port,1);
15 : romdata =`SHIFT_R(Port,Port);
16: romdata =`JUMP(14);
// 3 : romdata =`LOAD_IM(R0,100);
// 4 : romdata =`LOAD_IM(R1, 62444);
// 5 : romdata =`ADD(R0,R1,Port);
// 6 : romdata =`LOAD_IM(Port,10000);
// 7 : romdata =`ADD(R0,R1,R0);
// 8 : romdata =`SUB(R0,R1,R0);
// 9 : romdata =`ADD(Port,R0,R0);
// 10 : romdata =`JUMP_IF_LESS_THAN(R0,R1,0);
// 11 : romdata =`JUMP_IF_EQ(R0,R1,3);
17 : romdata =0;
18 : romdata =0;
19 : romdata =0;
20 : romdata =0;
default : romdata=0;
endcase
endfunction
<Confiugurable CPU>
このCPUで、設計していて面白いことに気づきました。それは、このCPUは、ROMワードいくつまでMAX2に載るとは、言えないことです。実際にROMで使う命令によります。ROMをメモリとしてではなく、LUTで構成しているので、実装する命令で影響を受けます。つまり、ALUでは、いろいろ定義してあるのですが、論理合成で、使わないロジックは、容赦なくRudctionされます。マジックの種明かしでした。
<パイプライン化ということ>
このCPUは、長いパスを持っています。多分、PCに始まりROM ->Register FILE −>ALU−> PC コントロールロジックー>PCに来るパスが最長だろうと思います。
最長のパスをクリティカルパスと言います。パイプライン化した普通のCPUでは、このパスは何段かのFF間の組み合わせ回路に分割されることになります。
CPUの応用
<パターンGenerator>
ループフラグをONにすると、ループカウンタが0になるまで、その場所に留まり、命令を繰り返します。これは、Shift演算にも活用することができます。
//loop command
`define Shift_Right_with_Loop(r0,num) {Shift_R,ZR,r0,r0,Eq,Loop_Enabled,16'd num }
`define Add_with_loop(r0,r1,num) { Add,r1,r0,r0,Eq,Loop_Enabled,16'd num} //Use Bport for port_reg
ループカウンタは、即値と兼用ですので、全命令で適用できる訳では、ありません。しかし、任意時間幅のパターンを作る場合には、この性質は、重宝します。 また、SYNC命令で、POS EDGEで命令スタートということもできます。BINARYのシリアルパターンを作る場合は、16ビットごとに、CPUへCLCOCKを与えれば、シリアルパターン生成器にもなります。 <VPI> 今回も vpi_dll5.dll に追加しました。$SendMessageと、$MessageBox を追加しました。その名の通りですが、詳細はソースを参照ください。 今回追加したソースは次です。
登録部
//Jul.11.2006
tf_data.type =vpiSysFunc;//
tf_data.sysfunctype =vpiIntFunc;//
tf_data.tfname = "$SendMessage";
tf_data.user_data = "$SendMessage";
tf_data.calltf = sys_SendMessage;
tf_data.compiletf = 0;
tf_data.sizetf = sys_systems_size_tf;//func
vpi_register_systf(&tf_data);
tf_data.type =vpiSysFunc;//
tf_data.sysfunctype =vpiIntFunc;//
tf_data.tfname = "$MessageBox";
tf_data.user_data = "$MessageBox";
tf_data.calltf = sys_MessageBox;
tf_data.compiletf = 0;
tf_data.sizetf = sys_systems_size_tf;//func
vpi_register_systf(&tf_data);
実装部
static int sys_SendMessage(char* name)
{
vpiHandle systfref, argsiter, argh;
s_vpi_value value;
systfref = vpi_handle(vpiSysTfCall, NULL); /* get system function that invoked C routine */
argsiter = vpi_iterate(vpiArgument, systfref);/* get iterator (list) of passed arguments */
unsigned message_parameter[4];
for (unsigned i=0;i<4;i++){
argh = vpi_scan(argsiter);/* get the one argument - add loop for more args */
if(!argh){
vpi_printf("$VPI sys_SendMessage: missing parameter. \n");
// vpi_sim_control(vpiFinish, 1);
return 0;
}
value.format = vpiIntVal;
vpi_get_value(argh, &value);
message_parameter[i]=value.value.integer;
}
unsigned result= ::SendMessage(reinterpret_cast<HWND>(message_parameter[0]),//handle
message_parameter[1],//WM_COMMAND
message_parameter[2],//WPARAM
message_parameter[3]);//LPARAM
if(argh) vpi_free_object(argsiter);
value.value.integer =result;//;
value.format = vpiIntVal;/* return the result */
vpi_put_value(systfref, &value, NULL, vpiNoDelay);
return(0);
}
static int sys_MessageBox(char* name)
{
vpiHandle systfref, argsiter, argh;
s_vpi_value value;
systfref = vpi_handle(vpiSysTfCall, NULL); /* get system function that invoked C routine */
argsiter = vpi_iterate(vpiArgument, systfref);/* get iterator (list) of passed arguments */
unsigned message_id=0;
string str_message;
string str_caption;
for (unsigned i=0;i<2;i++){
argh = vpi_scan(argsiter);/* get the one argument - add loop for more args */
if(!argh){
vpi_printf("$VPI sys_MessageBox: missing parameter. \n");
// vpi_sim_control(vpiFinish, 1);
return 0;
}
value.format = vpiStringVal;
vpi_get_value(argh, &value);
if (i==0) str_message=value.value.str;
else if (i==1) str_caption=value.value.str;
}
argh = vpi_scan(argsiter);
if (argh) {
value.format = vpiIntVal;
vpi_get_value(argh, &value);
message_id=value.value.integer;
}
unsigned result=MessageBox(0,str_message.c_str(),
str_caption.c_str(),message_id);
if(argh) vpi_free_object(argsiter);
value.value.integer =result;//;
value.format = vpiIntVal;/* return the result */
vpi_put_value(systfref, &value, NULL, vpiNoDelay);
return(0);
}
テストベンチのソースです。
Verilog HDLソースから、MessageBoxを開くことができます。


`timescale 1ns/1ps
`ifdef GATE_SIM
`define CYCLE 26.92
`else
`define CYCLE 30
`endif
module test;
reg clock=0;
reg reset=0;
reg [15:0] port_in_data=0;
reg sync=0;
wire [15:0] port_out_data;
always #(`CYCLE/2) clock=~clock;
initial begin
reset=1;
#105;
reset=0;
#1000000;
$finish;
end
cpu cpu(.clock,.reset,.sync,.port_in_data,.port_out_data);
always @(negedge clock) begin
if (port_out_data==51) begin
repeat( 100) begin
@(negedge clock);
end
sync=1;
@(negedge clock);
sync=0;
end
end
localparam integer MB_YESNO=4;
localparam integer MB_YES=6;
localparam integer MB_NO=7;
localparam integer TOP_MOST=32'h4_0000;
`ifndef GATE_SIM
integer handle,i;
reg [8*20:1] str;
reg [31:0] data;
integer result;
parameter integer WM_COMMAND=32'h111;
initial begin
handle=$FindWindow("MyCPU");
if (handle) begin
end else begin :loop0
result=$MessageBox("ようこそ。Visual デバッガを起動しますか?","VeritakMessageBox",MB_YESNO | TOP_MOST );
if (result==MB_YES) begin
$shell_execute("cpu");
repeat(100) begin//Retry LOOP; Repeat until MyCPU is detected.
$Sleep(100);//100ms wait
handle=$FindWindow("MyCPU");
if (handle) disable loop0;
end
end
end
if (handle) begin
$SendMessage(handle,WM_COMMAND,256,0);//ROM コードを送出
for (i=0;i<32;i=i+1) begin
data=cpu.rom.romdata(i);
$SendMessage(handle,WM_COMMAND,i+256,data);
end
$MessageBox("GUIの準備ができました。必要ならMyCPUのROMリストで、ブレークポイントを設定してください。その後にOKボタンを押してください。","VeritakMessageBox");
end else begin
//$MessageBox("MyCPUが起動できませんでした。","VeritakMessageBox");
end
end
always @(negedge clock) begin
handle=$FindWindow("MyCPU");
if (handle) begin//現在の状態を送出
//regfile
$SendMessage(handle,WM_COMMAND,1,cpu.rfile.out_dataA);
$SendMessage(handle,WM_COMMAND,2,cpu.rfile.out_dataB);
$SendMessage(handle,WM_COMMAND,3,cpu.rfile.alu_data);
$SendMessage(handle,WM_COMMAND,4,cpu.rfile.r0);
$SendMessage(handle,WM_COMMAND,5,cpu.rfile.r1);
$SendMessage(handle,WM_COMMAND,6,cpu.rfile.port_reg);
$SendMessage(handle,WM_COMMAND,7,cpu.ALU.portA);
$SendMessage(handle,WM_COMMAND,8,cpu.ALU.portB);
$SendMessage(handle,WM_COMMAND,9,cpu.ALU.alu_out);
$SendMessage(handle,WM_COMMAND,10,cpu.ALU.command);
$SendMessage(handle,WM_COMMAND,11,cpu.ALU.jump_cond);
$SendMessage(handle,WM_COMMAND,12,cpu.loop_counter);
//PC コマンドはラストに送る
result=$SendMessage(handle,WM_COMMAND,0,cpu.pc);
if (result==-1) begin//ブレーク要求がGUIから来たら
$stop;
end
end
end
`endif
integer handle_7led;
initial begin
handle_7led=$FindWindow("7LED");//を探す
if (!handle_7led)begin :loop1
result=$MessageBox("ポート出力を7セグメントLEDパネルに出力しますか?","VeritakMessageBox",MB_YESNO | TOP_MOST );
if (result==MB_YES) begin
$shell_execute("LED7SEG");//7セグメントLEDを立ち上げる
repeat(100) begin//最長100msx100=10sec待ち
$Sleep(100);
handle_7led=$FindWindow("7LED");//を探す
if (handle_7led) disable loop1;//見つかったら脱出
end
end
end
end
always @(negedge clock) begin
handle_7led=$FindWindow("7LED");
if (handle_7led) begin :LED//現在の状態を送出
reg [31:0] led_value={1'b0,decoder(port_out_data[15:12]),
1'b0,decoder(port_out_data[11:8]),
1'b0,decoder(port_out_data[7:4]),
1'b0,decoder(port_out_data[3:0])};
$SendMessage(handle_7led,WM_COMMAND,led_value,0);
end
end
function [6:0] decoder(input [3:0] din);//7segment LED
case (din)
4'b0000 : decoder = 7'b1111110;
4'b0001 : decoder = 7'b0110000;
4'b0010 : decoder = 7'b1101101;
4'b0011 : decoder = 7'b1111001;
4'b0100 : decoder = 7'b0110011;
4'b0101 : decoder = 7'b1011011;
4'b0110 : decoder = 7'b1011111;
4'b0111 : decoder = 7'b1110000;
4'b1000 : decoder = 7'b1111111;
4'b1001 : decoder = 7'b1111011;
4'b1010 : decoder = 7'b1110111;
4'b1011 : decoder = 7'b0011111;
4'b1100 : decoder = 7'b1001110;
4'b1101 : decoder = 7'b0111101;
4'b1110 : decoder = 7'b1001111;
4'b1111 : decoder = 7'b1000111;
endcase
endfunction
endmodule
C#ソースです。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using System.Runtime.InteropServices;
namespace cpu
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
command_array = new int[10000];
}
[DllImport("USER32.DLL")]
public static extern int FindWindow(
int hWnd, String lpText );
[DllImport("USER32.DLL")]
public static extern void PostMessage(
int hWnd, int com,int wp,int lp);
int wp = 0;
int lp = 0;
int[] command_array;
int a_port_address=0;
int b_port_address=0;
int write_port_address=0;
int current_pc=0;
uint clock = 0;
uint regfile_R0 = 0;
uint regfile_PORT = 0;
uint regfile_R1 = 0;
uint regfile_a_port=0;
uint regfile_b_port=0;
uint regfile_write_port=0;
uint alu_port_a=0;
uint alu_port_b=0;
uint alu_out_port=0;
uint loop_counter=0xffffffff;
uint alu_operation = 0;
uint alu_jump_cond = 0;
string get_register_name(int num)
{
switch (num)
{
case (0): return "ゼロ";
case (1): return "R0";
case (2): return "R1";
case (3): return "ポート";
}
return "";
}
private void make_code()
{
int command = (lp >> 28 ) & 0xf;
int address = wp - 256;
if (address == 0)
{
checkedListBox1.Items.Clear();
clock = 0;
command_array.Initialize();
}
if(address>=0) command_array[address] = lp;
int a_port_address=(lp >>26) & 3 ;
int b_port_address = (lp >> 24) & 3;
int write_port_address=(lp>>22) & 3;
int jump_cond = (lp >> 19) & 7;
int loop_flag = (lp >> 16) & 1;
uint immediate =(uint)( lp & 0xffff);
string str;
str =address.ToString("d4");
str +=" : ";
if (lp == 0)
{
str +="NOP";
checkedListBox1.Items.Add(str);
}
else if (command == 0)//Add
{
if (a_port_address == 0 && b_port_address == 0)//CLR
{
string str_word = str+get_register_name(write_port_address);
str_word += "をクリア";
checkedListBox1.Items.Add(str_word);
}else if (b_port_address == 0)//MOV
{
string str_word = str+get_register_name(a_port_address);
str_word += "から";
str_word += get_register_name(write_port_address);
str_word += "へのコピー";
checkedListBox1.Items.Add(str_word);
}
else
{//Add
string str_word =str+ get_register_name(write_port_address);
str_word += " <= ";
str_word += get_register_name(a_port_address);
str_word += " + ";
str_word += get_register_name(b_port_address);
if (loop_flag == 1) str_word += " ,ループ付";
checkedListBox1.Items.Add(str_word);
}
}
else if (command == 1)//load IM
{
string str_word = str + get_register_name(write_port_address);
str_word += "に";
str_word += immediate.ToString("d");
str_word += "dec , ";
str_word += immediate.ToString("X");
str_word += "hexをロード";
checkedListBox1.Items.Add(str_word);
}
else if (command == 2)//SUB
{
string str_word = str + get_register_name(write_port_address);
str_word += " <= ";
str_word += get_register_name(a_port_address);
str_word += " - ";
str_word += get_register_name(b_port_address);
checkedListBox1.Items.Add(str_word);
}
else if (command == 3)//
{
string str_word = str + get_register_name(write_port_address);
str_word += " <= ";
str_word += get_register_name(b_port_address);
str_word += " >> 1 ";
if (loop_flag == 1) str_word += " ,ループ付";
checkedListBox1.Items.Add(str_word);
}
else if (command == 4)//
{
string str_word = str + get_register_name(write_port_address);
str_word += " <= ";
str_word += get_register_name(b_port_address);
str_word += " << 1 ";
if (loop_flag == 1) str_word += " ,ループ付";
checkedListBox1.Items.Add(str_word);
}
else if (command == 5)//JUMP
{
string str_word = str ;
if (jump_cond==2) {//always
str_word += immediate.ToString("d");
str_word += "番地にジャンプ";
checkedListBox1.Items.Add(str_word);
}else if (jump_cond==3) {//not always
str +="NOP";
checkedListBox1.Items.Add(str);
}else {
str_word += get_register_name(a_port_address);
if (jump_cond == 0) str_word += " = ";
else if (jump_cond == 1) str_word += " != ";
else if (jump_cond == 4) str_word += " < ";
else if (jump_cond == 5) str_word += " > ";
else if (jump_cond == 6) str_word += " <= ";
else if (jump_cond == 7) str_word += " >= ";
str_word += get_register_name(b_port_address);
str_word += "なら";
str_word += immediate.ToString("d");
str_word += "番地にジャンプ";
checkedListBox1.Items.Add(str_word);
}
}
else if (command == 6)//Sync
{
string str_word = str ;
str_word += "Sync待ち";
checkedListBox1.Items.Add(str_word);
}
}
private void draw_values()
{
int address = wp - 256;
if (address >= 0) make_code();
else
{
switch (wp) {
case (0) :current_pc=lp;break;
case (1): regfile_a_port =(uint) lp; break;
case (2): regfile_b_port =(uint) lp; break;
case (3): regfile_write_port =(uint) lp; break;
case (7): alu_port_a = (uint)lp; break;
case (8): alu_port_b = (uint)lp; break;
case (9): alu_out_port = (uint)lp; break;
case (4): regfile_R0 = (uint)lp; break;
case (5): regfile_R1 = (uint)lp; break;
case (6): regfile_PORT = (uint)lp; break;
case (10): alu_operation = (uint)lp; break;
case (11): alu_jump_cond = (uint)lp; break;
case (12): loop_counter = (uint)lp; break;
}
}
}
private void draw_alu(Graphics g,int x, int y)
{
Pen p = new Pen(Color.Black, 2);
Point[] ps = {new Point(10+x, 30+y), new Point(220+x, 30+y),
new Point(200+x, 130+y), new Point(30+x, 130+y)};
//折れ線を引く
g.DrawPolygon(p, ps);
string a_port_str = alu_port_a.ToString("X4");
string b_port_str = alu_port_b.ToString("X4");
string write_port_str = alu_out_port.ToString("X4");
string alu_op_str = "オペレーション";
switch (alu_operation)
{
case (0): alu_op_str += "Add"; break;
case (2): alu_op_str += "Sub"; break;
case (3): alu_op_str += " >> "; break;
case (4): alu_op_str += " << "; break;
case (5): alu_op_str += " Jump";
switch (alu_jump_cond)
{
case (0): alu_op_str += "if Eq"; break;
case (1): alu_op_str += "if Not Eq"; break;
case (2): alu_op_str += "Always"; break;
case (3): alu_op_str += "Always Not"; break;
case (4): alu_op_str += "if < "; break;
case (5): alu_op_str += "if > "; break;
case (6): alu_op_str += "if <= "; break;
case (7): alu_op_str += "if >= "; break;
}
break;
}
Font objFont1 = new Font("MS Pゴシック", 11);
g.DrawString(a_port_str, objFont1, Brushes.Blue, 50+x, 30+y);
g.DrawString(b_port_str, objFont1, Brushes.Blue, 150+x, 30+y);
g.DrawString(write_port_str, objFont1, Brushes.Blue, 100+x, 110+y);
g.DrawString(alu_op_str, objFont1, Brushes.Blue, 25 + x, 70 + y);
}
private void draw_regfile(Graphics g, int x, int y)
{
Pen p = new Pen(Color.Black, 2);
Point[] ps = {new Point(10+x, 30+y), new Point(220+x, 30+y),
new Point(220+x, 130+y), new Point(10+x, 130+y)};
//折れ線を引く
g.DrawPolygon(p, ps);
string a_port_str = regfile_a_port.ToString("X4");
string b_port_str = regfile_b_port.ToString("X4");
string write_port_str = regfile_write_port.ToString("X4");
string regfile_zero_str = "・ゼロレジ =0";
string regfile_R0_str = "・R0 ="+regfile_R0.ToString("X4");
string regfile_R1_str = "・R1 ="+regfile_R1.ToString("X4");
string regfile_Port_str = "・ポートレジ="+regfile_PORT.ToString("X4");
Font objFont1 = new Font("MS Pゴシック", 11);
g.DrawString(a_port_str, objFont1, Brushes.Blue, 70, 140);
g.DrawString(b_port_str, objFont1, Brushes.Blue, 180, 140);
g.DrawString(write_port_str, objFont1, Brushes.Blue, 130, 60);
g.DrawString(regfile_zero_str, objFont1, Brushes.Green, 50, 75);
g.DrawString(regfile_R0_str, objFont1, Brushes.Green, 50, 90);
g.DrawString(regfile_R1_str, objFont1, Brushes.Green, 50, 105);
g.DrawString(regfile_Port_str, objFont1, Brushes.Green, 50, 120);
}
private void draw_pc(Graphics g, int x, int y)
{
Pen p = new Pen(Color.Black, 2);
Point[] ps = {new Point(10+x, 10+y), new Point(140+x, 10+y),
new Point(140+x, 40+y), new Point(10+x, 40+y)};
//折れ線を引く
g.DrawPolygon(p, ps);
Font objFont1 = new Font("MS Pゴシック", 12);
string str;
str = "PC=";
str += current_pc.ToString("d2");
g.DrawString(str, objFont1, Brushes.Blue, 10, 20);
string lc_str="LC=";
lc_str += loop_counter.ToString("x4");
Font objFont2 = new Font("MS Pゴシック", 10);
g.DrawString(lc_str, objFont2, Brushes.Blue, 80, 45);
if (checkedListBox1.Items.Count > current_pc)
{
checkedListBox1.SetSelected(current_pc , true);
}
string clock_str = "Total "+clock.ToString("x4") +"Clocks";
g.DrawString(clock_str, objFont1, Brushes.Brown, 145, 5);
}
private void paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Pen p = new Pen(Color.Black, 2);
draw_regfile(g, 30, 30);
g.DrawLine(p, 90, 160, 90, 230);
g.DrawLine(p, 200, 160, 200, 230);
draw_alu(g,30,200);
g.DrawLine(p, 150, 330, 150, 360);
g.DrawLine(p, 150, 360, 290, 360);
g.DrawLine(p, 290, 360, 290, 30);
g.DrawLine(p, 290, 30, 150, 30);
g.DrawLine(p, 150, 30, 150, 60);
}
protected override void WndProc(ref Message m)
{
const int WM_CLOSE = 0x0010;
const int WM_ENDSESSION = 0x16;
const int WM_SYSCOMMAND = 0x112;
const int SC_CLOSE = 0xF060;
const int WM_COMMAND = 0x111;
const int WM_SIZE = 0x0005;
switch (m.Msg)
{
case WM_ENDSESSION:
//OSのシャットダウンで閉じられようとしている
//Console.WriteLine("WM_ENDSESSION");
break;
case WM_SYSCOMMAND:
//if (m.WParam.ToInt32() == SC_CLOSE)
//Xボタン、コントロールメニューの「閉じる」、
//コントロールボックスのダブルクリック、
//Atl+F4などにより閉じられようとしている
// Console.WriteLine("SC_CLOSE");
break;
case WM_CLOSE:
//Application.Exit以外で閉じられようとしている
//Console.WriteLine("WM_CLOSE");
break;
case WM_SIZE:
pictureBox1.Refresh();
pictureBox2.Refresh();
break;
case WM_COMMAND:
wp = (int)m.WParam;
lp = (int)m.LParam;
clock++;
draw_values();
pictureBox1.Refresh();//再ドロー
pictureBox2.Refresh();
break;
}
base.WndProc(ref m);//baseの後に書き換えないとだめ
if (m.Msg==WM_COMMAND && wp == 0)//この後$stopするので、このコマンドは最後に送ること
{
if (checkedListBox1.Items.Count > current_pc)
{
if (checkedListBox1.GetItemChecked(current_pc))//BreakpointチェックがOnなら
{
m.Result = (IntPtr)(-1);//Mark As breakpoint
}
}
}
}
private void paint_picture_box2(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Pen p = new Pen(Color.Black, 2);
draw_pc(g, 0, 0);
}
private void StepButton(object sender, EventArgs e)
{
const int WM_COMMAND = 0x111;
int veritakwin_handle=FindWindow(0,"VeritakWin");
if (veritakwin_handle !=0) {//VeritakWinが存在するなら
PostMessage(veritakwin_handle,WM_COMMAND,133,0);//GO Command をVeritakWinに送る
}
}
private void checkedListBox1_double_click(object sender, MouseEventArgs e)
{
}
private void Stop_Button_clicked(object sender, EventArgs e)
{
const int WM_COMMAND = 0x111;
int veritakwin_handle = FindWindow(0, "VeritakWin");
if (veritakwin_handle != 0)
{//VeritakWinが存在するなら
PostMessage(veritakwin_handle, WM_COMMAND, 134, 0);//GO Command をVeritakWinに送る
}
}
private void Reload_and_go_Button_clicked(object sender, EventArgs e)
{
const int WM_COMMAND = 0x111;
int veritakwin_handle = FindWindow(0, "VeritakWin");
if (veritakwin_handle != 0)
{//VeritakWinが存在するなら
PostMessage(veritakwin_handle, WM_COMMAND, 153, 0);//GO Command をVeritakWinに送る
}
}
}
}
CPU の拡張
さて、一応上の記述で動きますが、やはり自分でいじってみないと良く分からないかもしれません。是非、上を雛形として、
という変更を行ってみてください。以下は、いくつかの拡張案です。
もちろん、MAX2で動かすとなると無理かもしれませんが、シミュレーションを行う分には、自由です。
それでは、実際に、変更を加える例をやってみましょう。
仕様は、次のコマンドを追加するものとします。ifdef で追加しています。
ADDITIONAL_COMMANDがdefineされているときに、Enableされます。このようにしておくと、元に戻したいときに便利です。

なんと、乗算器まで、定義してしまいました。大丈夫でしょうか?
次は、ALUに演算記述を追加します。乗算は、8x8=16ビット符号なしになります。

それから、parameter 定義も追加しておきます。

最後にROM コードです。
入力の値を100倍して、出力ポートに書き出しています。

MAX2での合成結果です。ふぅ、収まりました。

41MHzで走るそうです。

遅延シミュレーションです。
クロックエッジから8ns程度遅れますが、ちゃんと100倍になって返ってきました。

もう、このマジックは、お分かりですね。ハードとしてのROMを解析するとADDや、SUB、その他ロジックは使われていないことを合成器は分かったのでしょう。
これを、純ROMとして定義してしまうと、乗算器は凄く食うので、到底載らないでしょう。
<次の拡張アイデア>
ところで、この乗算器は、JUMPが入っているために、2CYCLEかかってしまっています。ここにループ命令をもってくれば、ループ期間は、1CYCLEで処理できます。
これは、マクロ命令を定義するだけで可能です。或いは、JUMP命令と融合するというアイデアも可能でしょう。命令が終わったら常にジャンプするという具合です。
(IMMEDIATE命令を除き、このフィールドは、遊んでいます)幸い、マイクロフィールドは、未だ2ビット残っていますので、このビットを使って、ビットが立っているときだけ、そういう風にするということもできます。
或いは、このCPUを何個が並列で使うアイデアもありでしょう。割り込みなんて、1サイクルで処理できてしまいます。。
ステートマシンでは、複雑すぎる、CPUでは遅すぎるという場合に好適なハードとソフトの中間みたいなマシンです。こういうハードをマイクロシーケンサと呼んでいます。
昔のCISCマシン(68000の時代)は、マイクロシーケンサで制御されていたと思います。CPUの中のCPUそれが、マイクロシーケンサです。
アーカイブです。
<番外編>
ST2だとどうなるかやってみました。動作速度140Mz、リソース2%の消費でした。
ユーザContribution![]()
ユーザ様から、拡張例をいただきました。

以下は、作者の方のコメントです。
ポート入出力を明示的に分けたり、命令セットや動作仕様を
自分なりにしっくり来るように変更したりしてみました。
ROMのマイクロコードだけ見ていると、アセンブラみたい(笑)
// バレルシフタ
1514: `SET(R0,
2);
1515: `SET(R1, 7);
1516: `SET(R2, 0);
1517: `BSHIFT_L(R0, R1,
R0);
1518: `BSHIFT_R(R0, R1, R0);
でも、実は組み合わせ回路に合成される辺り、何だか、楽しいですね。
調子に乗って、命令とか増やしたり、
ROM出力のビット幅を増やしたりしてみましたが、
クリティカルパスが益々長くなってしまいました。 。
ソースです。
// マイクロプログラム方式 CPU
// ネイティブコマンド
`define ADD_com 5'b00000
`define SET_com 5'b00001
`define SUB_com 5'b00010
`define SHIFT_L_com 5'b00011
`define SHIFT_R_com 5'b00100
`define JMP_com 5'b00101
`define SYNC_com 5'b00110
`define AND_com 5'b00111
`define OR_com 5'b01000
`define XOR_com 5'b01001
`define NOT_com 5'b01010
`define MUL_com 5'b01011
`define BSHIFT_L_com 5'b01100
`define BSHIFT_R_com 5'b01101
// ジャンプ条件
`define Eq 4'b0000
`define Not_Eq 4'b0001
`define Always 4'b0010
`define LT 4'b0011
`define GT 4'b0100
`define LTE 4'b0101
`define GTE 4'b0110
// レジスタアドレス
`define ZR 4'b0000
`define R0 4'b0001
`define R1 4'b0010
`define R2 4'b0011
`define R3 4'b0100
`define P_IN 4'b0101
`define P_OUT 4'b0110
// CPU
module cpu(input clock, reset,
input [15:0] port_in_data,
input sync,
output [15:0] port_out_data);
localparam integer Loop_counter_width=16;
localparam integer Stop_count = 2**Loop_counter_width -1;
reg [Loop_counter_width-1:0] loop_counter;
reg [15:0] pc;
// reg [15:0] port_in_reg;
reg sync_ff, sync_ff2;
/*----------------------------------------------------------------------------
ROMワイヤーマップ
ビット位置 ビット幅 内容
39:35 5 命令
34:31 4 レジスタファイルAポートのアドレス
30:27 4 レジスタファイルBポートのアドレス
26:23 4 レジスタファイルライトポートのアドレス
22:19 4 ジャンプ比較条件
18:16 3 将来拡張用
16:16 1 loop_flag ループ処理するかどうかのフラグ、1でループ処理
15:0 16 即値または、飛び先
-----------------------------------------------------------------------------*/
wire [39:0] rom_data;
wire [4:0] command = rom_data[39:35];
wire [3:0] A_address = rom_data[34:31];
wire [3:0] B_address = rom_data[30:27];
wire [3:0] write_address = rom_data[26:23];
wire [3:0] jump_cond = rom_data[22:19];
wire [2:0] reserved = rom_data[18:16];
wire [15:0] immediate_data = rom_data[15:0];
wire loop_flag = reserved[0];
wire [15:0] out_dataA, out_dataB;
wire [15:0] alu_data;
// SETコマンドの時は、"Aポートに代入する値" を接続する。
// ※ ALUで ゼロレジスタと加算して代入する事で、SETを実現する為。
wire [15:0] Aport_data = (command==`SET_com) ? immediate_data : port_in_data;
//ループカウンタリセット条件
wire Reset_loop_counter = (loop_counter<2)||
(loop_counter==Stop_count && immediate_data == 16'b0)
? 1 : 0;
//ループカウンタの処理
always @(posedge clock, posedge reset) begin
if(reset)
begin
loop_counter <= Stop_count;
end
else if(Reset_loop_counter)
begin
loop_counter <= Stop_count;
end
else if(loop_flag && loop_counter==Stop_count)
begin
// ループカウント値をセット
loop_counter <= immediate_data;
end
//else if ((loop_flag) && (loop_counter>1))
else if(loop_flag)
begin
// ループ処理
loop_counter <= loop_counter - 16'b1;
end
else
begin
loop_counter <= Stop_count;
end
end
// 入力同期エッジ検出
always @(posedge clock, posedge reset) begin
if (reset) sync_ff<=0;
else sync_ff<=sync;
end
always @(posedge clock, posedge reset) begin
if (reset) sync_ff2<=0;
else sync_ff2<=sync_ff;
end
// 立ち上がりエッジを検出
wire sync_pos_edge_detected = sync_ff & ~sync_ff2;
// マイクロプログラムカウンタ
always @(posedge clock, posedge reset) begin
if(reset)
begin
pc <= 0;
end
// ジャンプ命令処理
else if(command==`JMP_com && alu_data[0]==1'b1)
begin
//飛んでいけ
pc <= immediate_data;
end
else if(command==`SYNC_com)
begin //同期待ちスタート処理
if(sync_pos_edge_detected)
begin
//同期信号を検出。次のアドレスを実行開始。
pc <= pc + 1;
end
else
begin
//同期エッジが来るまで待つ
pc <= pc;
end
end
else if(loop_flag)
begin //ループ処理
if(Reset_loop_counter)
begin
//ループ処理から抜ける
pc <= pc + 1;
end
else
begin
//ダウンカウント中は待つ
pc <= pc;
end
end
else
begin
// 何もない時は次のアドレスを実行開始
pc <= pc + 1;
end
end
/*
//input reg
always @(posedge clock, posedge reset)
begin
if(reset)
begin
port_in_reg<=0;
end
else
begin
port_in_reg<=port_in_data;
end
end
*/
// レジスタファイル
register_file register_file(.clock(clock),
.reset(reset),
.A_address(A_address),
.B_address(B_address),
.write_address(write_address),
.alu_data(alu_data),
.out_dataA(out_dataA),
.out_dataB(out_dataB),
.port_in_data(Aport_data),
.port_out_data(port_out_data));
// ALU
alu alu(.command(command),
.jump_cond(jump_cond),
.portA(out_dataA),
.portB(out_dataB),
.alu_out(alu_data));
// ROM
rom rom(.rom_address(pc),.Data(rom_data));
endmodule
// レジスタファイル
// 記憶素子をレジスタの集まりとして、まとめてモジュール化。
// 同時に読み書きできるポートが3つ
// ライトポートが1つ、リードポートが2つ。
module register_file(input clock,
input reset,
input [3:0] A_address,
input [3:0] B_address,
input [3:0] write_address,
input [15:0] alu_data, port_in_data,
output [15:0] out_dataA, out_dataB, port_out_data);
// CPUレジスタ
reg [15:0] p_out_reg, r0, r1, r2, r3;
// ポートアウトレジスタ
always @(posedge clock, posedge reset) begin
if(reset)
begin
p_out_reg <= 0;
end
else if(write_address == `P_OUT)
begin
p_out_reg <= alu_data;
end
else
begin
p_out_reg <= p_out_reg;
end
end
// 汎用レジスタR0
always @(posedge clock, posedge reset) begin
if(reset)
begin
r0 <= 0;
end
else if(write_address == `R0)
begin
r0 <= alu_data;
end
else
begin
r0 <= r0;
end
end
// 汎用レジスタR1
always @(posedge clock, posedge reset) begin
if(reset)
begin
r1 <= 0;
end
else if(write_address == `R1)
begin
r1 <= alu_data;
end
else
begin
r1 <= r1;
end
end
// 汎用レジスタR2
always @(posedge clock, posedge reset) begin
if(reset)
begin
r2 <= 0;
end
else if(write_address == `R2)
begin
r2 <= alu_data;
end
else
begin
r2 <= r2;
end
end
// 汎用レジスタR3
always @(posedge clock, posedge reset) begin
if(reset)
begin
r3 <= 0;
end
else if(write_address == `R3)
begin
r3 <= alu_data;
end
else
begin
r3 <= r3;
end
end
// Aポート出力(ALUへ)
assign out_dataA = (A_address==`P_OUT) ? p_out_reg :
(A_address==`P_IN) ? port_in_data :
(A_address==`R0) ? r0 :
(A_address==`R1) ? r1 :
(A_address==`R2) ? r2 :
(A_address==`R3) ? r3 : 16'b0;
// Bポート出力(ALUへ)
assign out_dataB = (B_address==`P_OUT) ? p_out_reg :
(B_address==`P_IN) ? port_in_data :
(B_address==`R0) ? r0 :
(B_address==`R1) ? r1 :
(B_address==`R2) ? r2 :
(B_address==`R3) ? r3 : 16'b0;
// 出力ポート(そのまま出力)
assign port_out_data = p_out_reg;
endmodule
// ALU
module alu(input [4:0] command,
input [3:0] jump_cond,
input [15:0] portA,portB,
output reg [15:0] alu_out);
always @(*) begin
case (command)
`ADD_com: alu_out=portA+portB;
`SUB_com: alu_out=portA-portB;
`SHIFT_L_com: alu_out=portB << 1;
`SHIFT_R_com: alu_out=portB >> 1;
`BSHIFT_L_com:alu_out=portA << portB;
`BSHIFT_R_com:alu_out=portA >> portB;
`MUL_com: alu_out=portA * portB;
`AND_com: alu_out=portA & portB;
`OR_com: alu_out=portA | portB;
`XOR_com: alu_out=portA ^ portB;
`NOT_com: alu_out=~portA;
`JMP_com:
case (jump_cond)
`Eq: alu_out=(portA == portB);
`Not_Eq: alu_out=(portA != portB);
`Always: alu_out=1;
`LT: if (portA < portB)
alu_out=1;
else
alu_out=0;
`GT: if (portA > portB)
alu_out=1;
else
alu_out=0;
`LTE: if (portA <= portB)
alu_out=1;
else
alu_out=0;
`GTE: if (portA >= portB)
alu_out=1;
else
alu_out=0;
default: alu_out=0;
endcase
default: alu_out=portA+portB;
endcase
end
endmodule
module rom(input [15:0] rom_address,
output [39:0] Data);
assign Data = romdata(rom_address);
//TAK start
wire [4:0] command = Data[39:35];
wire [3:0] A_address = Data[34:31];
wire [3:0] B_address = Data[30:27];
wire [3:0] write_address = Data[26:23];
wire [3:0] jump_cond = Data[22:19];
wire [15:0] im_val=Data[15:0];
wire [2:0] loop_cond=Data[18:16];
//TAK end
// コマンド
parameter [4:0] Set=`SET_com,
Add=`ADD_com,
Sub=`SUB_com,
Shift_L=`SHIFT_L_com,
Shift_R=`SHIFT_R_com,
Jmp=`JMP_com,
Sync=`SYNC_com,
Mul=`MUL_com,
And=`AND_com,
Or =`OR_com,
Xor=`XOR_com,
Not=`NOT_com,
BShift_L=`BSHIFT_L_com,
BShift_R=`BSHIFT_R_com;
parameter [4:0] \セット =`SET_com,
\加算 =`ADD_com,
\減算 =`SUB_com,
\左シフト =`SHIFT_L_com,
\右シフト =`SHIFT_R_com,
\ジャンプ =`JMP_com,
\シンク命令 =`SYNC_com,
\乗算 =`MUL_com,
\& =`AND_com,
\OR =`OR_com,
\XOR =`XOR_com,
\NOT =`NOT_com,
\左バレルシフト =`BSHIFT_L_com,
\右バレルシフト =`BSHIFT_R_com;
// レジスタアドレス
parameter [3:0] P_OUT=`P_OUT,//IOポート出力
P_IN=`P_IN, //IOポート入力
R0= `R0,
R1= `R1,
R2= `R2,
R3= `R3,
ZR= `ZR; // ゼロレジスタ (= ゼロGND)
// ジャンプ条件
parameter [3:0] Eq=`Eq,
Not_Eq=`Not_Eq,
Always=`Always,
LT=`LT,
GT=`GT,
LTE=`LTE,
GTE=`GTE;
// ループ処理
parameter [2:0] Reserved = 3'b000,
LoopEnabled = 3'b001;
// マクロ命令
// NOP
`define NOP romdata={Add,ZR,ZR,ZR,Eq,Reserved,16'h 0000}
// コピー : r0 → r1
`define MOV(r0,r1) romdata={Add,r0,ZR,r1,Eq,Reserved,16'h 0000}
// クリア : r0 = 0
`define CLR(r0) romdata={Add,ZR,ZR,r0,Eq,Reserved,16'h 0000}
// 定数代入 : r0 = num
`define SET(r0,num) romdata={Set,P_IN,ZR,r0,Eq,Reserved,16'd num}
// ジャンプ
`define JUMP(num) romdata={Jmp,ZR,ZR,ZR,Always,Reserved,16'd num}
// 論理反転
`define NOT(r0,r1) romdata={Not,r0,ZR,r1,Always,Reserved,16'h 0000}
// 足し算 : r2 = r0 + r1
`define ADD(r0,r1,r2) romdata={Add,r0,r1,r2,Eq,Reserved,16'h 0000}
// 引き算 : r2 = r0 - r1
`define SUB(r0,r1,r2) romdata={Sub,r0,r1,r2,Eq,Reserved,16'h 0000}
// 乗算 : r2 = r0 * r1
`define MUL(r0,r1,r2) romdata={Mul,r0,r1,r2,Eq,Reserved,16'h 0000}
// 条件文 : if(r0<r1) goto num;
`define JUMP_IF_LT(r0,r1,num) romdata={Jmp,r0,r1,ZR,LT,Reserved,16'd num}
// 条件文 : if(r0>r1) goto num;
`define JUMP_IF_GT(r0,r1,num) romdata={Jmp,r0,r1,ZR,GT,Reserved,16'd num}
// 条件文 : if(r0<=r1) goto num;
`define JUMP_IF_LTE(r0,r1,num) romdata={Jmp,r0,r1,ZR,LTE,Reserved,16'd num}
// 条件文 : if(r0>=r1) goto num;
`define JUMP_IF_GTE(r0,r1,num) romdata={Jmp,r0,r1,ZR,GTE,Reserved,16'd num}
// 条件文 : if(r0==r1) goto num;
`define JUMP_IF_EQ(r0,r1,num) romdata={Jmp,r0,r1,ZR,Eq,Reserved,16'd num}
// 1ビットシフト
`define SHIFT_L(r0,r1) romdata={Shift_L,ZR,r0,r1,Eq,Reserved,16'h 0000}
`define SHIFT_R(r0,r1) romdata={Shift_R,ZR,r0,r1,Eq,Reserved,16'h 0000}
// バレルシフト
`define BSHIFT_L(r0,r1,r2) romdata={BShift_L,r0,r1,r2,Eq,Reserved,16'h 0000}
`define BSHIFT_R(r0,r1,r2) romdata={BShift_R,r0,r1,r2,Eq,Reserved,16'h 0000}
// 外部入力・立ち上がりエッジまで待つ
`define SYNC romdata={Sync,ZR,ZR,ZR,Eq,Reserved,16'h 0000}
// ループ繰り返し命令(JUMP命令分の1クロックを節約)
// ex : r2 = r0 + r1 を num+1 回繰り返す。その間、pc は 現アドレスで待機。
`define ADD_LOOP(r0,r1,r2,num) romdata={Add,r0,r1,r2,Eq,LoopEnabled,16'd num}
`define SUB_LOOP(r0,r1,r2,num) romdata={Sub,r0,r1,r2,Eq,LoopEnabled,16'd num}
`define MUL_LOOP(r0,r1,r2,num) romdata={Mul,r0,r1,r2,Eq,LoopEnabled,16'd num}
`define SHIFT_L_LOOP(r0,num) romdata={Shift_L,ZR,r0,r0,Eq,LoopEnabled,16'd num}
`define SHIFT_R_LOOP(r0,num) romdata={Shift_R,ZR,r0,r0,Eq,LoopEnabled,16'd num}
//Tak start 逆アセンブラ
reg [8*40:1] str;
function [8*3:1] get_rn(input [3:0] addr);
case (addr)
P_OUT: get_rn="OUT";
P_IN: get_rn="IN"; //IOポート入力
R0: get_rn="R0";
R1: get_rn="R1";
R2: get_rn="R2";
R3: get_rn="R3";
ZR: get_rn="ZR"; // ゼロレジスタ (= ゼロGND)
default: get_rn="???";
endcase
endfunction
always @* begin
case (command)
Add:
if(loop_cond==0) begin
if (A_address==ZR && B_address==ZR & write_address==ZR) $sformat(str,"NOP");
else if (B_address==ZR && A_address==ZR ) $sformat(str,"%sをクリア",get_rn(write_address));
else if (B_address==ZR ) $sformat(str,"%sから%sへコピー",get_rn(A_address),get_rn(write_address));
else $sformat(str,"%s=%s + %s",get_rn(write_address),get_rn(A_address),get_rn(B_address));
end else begin
if (A_address==ZR && B_address==ZR & write_address==ZR) $sformat(str,"NOP %dループ付",im_val);
else if (B_address==ZR && A_address==ZR ) $sformat(str,"%sをクリア %dループ付",get_rn(write_address),im_val);
else if (B_address==ZR ) $sformat(str,"%sから%sへコピー %dループ付",get_rn(A_address),get_rn(write_address),im_val);
else $sformat(str,"%s=%s + %s %dループ付",get_rn(write_address),get_rn(A_address),get_rn(B_address),im_val);
end
Set: $sformat(str,"%s=%h[Hex]",get_rn(write_address),im_val);
Mul:
if (loop_cond==0) begin
$sformat(str,"%s=%s * %s",get_rn(write_address),get_rn(A_address),get_rn(B_address));
end else $sformat(str,"%s=%s * %s %dループ付",get_rn(write_address),get_rn(A_address),get_rn(B_address),im_val);
Sub:
if (loop_cond==0) begin
$sformat(str,"%s=%s - %s",get_rn(write_address),get_rn(A_address),get_rn(B_address));
end else $sformat(str,"%s=%s - %s %dループ付",get_rn(write_address),get_rn(A_address),get_rn(B_address),im_val);
Sync:
$sformat(str,"シンク命令");
Not: $sformat(str,"%s=~%s",get_rn(write_address),get_rn(A_address));
Jmp:
if (jump_cond==Always) $sformat(str,"%d番地へジャンプ",im_val);
else if (jump_cond==LT) $sformat(str,"%s < %s なら%d番地へジャンプ",get_rn(A_address),get_rn(B_address),im_val);
else if (jump_cond==GT) $sformat(str,"%s > %s なら%d番地へジャンプ",get_rn(A_address),get_rn(B_address),im_val);
else if (jump_cond==LTE) $sformat(str,"%s <= %s なら%d番地へジャンプ",get_rn(A_address),get_rn(B_address),im_val);
else if (jump_cond==GTE) $sformat(str,"%s >= %s なら%d番地へジャンプ",get_rn(A_address),get_rn(B_address),im_val);
else if (jump_cond==Eq) $sformat(str,"%s == %s なら%d番地へジャンプ",get_rn(A_address),get_rn(B_address),im_val);
Shift_L:
if (loop_cond==0) $sformat(str,"%s=%s << 1",get_rn(write_address),get_rn(B_address));
else $sformat(str,"%s=%s << %d",get_rn(write_address),get_rn(B_address),im_val);
Shift_R:
if (loop_cond==0) $sformat(str,"%s=%s >> 1",get_rn(write_address),get_rn(B_address));
else $sformat(str,"%s=%s >> %d",get_rn(write_address),get_rn(B_address),im_val);
BShift_L:
$sformat(str,"%s=%s <<%s",get_rn(write_address),get_rn(A_address),get_rn(B_address));
BShift_R:
$sformat(str,"%s=%s >> %s",get_rn(write_address),get_rn(A_address),get_rn(B_address));
endcase
end
//Tak end
// マイクロコード
function [39:0]romdata;
input [15:0] address;
case(address)
//Commad A_addr, B_addr, Write_addr, Jmp_cond, Reserved, IM
0: `SET(P_OUT, 100);
1: `SET(R0, 5);
2: `MOV(R0, R1);
3: `MOV(R1, R2);
4: `MOV(R2, R3);
5: `SUB(R1, R2, R0);
6: `SUB(R3, R1, R3);
7: `ADD(P_IN, P_OUT, P_OUT);
8: `ADD(P_OUT, P_OUT, P_OUT);
9: `SUB(P_IN, P_IN, R1);
10: `SUB(P_OUT, P_IN, R2);
11: `SET(R0, 2);
12: `SET(R1, 8);
13: `MUL(R0, R1, R3);
14: `NOP;
15: `MUL(R3, R3, R3);
16: `ADD(P_OUT, R3, P_OUT);
17: `SYNC;
18: `JUMP(1000);
// シフト・乗算
1000: `SET(R0, 5);
1001: `SET(R1, 2);
1002: `ADD_LOOP(R0, R1, R0, 10);
1003: `SHIFT_L_LOOP(R1, 5);
1004: `SHIFT_R_LOOP(R1, 5);
1005: `SUB_LOOP(R0, R1, R0, 10);
1006: `SET(P_OUT, 5);
1007: `MUL_LOOP(P_OUT, R1, P_OUT, 7);
1008: `JUMP(1500);
// 条件文
1500: `SET(R0, 16);
1501: `SET(R1, 2);
1502: `SET(P_OUT, 0);
1503: `SUB(R0, R1, R0);
1504: `JUMP_IF_GT(R0, R1 ,1503);
// 条件文
1505: `SET(R2, 16);
1506: `SET(R3, 2);
1507: `SET(P_OUT, 0);
1509: `SUB(R2, R3, R2);
1511: `JUMP_IF_GTE(R2, R3 ,1509);
// 1010_1010_1010_1010
// ビット反転
1512: `SET(R0, 43690);
1513: `NOT(R0, R1);
// バレルシフタ
1514: `SET(R0, 2);
1515: `SET(R1, 7);
1516: `SET(R2, 0);
1517: `BSHIFT_L(R0, R1, R0);
1518: `BSHIFT_R(R0, R1, R0);
1519: `JUMP(0);
default: romdata = 0;
endcase
endfunction
endmodule
アーカイブです。
なお、記述の動作確認としてQuartasUで合成をしてみました。デバイスはStratix2
速度は、72MHzでした。

内部動作の確認をしたい為に、pcとrom_dataを引き出しています。
`Debug で、切り替えしています。
`define Debug
// CPU
module cpu(input clock, reset,
input [15:0] port_in_data,
input sync,
output [15:0] port_out_data
`ifdef Debug
,
output [39:0] rom_data,
output reg [15:0] pc
`endif
);
localparam integer Loop_counter_width=16;
localparam integer Stop_count = 2**Loop_counter_width -1;
reg [Loop_counter_width-1:0] loop_counter;
`ifndef Debug
reg [15:0] pc;
`endif
// reg [15:0] port_in_reg;
reg sync_ff, sync_ff2;
/*----------------------------------------------------------------------------
ROMワイヤーマップ
ビット位置 ビット幅 内容
39:35 5 命令
34:31 4 レジスタファイルAポートのアドレス
30:27 4 レジスタファイルBポートのアドレス
26:23 4 レジスタファイルライトポートのアドレス
22:19 4 ジャンプ比較条件
18:16 3 将来拡張用
16:16 1 loop_flag ループ処理するかどうかのフラグ、1でループ処理
15:0 16 即値または、飛び先
-----------------------------------------------------------------------------*/
`ifndef Debug
wire [39:0] rom_data;
`endif
シミュレーションの最後ところです。青色が遅延SIMで、トップ階層の信号をVCDで記録したものです。
さすがにrom_dataの遅延が大きくなっていますが、RTL SIMと一致しています。
これで、動作の検証は終了です。

このCPUを
spartan3でも合成してみました。速度は40MHzです。

遅延シミュレーションです。StratixUと比べるとやはり遅延は大きくなります。

シンクロナスROMの実装
現在のFPGAのRAM/ROMは、クロックに同期して動作するタイプが主流ですので、今までのRTL SIMからその辺を修正する必要があります。
AlteraのROM作成
Mega−Wizardで生成します。
下のように、address_latachがdefault で付いてしまいます。この対応については後述します。
ビット幅は、40ビット、ワード数は、とりあえず現実的な値として2Kワードを指定します。

後段の出力は、レジスタなしにします。

ROMの初期化は、hexファイルを指定します。(hexファイルは後述)

以上で、Wizard終了です。

さて、問題は、プログラムカウンタPCとRAM内のアドレスラッチ動作がダブっていることです。タダで1CLK遅れてしまうので面白くありません。そこで、ROMに渡すPCは、レジスタ出力ではなく、組み合わせ回路のまま、ROMに渡します。
Xilinx版も同様で、pc_comを渡します。
// ROM
`ifdef ALTERA
micro_rom rom(.address(pc_com),.q(rom_data),.clock(clock));//ROM にはpcではなくpc_comを渡す形に変更
`else
`ifdef XILINX
micro_rom_xilinx rom(.addr(pc_com),.dout(rom_data),.clk(clock));//ROM にはpcではなくpc_comを渡す形に変更
`else
rom rom(.rom_address(pc_com),.Data(rom_data),.clock(clock));//ROM にはpcではなくpc_comを渡す形に変更
`endif
`endif
pc_comは、レジスタpcにする前の組み合わせ回路で、以下のようになります。
ここで、pc_comは、clockに対して同期した信号のみで構成することに注意します。FPGA内部は、非同期リセットのFFを除き、全部同期した信号で構成することが重要です。resetは、非同期なので、clockで、同期化した信号にします。
// マイクロプログラムカウンタ
reg reset_sync;
always @(clock) reset_sync<=reset;//同期化 pc組み合わせ回路は、同期化したものが必要
//pc 組み合わせ回路
//Altera sync rom は、必ずAddressラッチがついてしまうので、
//pcを渡してしまうのは、1CLK DELAYになってしまう。それを防ぐ
//ために次アドレスを組み合わせ回路で渡す。メモリ内ラッチはPCとDuplicate動作する
reg [15:0] pc_com;
always @* begin
if(reset_sync)
begin
pc_com= 0;
end
// ジャンプ命令処理
else if(command==`JMP_com && alu_data[0]==1'b1)
begin
//飛んでいけ
pc_com= immediate_data;
end
else if(command==`SYNC_com)
begin //同期待ちスタート処理
if(sync_pos_edge_detected)
begin
//同期信号を検出。次のアドレスを実行開始。
pc_com= pc + 1;
end
else
begin
//同期エッジが来るまで待つ
pc_com= pc;
end
end
else if(loop_flag)
begin //ループ処理
if(Reset_loop_counter)
begin
//ループ処理から抜ける
pc_com= pc + 1;
end
else
begin
//ダウンカウント中は待つ
pc_com= pc;
end
end
else
begin
// 何もない時は次のアドレスを実行開始
pc_com= pc + 1;
end
end
always @(posedge clock, posedge reset) begin
if(reset)
begin
pc <= 0;
end
else pc<=pc_com;
end
このようにすれば、RAM内のアドレスラッチは、PCのように振るまうので、今までのRTL SIMと同じタイミングで動作することになります。
XilinxのROM
coregenで、1ポートROMを生成します。

なにも指定せずに進みます。

ROMの初期化ファイルは、rom.coe(後述)を指定します。

ROM初期化ファイルの生成
Altera、Xilinx共専用の初期化ファイルが必要になります。これから先、いちいち他のツールで変換するのは、面倒なので、Verilog HDLソース中、RTL SIM時に自動生成するようにします。
HEXファイルの生成タスクです。micro_romの内容をINTEL HEXファイル(rom.hex)にするタスクです。
//memory の内容をAltera ROM初期化用に
//intel-hexフォーマットに変換する
//ファイル->rom.hex として出力する
//address 64K word max
//zxを含まないこと
task make_intel_hex(input [7:0] bytes_per_word,
input integer address_max);
reg [9*8:1] str;
integer fi;
reg [15:0] ad;
integer bytes;
reg [7:0] parity;
begin
fi=$fopen("rom.hex","w");
for (ad=0;ad <=address_max;ad=ad+1) begin
$sformat(str,":%2h%4h00",bytes_per_word,ad);
parity=bytes_per_word;
parity =parity + ad[8*1 +:8];//チェックサム
parity =parity + ad[0 +:8];//チェックサム
$fwrite(fi,"%s",str);
for (bytes=0;bytes<bytes_per_word;bytes=bytes+1) begin: byte_loop
reg [7:0] byte;
byte=micro_rom[ad][(bytes_per_word-1-bytes)*8 +:8];
parity=parity+byte;//チェックサム
$fwrite(fi,"%2h",byte);
end
parity=~parity +8'h01;//チェックサム
$fwrite(fi,"%2h",parity);
$fwrite(fi,"\n");
end
$fdisplay(fi,":00000001FF");
$fclose(fi);
str="rom.ver";
//$convert_hex2ver("rom.hex",40,str);//チェックVeritak Uniqueファンクション
end
endtask
micro_romの内容をXILINX Coeファイルにするタスクです。
//xilinx coe file
task make_coe_file(input [7:0] bytes_per_word,
input integer address_max);
integer fi;
integer ad;
begin
fi=$fopen("rom.coe","w");
$fdisplay(fi,"memory_initialization_radix=16;");
$fdisplay(fi,"memory_initialization_vector=");
for (ad=0;ad <address_max;ad=ad+1) begin
$fdisplay(fi,"%h,",micro_rom[ad]);
end
$fdisplay(fi,"%h;",micro_rom[address_max]);
$fclose(fi);
end
endtask
以上のタスクをRTL SIM時に生成するようにします。
// マイクロコード
`define PHY_ADD_MAX (1024*2-1)
//ALTERA ROMの模擬コード
reg [39:0] micro_rom [0:`PHY_ADD_MAX];
reg [15:0] address_latch;
always @(posedge clock) address_latch<=rom_address;
// always @(posedge clock ) begin//q latch 出力付の場合、将来パイプライン化したときに必要になる。
always @* begin
Data=micro_rom[address_latch];
end
`ifdef RTL_SIM
integer i;
initial begin
for (i=0;i<= `PHY_ADD_MAX;i=i+1) begin
micro_rom[i]=romdata(i);
end
make_intel_hex(5,`PHY_ADD_MAX);//RTL SIM時にINTEL HEXファイルを生成する Altera 用
make_coe_file(5,`PHY_ADD_MAX);//RTL SIM時にXilinx用 Coeファイルを生成する
end
`endif
以上で、シンクロナスROM実装への変更が終わりました。
XILINX,ALTERAの切り替えは、冒頭のDefineの定義の有無です。RTL シミュレーションの場合は、Veritak ProjectにDefineを入れ込めば指定しなくてもよいのですが、合成する場合は、それぞれ定義を変えます。

今回の全体ソースです。
// マイクロプログラム方式 CPU
// ネイティブコマンド
`define ADD_com 5'b00000
`define SET_com 5'b00001
`define SUB_com 5'b00010
`define SHIFT_L_com 5'b00011
`define SHIFT_R_com 5'b00100
`define JMP_com 5'b00101
`define SYNC_com 5'b00110
`define AND_com 5'b00111
`define OR_com 5'b01000
`define XOR_com 5'b01001
`define NOT_com 5'b01010
`define MUL_com 5'b01011
`define BSHIFT_L_com 5'b01100
`define BSHIFT_R_com 5'b01101
// ジャンプ条件
`define Eq 4'b0000
`define Not_Eq 4'b0001
`define Always 4'b0010
`define LT 4'b0011
`define GT 4'b0100
`define LTE 4'b0101
`define GTE 4'b0110
// レジスタアドレス
`define ZR 4'b0000
`define R0 4'b0001
`define R1 4'b0010
`define R2 4'b0011
`define R3 4'b0100
`define P_IN 4'b0101
`define P_OUT 4'b0110
`define Debug
//`define ALTERA //Altera の場合コメントを取りXILINXをコメントアウト
//`define XILINX //Xilinxの場合コメントを取りALERAをコメントアウト
// CPU
module cpu(input clock, reset,
input [15:0] port_in_data,
input sync,
output [15:0] port_out_data
`ifdef Debug
,
output [39:0] rom_data,
output reg [15:0] pc
`endif
);
localparam integer Loop_counter_width=16;
localparam integer Stop_count = 2**Loop_counter_width -1;
reg [Loop_counter_width-1:0] loop_counter;
`ifndef Debug
reg [15:0] pc;
reg [15:0] pc_com;//組み合わせロジック
`endif
// reg [15:0] port_in_reg;
reg sync_ff, sync_ff2;
/*----------------------------------------------------------------------------
ROMワイヤーマップ
ビット位置 ビット幅 内容
39:35 5 命令
34:31 4 レジスタファイルAポートのアドレス
30:27 4 レジスタファイルBポートのアドレス
26:23 4 レジスタファイルライトポートのアドレス
22:19 4 ジャンプ比較条件
18:16 3 将来拡張用
16:16 1 loop_flag ループ処理するかどうかのフラグ、1でループ処理
15:0 16 即値または、飛び先
-----------------------------------------------------------------------------*/
`ifndef Debug
wire [39:0] rom_data;
`endif
wire [4:0] command = rom_data[39:35];
wire [3:0] A_address = rom_data[34:31];
wire [3:0] B_address = rom_data[30:27];
wire [3:0] write_address = rom_data[26:23];
wire [3:0] jump_cond = rom_data[22:19];
wire [2:0] reserved = rom_data[18:16];
wire [15:0] immediate_data = rom_data[15:0];
wire loop_flag = reserved[0];
wire [15:0] out_dataA, out_dataB;
wire [15:0] alu_data;
// SETコマンドの時は、"Aポートに代入する値" を接続する。
// ※ ALUで ゼロレジスタと加算して代入する事で、SETを実現する為。
wire [15:0] Aport_data = (command==`SET_com) ? immediate_data : port_in_data;
//ループカウンタリセット条件
wire Reset_loop_counter = (loop_counter<2)||
(loop_counter==Stop_count && immediate_data == 16'b0)
? 1 : 0;
//ループカウンタの処理
always @(posedge clock, posedge reset) begin
if(reset)
begin
loop_counter <= Stop_count;
end
else if(Reset_loop_counter)
begin
loop_counter <= Stop_count;
end
else if(loop_flag && loop_counter==Stop_count)
begin
// ループカウント値をセット
loop_counter <= immediate_data;
end
//else if ((loop_flag) && (loop_counter>1))
else if(loop_flag)
begin
// ループ処理
loop_counter <= loop_counter - 16'b1;
end
else
begin
loop_counter <= Stop_count;
end
end
// 入力同期エッジ検出
always @(posedge clock, posedge reset) begin
if (reset) sync_ff<=0;
else sync_ff<=sync;
end
always @(posedge clock, posedge reset) begin
if (reset) sync_ff2<=0;
else sync_ff2<=sync_ff;
end
// 立ち上がりエッジを検出
wire sync_pos_edge_detected = sync_ff & ~sync_ff2;
// マイクロプログラムカウンタ
reg reset_sync;
always @(clock) reset_sync<=reset;//同期化 pc組み合わせ回路は、同期化したものが必要
//pc 組み合わせ回路
//Altera sync rom は、必ずAddressラッチがついてしまうので、
//pcを渡してしまうのは、1CLK DELAYになってしまう。それを防ぐ
//ために次アドレスを組み合わせ回路で渡す。メモリ内ラッチはPCとDuplicate動作する
reg [15:0] pc_com;
always @* begin
if(reset_sync)
begin
pc_com= 0;
end
// ジャンプ命令処理
else if(command==`JMP_com && alu_data[0]==1'b1)
begin
//飛んでいけ
pc_com= immediate_data;
end
else if(command==`SYNC_com)
begin //同期待ちスタート処理
if(sync_pos_edge_detected)
begin
//同期信号を検出。次のアドレスを実行開始。
pc_com= pc + 1;
end
else
begin
//同期エッジが来るまで待つ
pc_com= pc;
end
end
else if(loop_flag)
begin //ループ処理
if(Reset_loop_counter)
begin
//ループ処理から抜ける
pc_com= pc + 1;
end
else
begin
//ダウンカウント中は待つ
pc_com= pc;
end
end
else
begin
// 何もない時は次のアドレスを実行開始
pc_com= pc + 1;
end
end
always @(posedge clock, posedge reset) begin
if(reset)
begin
pc <= 0;
end
else pc<=pc_com;
end
// レジスタファイル
register_file register_file(.clock(clock),
.reset(reset),
.A_address(A_address),
.B_address(B_address),
.write_address(write_address),
.alu_data(alu_data),
.out_dataA(out_dataA),
.out_dataB(out_dataB),
.port_in_data(Aport_data),
.port_out_data(port_out_data));
// ALU
alu alu(.command(command),
.jump_cond(jump_cond),
.portA(out_dataA),
.portB(out_dataB),
.alu_out(alu_data));
// ROM
`ifdef ALTERA
micro_rom rom(.address(pc_com),.q(rom_data),.clock(clock));//ROM にはpcではなくpc_comを渡す形に変更
`else
`ifdef XILINX
micro_rom_xilinx rom(.addr(pc_com),.dout(rom_data),.clk(clock));//ROM にはpcではなくpc_comを渡す形に変更
`else
rom rom(.rom_address(pc_com),.Data(rom_data),.clock(clock));//ROM にはpcではなくpc_comを渡す形に変更
`endif
`endif
endmodule
// レジスタファイル
// 記憶素子をレジスタの集まりとして、まとめてモジュール化。
// 同時に読み書きできるポートが3つ
// ライトポートが1つ、リードポートが2つ。
module register_file(input clock,
input reset,
input [3:0] A_address,
input [3:0] B_address,
input [3:0] write_address,
input [15:0] alu_data, port_in_data,
output [15:0] out_dataA, out_dataB, port_out_data);
// CPUレジスタ
reg [15:0] p_out_reg, r0, r1, r2, r3;
// ポートアウトレジスタ
always @(posedge clock, posedge reset) begin
if(reset)
begin
p_out_reg <= 0;
end
else if(write_address == `P_OUT)
begin
p_out_reg <= alu_data;
end
else
begin
p_out_reg <= p_out_reg;
end
end
// 汎用レジスタR0
always @(posedge clock, posedge reset) begin
if(reset)
begin
r0 <= 0;
end
else if(write_address == `R0)
begin
r0 <= alu_data;
end
else
begin
r0 <= r0;
end
end
// 汎用レジスタR1
always @(posedge clock, posedge reset) begin
if(reset)
begin
r1 <= 0;
end
else if(write_address == `R1)
begin
r1 <= alu_data;
end
else
begin
r1 <= r1;
end
end
// 汎用レジスタR2
always @(posedge clock, posedge reset) begin
if(reset)
begin
r2 <= 0;
end
else if(write_address == `R2)
begin
r2 <= alu_data;
end
else
begin
r2 <= r2;
end
end
// 汎用レジスタR3
always @(posedge clock, posedge reset) begin
if(reset)
begin
r3 <= 0;
end
else if(write_address == `R3)
begin
r3 <= alu_data;
end
else
begin
r3 <= r3;
end
end
// Aポート出力(ALUへ)
assign out_dataA = (A_address==`P_OUT) ? p_out_reg :
(A_address==`P_IN) ? port_in_data :
(A_address==`R0) ? r0 :
(A_address==`R1) ? r1 :
(A_address==`R2) ? r2 :
(A_address==`R3) ? r3 : 16'b0;
// Bポート出力(ALUへ)
assign out_dataB = (B_address==`P_OUT) ? p_out_reg :
(B_address==`P_IN) ? port_in_data :
(B_address==`R0) ? r0 :
(B_address==`R1) ? r1 :
(B_address==`R2) ? r2 :
(B_address==`R3) ? r3 : 16'b0;
// 出力ポート(そのまま出力)
assign port_out_data = p_out_reg;
endmodule
// ALU
module alu(input [4:0] command,
input [3:0] jump_cond,
input [15:0] portA,portB,
output reg [15:0] alu_out);
always @(*) begin
case (command)
`ADD_com: alu_out=portA+portB;
`SUB_com: alu_out=portA-portB;
`SHIFT_L_com: alu_out=portB << 1;
`SHIFT_R_com: alu_out=portB >> 1;
`BSHIFT_L_com:alu_out=portA << portB;
`BSHIFT_R_com:alu_out=portA >> portB;
`MUL_com: alu_out=portA * portB;
`AND_com: alu_out=portA & portB;
`OR_com: alu_out=portA | portB;
`XOR_com: alu_out=portA ^ portB;
`NOT_com: alu_out=~portA;
`JMP_com:
case (jump_cond)
`Eq: alu_out=(portA == portB);
`Not_Eq: alu_out=(portA != portB);
`Always: alu_out=1;
`LT: if (portA < portB)
alu_out=1;
else
alu_out=0;
`GT: if (portA > portB)
alu_out=1;
else
alu_out=0;
`LTE: if (portA <= portB)
alu_out=1;
else
alu_out=0;
`GTE: if (portA >= portB)
alu_out=1;
else
alu_out=0;
default: alu_out=0;
endcase
default: alu_out=portA+portB;
endcase
end
endmodule
module rom(input [15:0] rom_address,
input clock,//シンクロナスROMに変更
output reg [39:0] Data);
//assign Data = romdata(rom_address);//シンクロナスROMに変更
//TAK start
wire [4:0] command = Data[39:35];
wire [3:0] A_address = Data[34:31];
wire [3:0] B_address = Data[30:27];
wire [3:0] write_address = Data[26:23];
wire [3:0] jump_cond = Data[22:19];
wire [15:0] im_val=Data[15:0];
wire [2:0] loop_cond=Data[18:16];
//TAK end
// コマンド
parameter [4:0] Set=`SET_com,
Add=`ADD_com,
Sub=`SUB_com,
Shift_L=`SHIFT_L_com,
Shift_R=`SHIFT_R_com,
Jmp=`JMP_com,
Sync=`SYNC_com,
Mul=`MUL_com,
And=`AND_com,
Or =`OR_com,
Xor=`XOR_com,
Not=`NOT_com,
BShift_L=`BSHIFT_L_com,
BShift_R=`BSHIFT_R_com;
parameter [4:0] \セット =`SET_com,
\加算 =`ADD_com,
\減算 =`SUB_com,
\左シフト =`SHIFT_L_com,
\右シフト =`SHIFT_R_com,
\ジャンプ =`JMP_com,
\シンク命令 =`SYNC_com,
\乗算 =`MUL_com,
\& =`AND_com,
\OR =`OR_com,
\XOR =`XOR_com,
\NOT =`NOT_com,
\左バレルシフト =`BSHIFT_L_com,
\右バレルシフト =`BSHIFT_R_com;
// レジスタアドレス
parameter [3:0] P_OUT=`P_OUT,//IOポート出力
P_IN=`P_IN, //IOポート入力
R0= `R0,
R1= `R1,
R2= `R2,
R3= `R3,
ZR= `ZR; // ゼロレジスタ (= ゼロGND)
// ジャンプ条件
parameter [3:0] Eq=`Eq,
Not_Eq=`Not_Eq,
Always=`Always,
LT=`LT,
GT=`GT,
LTE=`LTE,
GTE=`GTE;
// ループ処理
parameter [2:0] Reserved = 3'b000,
LoopEnabled = 3'b001;
// マクロ命令
// NOP
`define NOP romdata={Add,ZR,ZR,ZR,Eq,Reserved,16'h 0000}
// コピー : r0 → r1
`define MOV(r0,r1) romdata={Add,r0,ZR,r1,Eq,Reserved,16'h 0000}
// クリア : r0 = 0
`define CLR(r0) romdata={Add,ZR,ZR,r0,Eq,Reserved,16'h 0000}
// 定数代入 : r0 = num
`define SET(r0,num) romdata={Set,P_IN,ZR,r0,Eq,Reserved,16'd num}
// ジャンプ
`define JUMP(num) romdata={Jmp,ZR,ZR,ZR,Always,Reserved,16'd num}
// 論理反転
`define NOT(r0,r1) romdata={Not,r0,ZR,r1,Always,Reserved,16'h 0000}
// 足し算 : r2 = r0 + r1
`define ADD(r0,r1,r2) romdata={Add,r0,r1,r2,Eq,Reserved,16'h 0000}
// 引き算 : r2 = r0 - r1
`define SUB(r0,r1,r2) romdata={Sub,r0,r1,r2,Eq,Reserved,16'h 0000}
// 乗算 : r2 = r0 * r1
`define MUL(r0,r1,r2) romdata={Mul,r0,r1,r2,Eq,Reserved,16'h 0000}
// 条件文 : if(r0<r1) goto num;
`define JUMP_IF_LT(r0,r1,num) romdata={Jmp,r0,r1,ZR,LT,Reserved,16'd num}
// 条件文 : if(r0>r1) goto num;
`define JUMP_IF_GT(r0,r1,num) romdata={Jmp,r0,r1,ZR,GT,Reserved,16'd num}
// 条件文 : if(r0<=r1) goto num;
`define JUMP_IF_LTE(r0,r1,num) romdata={Jmp,r0,r1,ZR,LTE,Reserved,16'd num}
// 条件文 : if(r0>=r1) goto num;
`define JUMP_IF_GTE(r0,r1,num) romdata={Jmp,r0,r1,ZR,GTE,Reserved,16'd num}
// 条件文 : if(r0==r1) goto num;
`define JUMP_IF_EQ(r0,r1,num) romdata={Jmp,r0,r1,ZR,Eq,Reserved,16'd num}
// 1ビットシフト
`define SHIFT_L(r0,r1) romdata={Shift_L,ZR,r0,r1,Eq,Reserved,16'h 0000}
`define SHIFT_R(r0,r1) romdata={Shift_R,ZR,r0,r1,Eq,Reserved,16'h 0000}
// バレルシフト
`define BSHIFT_L(r0,r1,r2) romdata={BShift_L,r0,r1,r2,Eq,Reserved,16'h 0000}
`define BSHIFT_R(r0,r1,r2) romdata={BShift_R,r0,r1,r2,Eq,Reserved,16'h 0000}
// 外部入力・立ち上がりエッジまで待つ
`define SYNC romdata={Sync,ZR,ZR,ZR,Eq,Reserved,16'h 0000}
// ループ繰り返し命令(JUMP命令分の1クロックを節約)
// ex : r2 = r0 + r1 を num+1 回繰り返す。その間、pc は 現アドレスで待機。
`define ADD_LOOP(r0,r1,r2,num) romdata={Add,r0,r1,r2,Eq,LoopEnabled,16'd num}
`define SUB_LOOP(r0,r1,r2,num) romdata={Sub,r0,r1,r2,Eq,LoopEnabled,16'd num}
`define MUL_LOOP(r0,r1,r2,num) romdata={Mul,r0,r1,r2,Eq,LoopEnabled,16'd num}
`define SHIFT_L_LOOP(r0,num) romdata={Shift_L,ZR,r0,r0,Eq,LoopEnabled,16'd num}
`define SHIFT_R_LOOP(r0,num) romdata={Shift_R,ZR,r0,r0,Eq,LoopEnabled,16'd num}
//Tak start 逆アセンブラ
reg [8*40:1] str;
function [8*3:1] get_rn(input [3:0] addr);
case (addr)
P_OUT: get_rn="OUT";
P_IN: get_rn="IN"; //IOポート入力
R0: get_rn="R0";
R1: get_rn="R1";
R2: get_rn="R2";
R3: get_rn="R3";
ZR: get_rn="ZR"; // ゼロレジスタ (= ゼロGND)
default: get_rn="???";
endcase
endfunction
always @* begin
case (command)
Add:
if(loop_cond==0) begin
if (A_address==ZR && B_address==ZR & write_address==ZR) $sformat(str,"NOP");
else if (B_address==ZR && A_address==ZR ) $sformat(str,"%sをクリア",get_rn(write_address));
else if (B_address==ZR ) $sformat(str,"%sから%sへコピー",get_rn(A_address),get_rn(write_address));
else $sformat(str,"%s=%s + %s",get_rn(write_address),get_rn(A_address),get_rn(B_address));
end else begin
if (A_address==ZR && B_address==ZR & write_address==ZR) $sformat(str,"NOP %dループ付",im_val);
else if (B_address==ZR && A_address==ZR ) $sformat(str,"%sをクリア %dループ付",get_rn(write_address),im_val);
else if (B_address==ZR ) $sformat(str,"%sから%sへコピー %dループ付",get_rn(A_address),get_rn(write_address),im_val);
else $sformat(str,"%s=%s + %s %dループ付",get_rn(write_address),get_rn(A_address),get_rn(B_address),im_val);
end
Set: $sformat(str,"%s=%h[Hex]",get_rn(write_address),im_val);
Mul:
if (loop_cond==0) begin
$sformat(str,"%s=%s * %s",get_rn(write_address),get_rn(A_address),get_rn(B_address));
end else $sformat(str,"%s=%s * %s %dループ付",get_rn(write_address),get_rn(A_address),get_rn(B_address),im_val);
Sub:
if (loop_cond==0) begin
$sformat(str,"%s=%s - %s",get_rn(write_address),get_rn(A_address),get_rn(B_address));
end else $sformat(str,"%s=%s - %s %dループ付",get_rn(write_address),get_rn(A_address),get_rn(B_address),im_val);
Sync:
$sformat(str,"シンク命令");
Not: $sformat(str,"%s=~%s",get_rn(write_address),get_rn(A_address));
Jmp:
if (jump_cond==Always) $sformat(str,"%d番地へジャンプ",im_val);
else if (jump_cond==LT) $sformat(str,"%s < %s なら%d番地へジャンプ",get_rn(A_address),get_rn(B_address),im_val);
else if (jump_cond==GT) $sformat(str,"%s > %s なら%d番地へジャンプ",get_rn(A_address),get_rn(B_address),im_val);
else if (jump_cond==LTE) $sformat(str,"%s <= %s なら%d番地へジャンプ",get_rn(A_address),get_rn(B_address),im_val);
else if (jump_cond==GTE) $sformat(str,"%s >= %s なら%d番地へジャンプ",get_rn(A_address),get_rn(B_address),im_val);
else if (jump_cond==Eq) $sformat(str,"%s == %s なら%d番地へジャンプ",get_rn(A_address),get_rn(B_address),im_val);
Shift_L:
if (loop_cond==0) $sformat(str,"%s=%s << 1",get_rn(write_address),get_rn(B_address));
else $sformat(str,"%s=%s << %d",get_rn(write_address),get_rn(B_address),im_val);
Shift_R:
if (loop_cond==0) $sformat(str,"%s=%s >> 1",get_rn(write_address),get_rn(B_address));
else $sformat(str,"%s=%s >> %d",get_rn(write_address),get_rn(B_address),im_val);
BShift_L:
$sformat(str,"%s=%s <<%s",get_rn(write_address),get_rn(A_address),get_rn(B_address));
BShift_R:
$sformat(str,"%s=%s >> %s",get_rn(write_address),get_rn(A_address),get_rn(B_address));
endcase
end
//Tak end
// マイクロコード
`define PHY_ADD_MAX (1024*2-1)
//ALTERA ROMの模擬コード
reg [39:0] micro_rom [0:`PHY_ADD_MAX];
reg [15:0] address_latch;
always @(posedge clock) address_latch<=rom_address;
// always @(posedge clock ) begin//q latch 出力付の場合、将来パイプライン化したときに必要になる。
always @* begin
Data=micro_rom[address_latch];
end
`ifdef RTL_SIM
integer i;
initial begin
for (i=0;i<= `PHY_ADD_MAX;i=i+1) begin
micro_rom[i]=romdata(i);
end
make_intel_hex(5,`PHY_ADD_MAX);//RTL SIM時にINTEL HEXファイルを生成する Altera 用
make_coe_file(5,`PHY_ADD_MAX);//RTL SIM時にXilinx用 Coeファイルを生成する
end
`endif
//memory の内容をAltera ROM初期化用に
//intel-hexフォーマットに変換する
//ファイル->rom.hex として出力する
//address 64K word max
//zxを含まないこと
task make_intel_hex(input [7:0] bytes_per_word,
input integer address_max);
reg [9*8:1] str;
integer fi;
reg [15:0] ad;
integer bytes;
reg [7:0] parity;
begin
fi=$fopen("rom.hex","w");
for (ad=0;ad <=address_max;ad=ad+1) begin
$sformat(str,":%2h%4h00",bytes_per_word,ad);
parity=bytes_per_word;
parity =parity + ad[8*1 +:8];//チェックサム
parity =parity + ad[0 +:8];//チェックサム
$fwrite(fi,"%s",str);
for (bytes=0;bytes<bytes_per_word;bytes=bytes+1) begin: byte_loop
reg [7:0] byte;
byte=micro_rom[ad][(bytes_per_word-1-bytes)*8 +:8];
parity=parity+byte;//チェックサム
$fwrite(fi,"%2h",byte);
end
parity=~parity +8'h01;//チェックサム
$fwrite(fi,"%2h",parity);
$fwrite(fi,"\n");
end
$fdisplay(fi,":00000001FF");
$fclose(fi);
str="rom.ver";
//$convert_hex2ver("rom.hex",40,str);//チェックVeritak Uniqueファンクション
end
endtask
//xilinx coe file
task make_coe_file(input [7:0] bytes_per_word,
input integer address_max);
integer fi;
integer ad;
begin
fi=$fopen("rom.coe","w");
$fdisplay(fi,"memory_initialization_radix=16;");
$fdisplay(fi,"memory_initialization_vector=");
for (ad=0;ad <address_max;ad=ad+1) begin
$fdisplay(fi,"%h,",micro_rom[ad]);
end
$fdisplay(fi,"%h;",micro_rom[address_max]);
$fclose(fi);
end
endtask
function [39:0]romdata;
input [15:0] address;
case(address)
//Commad A_addr, B_addr, Write_addr, Jmp_cond, Reserved, IM
0: `SET(P_OUT, 100);
1: `SET(R0, 5);
2: `MOV(R0, R1);
3: `MOV(R1, R2);
4: `MOV(R2, R3);
5: `SUB(R1, R2, R0);
6: `SUB(R3, R1, R3);
7: `ADD(P_IN, P_OUT, P_OUT);
8: `ADD(P_OUT, P_OUT, P_OUT);
9: `SUB(P_IN, P_IN, R1);
10: `SUB(P_OUT, P_IN, R2);
11: `SET(R0, 2);
12: `SET(R1, 8);
13: `MUL(R0, R1, R3);
14: `NOP;
15: `MUL(R3, R3, R3);
16: `ADD(P_OUT, R3, P_OUT);
17: `SYNC;
18: `JUMP(1000);
// シフト・乗算
1000: `SET(R0, 5);
1001: `SET(R1, 2);
1002: `ADD_LOOP(R0, R1, R0, 10);
1003: `SHIFT_L_LOOP(R1, 5);
1004: `SHIFT_R_LOOP(R1, 5);
1005: `SUB_LOOP(R0, R1, R0, 10);
1006: `SET(P_OUT, 5);
1007: `MUL_LOOP(P_OUT, R1, P_OUT, 7);
1008: `JUMP(1500);
// 条件文
1500: `SET(R0, 16);
1501: `SET(R1, 2);
1502: `SET(P_OUT, 0);
1503: `SUB(R0, R1, R0);
1504: `JUMP_IF_GT(R0, R1 ,1503);
1505: `NOP ;//NOP挿入 ディレイドスロット
// 条件文
1506: `SET(R2, 16);
1507: `SET(R3, 2);
1508: `SET(P_OUT, 0);
1509: `SUB(R2, R3, R2);
1511: `JUMP_IF_GTE(R2, R3 ,1509);
1512:`NOP;//NOP挿入 ディレイドスロット
// 1010_1010_1010_1010
// ビット反転
1513: `SET(R0, 43690);
1514: `NOT(R0, R1);
// バレルシフタ
1515: `SET(R0, 2);
1516: `SET(R1, 7);
1517: `SET(R2, 0);
1518: `BSHIFT_L(R0, R1, R0);
1519: `BSHIFT_R(R0, R1, R0);
1520: `JUMP(0);
default: romdata = 0;
endcase
endfunction
endmodule
なお、今回の変更で、
StaratixUは、88MHz
Spartan 3は、44MHz となりました。
合成後の遅延シミュレーション結果(青色VCD)です。
Spartan3

同様にStaratixUです。

シンクロナスROMのアーカイブです。
RAMの実装
FPGAのRAMをデータメモリとして使うことを考えます。ここでは、外部RAMを使うことは考慮せずに、FPGA内のRAMを使うことを考えます。
さてここでの問題は、同期RAMにあります。とりあえずWIZARDでRAMを作ってみましょう。
Single_PortRAMを生成します。

とりあえず、16ビット幅2Kワードです。

Q出力なしにします。このダイアグラムの通り、RAMにおいても前段にFFが入ってしまいます。従って、1CLOCK遅れてしまうことに注意してください。CPUから見てWRITEは、書きっぱなしなので1CLOCKでWRITEできますが、READは、1CLOCKでは返ってきません。ここでの設計は大きな分かれ目です。2CLOCKを許容できないとするか、見た目に1CLOCK R/Wとなるように、全体のブロックを考えなおすかという問題になります。後者は、CPU全体のパイプライン化によって達成することができ、パイプラインにより、動作速度を向上させることができます。しかし良い事ばかりではなく、しわ寄せは、JUMP命令に現れます。JUMPアドレスが静的に決まっているならば、先読みにより回避することができますが。JUMPアドレスが動作時にしか決まらない場合、即ち、条件ブランチや、条件CALLがある場合、パイプライン分の段数に応じたクロック数がかかってしまいます。 (1CLOCKでREAD/WRITEするパイプラインの例は、こちらのパイプラインの検討を見てください。パイプラインが深いとそれだけペナルティも大きくなります。)
今回は、簡単な設計を目指しているので、パイプラインは使わず、READのみ2CLOCK、その他は1CLOCKとする方針で設計します。

アドレシングモード
下記の二つを備えます。R0をインデックスとしたアドレシングです。
以下追加部です。
Altera/Xilinx のRAMを入れ込む前に模擬バージョンで動作確認しておきます。
//ALTERA RAMの模擬コード
`define PHY_ADD_MAX_RAM (1024*2-1)//Dec.17.2006
reg [15:0] ram [0:`PHY_ADD_MAX_RAM];
reg [15:0] ram_address_latch;
reg ram_write_enable_latch;
reg [15:0] ram_data_latch,ram_output_data;
//アドレスラッチ、データラッチ、ライトEnableラッチがある
always @(posedge clock) ram_address_latch<=ram_address;
always @(posedge clock) ram_write_enable_latch<=ram_write_enable;
always @(posedge clock) ram_data_latch<=ram_input_port;
always @* begin
ram_output_data=ram[ram_address_latch];
end
assign ram_output_port=ram_output_data;
always @(negedge clock) begin//立下りで内部RAMに書き込み
if (ram_write_enable_latch) begin
ram[ram_address_latch] <=ram_data_latch;
end
end
RAMをレジスタと同じように扱うので、レジスタアドレスのアドレス空間に追加します。
// レジスタアドレス `define ZR 4'b0000 `define R0 4'b0001 `define R1 4'b0010 `define R2 4'b0011 `define R3 4'b0100 `define P_IN 4'b0101 `define P_OUT 4'b0110 `define RAM_PORT 4'b0111 //Dec.17.2006
ROMのビットフィールドにも追加します。
ビット18は、RAMのWriteEnableにします。
ビット17は、インデックスアドレシングか絶対アドレスかを決めているフラグです。
ROMワイヤーマップ
ビット位置 ビット幅 内容
39:35 5 命令
34:31 4 レジスタファイルAポートのアドレス
30:27 4 レジスタファイルBポートのアドレス
26:23 4 レジスタファイルライトポートのアドレス
22:19 4 ジャンプ比較条件
18:18 1 RAM_WRITE_Enable //Dec.17.2006
17:17 1 RAM アクセスの場合のR0をindex reg として使用するかどうかのフラグ.1で間接アドレシング//Dec.17.2006
16:16 1 loop_flag ループ処理するかどうかのフラグ、1でループ処理
15:0 16 即値または、飛び先,または、RAMアドレス
RAMのアドレス生成部と、RAMのデータ入力部です。RAMのデータ入力は、ALUの出力をそのまま出します。この辺は、CISCぽいですね。
wire index_flag =reserved[1];//Dec.17.2006 wire [15:0] out_dataA, out_dataB; wire [15:0] alu_data; wire ram_write_enable = reserved[2];//Dec.17.2006 wire [15:0] ram_output_port;//Dec.17.2006 wire [15:0] ram_input_port=alu_data;//Dec.17.2006 wire [15:0] index_reg;//Dec.17.2006 wire [15:0] ram_address=index_flag? immediate_data+index_reg :immediate_data;//Dec.17.2006
レジスタファイルからは、R0をindex_regとして引き出します。
// レジスタファイル
register_file register_file(.clock(clock),
.reset(reset),
.A_address(A_address),
.B_address(B_address),
.write_address(write_address),
.alu_data(alu_data),
.out_dataA(out_dataA),
.out_dataB(out_dataB),
.port_in_data(Aport_data),.ram_port(ram_output_port),.r0(index_reg),//Dec.17.2006
.port_out_data(port_out_data));
ifdef でRAMのインスタンスは、切り替えを行います。
`ifdef ALTERA
altera_ram2kx16 altera_ram (
.address(ram_address),
.clock(clock),
.data(ram_input_port),
.wren(ram_write_enable),
.q(ram_output_port)
);
`elsif XILINX
ram_xilinx2k16 xilinx_ram(
.addr(ram_address),
.clk(clock),
.din(ram_input_port),
.dout(ram_output_port),
.we(ram_write_enable));
`else
//ALTERA RAMの模擬コード
`define PHY_ADD_MAX_RAM (1024*2-1)//Dec.17.2006
reg [15:0] ram [0:`PHY_ADD_MAX_RAM];
reg [15:0] ram_address_latch;
reg ram_write_enable_latch;
reg [15:0] ram_data_latch,ram_output_data;
//アドレスラッチ、データラッチ、ライトEnableラッチがある
always @(posedge clock) ram_address_latch<=ram_address;
always @(posedge clock) ram_write_enable_latch<=ram_write_enable;
always @(posedge clock) ram_data_latch<=ram_input_port;
always @* begin
ram_output_data=ram[ram_address_latch];
end
assign ram_output_port=ram_output_data;
always @(negedge clock) begin//立下りで内部RAMに書き込み
if (ram_write_enable_latch) begin
ram[ram_address_latch] <=ram_data_latch;
end
end
`endif
レジスタファイルの記述です。
// レジスタファイル
// 記憶素子をレジスタの集まりとして、まとめてモジュール化。
// 同時に読み書きできるポートが3つ
// ライトポートが1つ、リードポートが2つ。
// R0をIndexRegとして使用する
module register_file(input clock,
input reset,
input [3:0] A_address,
input [3:0] B_address,
input [3:0] write_address,
input [15:0] alu_data, port_in_data,ram_port, //Dec.17.2006
output [15:0] out_dataA, out_dataB, port_out_data,
output reg [15:0] r0
);
// CPUレジスタ
reg [15:0] p_out_reg, r1, r2, r3;
// ポートアウトレジスタ
always @(posedge clock, posedge reset) begin
if(reset)
begin
p_out_reg <= 0;
end
else if(write_address == `P_OUT)
begin
p_out_reg <= alu_data;
end
else
begin
p_out_reg <= p_out_reg;
end
end
// 汎用レジスタR0
always @(posedge clock, posedge reset) begin
if(reset)
begin
r0 <= 0;
end
else if(write_address == `R0)
begin
r0 <= alu_data;
end
else
begin
r0 <= r0;
end
end
// 汎用レジスタR1
always @(posedge clock, posedge reset) begin
if(reset)
begin
r1 <= 0;
end
else if(write_address == `R1)
begin
r1 <= alu_data;
end
else
begin
r1 <= r1;
end
end
// 汎用レジスタR2
always @(posedge clock, posedge reset) begin
if(reset)
begin
r2 <= 0;
end
else if(write_address == `R2)
begin
r2 <= alu_data;
end
else
begin
r2 <= r2;
end
end
// 汎用レジスタR3
always @(posedge clock, posedge reset) begin
if(reset)
begin
r3 <= 0;
end
else if(write_address == `R3)
begin
r3 <= alu_data;
end
else
begin
r3 <= r3;
end
end
// Aポート出力(ALUへ)
assign out_dataA = (A_address==`P_OUT) ? p_out_reg :
(A_address==`P_IN) ? port_in_data :
(A_address==`RAM_PORT) ? ram_port: //Dec.17.2006
(A_address==`R0) ? r0 :
(A_address==`R1) ? r1 :
(A_address==`R2) ? r2 :
(A_address==`R3) ? r3 : 16'b0;
// Bポート出力(ALUへ)
assign out_dataB = (B_address==`P_OUT) ? p_out_reg :
(B_address==`P_IN) ? port_in_data :
(B_address==`RAM_PORT) ? ram_port: //Dec.17.2006
(B_address==`R0) ? r0 :
(B_address==`R1) ? r1 :
(B_address==`R2) ? r2 :
(B_address==`R3) ? r3 : 16'b0;
// 出力ポート(そのまま出力)
assign port_out_data = p_out_reg;
endmodule
RAMのR/Wに関するマイクロ命令の追加部です。
CPU->RAMへの書き込みは、2CLKかかるのですが、CPUからは見えません。ですので1CLKで次の命令を実行できます。
RAM->CPUの読み込みは、2CLKかかるので、二つの命令で構成します。最初の命令は、アドレス指定だけです。次の命令で、読み込み、つまりレジスタへの書き込みを行います。このとき、マイクロフィールドのアドレス部は遊んでいますので、次の読み込みアドレスを指定できます。つまりパイプライン的な記述が可能で、読み込み命令が連続する場合は、1CLKで済みます。
wire indirect_flag=Data[17];//Dec.17.2006
wire ram_write=Data[18];//Dec.17.2006
// レジスタアドレス
parameter [3:0] P_OUT=`P_OUT,//IOポート出力
P_IN=`P_IN, //IOポート入力
RAM_PORT=`RAM_PORT, //RAM ポート Dec.17.2006
R0= `R0,
R1= `R1,
R2= `R2,
R3= `R3,
ZR= `ZR; // ゼロレジスタ (= ゼロGND)
// ループ処理 または、RAMライト処理
//ループ値はRAMアドレスと共用なのでRAMに関しループは不可
parameter [2:0] Reserved = 3'b000,
LoopEnabled = 3'b001,
RAM_write_enable=3'b100, //Dec.17.2006 ビット2をRAM WriteEnableに使用
RAM_read_port_enable_with_index=3'b011,//Dec.17.2006 ビット1ー> INDEX付き
RAM_write_enable_with_index=3'b111; //Dec.17.2006 ビット2をRAM WriteEnableに使用ビット1−>INDEX付き
//RAMへライト RAM[address]=source_reg Dec.17.2006 ライトは2クロックかかるがCPUからは見えない
`define MOV_TO_RAM(source_reg,address) romdata={Add,source_reg,ZR,ZR,Eq,RAM_write_enable,16'd address}
// RAM[address+r0]=source_reg
`define MOV_TO_RAM_W_I(source_reg,address) romdata={Add,source_reg,ZR,ZR,Eq,RAM_write_enable_with_index,16'd address}
//RAMからリード Dec.17.2006
//READ は2クロックかかるので、2マイクロ命令組で使う 1クロック目でアドレス指定、2クロック目でレジスタへのライト
// dest_reg=RAM[address]
`define MOV_FROM_RAM_pre(address) romdata={Add,ZR,ZR,ZR,Eq,Reserved,16'd address}//アドレス指定,この間他のレジスタ演算を行っても良い
`define MOV_FROM_RAM(dest_reg,address) romdata={Add,RAM_PORT,ZR,dest_reg,Eq,Reserved,16'd address}//レジスタへ書き込み
//dest_reg=RAM[address+r0]
`define MOV_FROM_RAM_W_I_pre(address) romdata={Add,ZR,ZR,ZR,Eq,RAM_read_port_enable_with_index,16'd address}//アドレス指定,この間他のレジスタ演算を行っても良い
`define MOV_FROM_RAM_W_I(dest_reg,address) romdata={Add,RAM_PORT,ZR,dest_reg,Eq,RAM_read_port_enable_with_index,16'd address}//レジスタへ書き込み
マイクロコードの例です。単発読み出しでは、xx_pre命令でアドレス指定した後に読み出す命令が必要ですが、連続読み出しでは、xx_pre命令が不要になっています。
function [39:0]romdata;
input [15:0] address;
case(address)
//Commad A_addr, B_addr, Write_addr, Jmp_cond, Reserved, IM
//simple test
0:`SET(R0,1000);
1:`SET(R1,2000);
2:`SET(R2,3000);
3:`MOV_TO_RAM(R0,0);
4:`MOV_TO_RAM(R1,1);
5:`MOV_TO_RAM(R2,2);
6:`MOV_FROM_RAM_pre(0);
7:`MOV_FROM_RAM(P_OUT,0);
8:`MOV_FROM_RAM_pre(1);
9:`MOV_FROM_RAM(P_OUT,1);
10:`MOV_FROM_RAM_pre(2);
11:`MOV_FROM_RAM(P_OUT,2);
//index register write test
12:`SET(R3,2048);//for (r0=0;r0<2048;r0++){
13:`SET(R1,1);//r1=1;
14:`SET(R0,0);//r0=0;
15:`MOV_TO_RAM_W_I(R0,0);//mem[r0+0]=r0
16:`ADD(R0,R1,R0);// r0++;
17:`JUMP_IF_LT(R0, R3 ,15);// }
//index register read test
18:`SET(R0,0);//r0=0;
19:`SET(R3,1000);
20:`SET(R1,4);
21:`MOV_FROM_RAM_W_I_pre(1048);//ram_address=r0+1048
22:`MOV_FROM_RAM_W_I(P_OUT,1049);//P_OUT=mem[ram_address] ram_address2=r0+1049
23:`MOV_FROM_RAM_W_I(P_OUT,1050);//P_OUT=mem[ram_addres2]
24:`MOV_FROM_RAM_W_I(P_OUT,1051);//.. software pipeline
25:`MOV_FROM_RAM_W_I(P_OUT,1052);//.. software pipeline
26:`ADD(R0,R1,R0);// r0 +=4;
27:`JUMP_IF_LT(R0, R3 ,21);// }
45:`JUMP(0);
..
RTL Sim
RAM Writeの様子です。1CLK毎に書けます。

Readの様子です。

XILINX RTL SIMと遅延SIM
遅延SIMは、PortOUTの出力をVCDで見ています。RTLと一致しています。

Altera RTL SIMと遅延SIM
同様にAltera Versionです。

RAM版のダウンロード(Generic,Altera,Xilinx、RTL/遅延SIM)
<次の拡張案>
とりあえずRAMの実装は終わったのですが、普通のCPUでは、やっぱりあまり面白くないので、アーキテクチャとしてDSPを志向してみることにしました。FIRフィルタ、FFT,ダウンコンバータ、サーボ演算等で遊べたらよいな?と思います。リソース使用量はあまり気にしないで、できるだけ簡単な設計を心がけたいと思います。コンパイラ作成では、日本ではあまりなじみがないと思われるAntlrというツールを紹介する予定です。構文解析は、Flex/Bisonや、Boost::spiritがよく知られていますが、Antlrもそのようなツールです。このツールは、C++や、JAVA,Pythonを出力します。
DSPライクなアーキテクチャの検討
こういう石にしたい、というのを思いつくままに列挙してみました。
最近のFPGAのアーキテクチャをうまく生かすには?
プロセッサを高速化するには、垂直方向の高速化、すなわちDeep Pipelineが有効です。動的なジャンプがないとしたら、いくらでもパイプライン化で高速にすることができます。問題はジャンプであるということは、上で見ました。ところで、FFTや、FIRは、FOR LOOPによる演算になりますが、これらは、ジャンプ箇所とループ数が実行時に変化することはないので、プログラムによるHW化をすることができます。つまりDeep Pipelineを維持しながら、演算ループではJUMPペナルティが全くない処理をすることは可能です。ただ、予測分岐とか面倒なことはしないので、ジャンプ命令自体は4CLK位Delayed Slot(ジャンプ命令の後も実行してしまう)が発生してもよし、とします。また、最近のFPGAは、乗算器を、数個以上積んでいるのが普通で、遊んでいるのも勿体ない話です。これには水平方向の並列演算が有効でしょう。FPGAでは、シングルALUにこだわる理由はありません。
しかしながら、思いついた仕様は、ハード的に飛躍し過ぎるので、
テーマは、「Verilogでお手軽CPU」に戻ります。
まずは、普通にTINY Cコンパイラが走るCPUを作ることを目標にします。
そこで、普通のCPUの仕様を考えてみます。
テーマが紆余曲折していますが、事情があり、YACCベースでの32ビットCPUハード製作に変更します。TINY Cコンパイラの製作はハード製作後に行います。ごめんなさい。旧YACCとの違いは、
第一に
第二の目標は
です。YACCは、ドライストーンを良く見せることを第一義に設計したために、駆動周波数は速いのですが、外部RAMには接続できず汎用的とは言えません。そこで、多少速度を犠牲にして汎用的な設計を行ってみます。
最終目標は、自前Cコンパイラを使い、OS上で自前のCPUを走らせることです。道は遠く長いです。そこで、先人の知見を辿って見ることにしましょう。RISCの教科書とも言える設計例として、(Z80を上回る量の)世界中に様々なソースがあります。またこの他に、CPUシミュレータ、教育用OSも沢山あります。ちょっと集めただけで以下のように沢山のCPUソースがあります。しかし、見ただけでは、機械語からCソースを想像するようなものでなにも分からないでしょう。( そこに本稿の存在意義があります。)
| ニックネーム | 言語 | 特徴 |
| YACC | Verilog | 拙作。5段パイプライン。 |
| MIPSマイコンを作ろう | Verilog | リソース使用量はFZ80の1/2になったとのこと。 |
| Plasma | VHDL | 3-5段パイプライン。自前RTOSでWEBサーバのデモあり |
| Yellow star | 回路図 | 5段パイプライン。TLBを含む設計。 |
| Ucore | Verilog | 5段パイプライン。 |
| 電通大 | Verilog | |
| 九工大 | VHDL | ソースを見るには登録が必要(登録しました) |
| Minimips | VHDL |
まずは、要件を書き出してみます。
教科書は、
で、これは必須です。細かい命令セットの中身は東芝のTX-39ファミリーを見ればよいでしょう。
パイプライン割付検討
本物のLINUXを走らせるためにはMMUなしでは走