Last update $Id$
(c) 2007 BakaOyaji

プログラムカウンタ(PC)の設計

図6のブロックダイアグラムのなかのプログラムカウンタ(PC)の 設計です。基本的にはPCは次に実行する命令のアドレスを+1づつインクリメントするだけなのですが、今回はスタックも同じモジュール内に実装しています のでそちらの処理も必要です。アドレスの変化は、+1でよいのですが、プロセッサがSLEEP状態にある場合や、準備編で検討した、図5-1図5-4の場合のように、PCの値が変化しないサイクルがあるの で注意が必要です。整理するとPCの動作は、
  1. SLEEP状態中は、値をホールド
  2. 割り込み受付サイクル(CYCLE=2)は、0004番地へサブルーチンコール
  3. ファンクションレジスタPCLへの書き込みによる分岐
  4. GOTO、CALL、RETURN系の命令による分岐
  5. 割り込み受付サイクル(CYCLE=1)は、値をホールド
というふうになります。

SYNOPSIS  yap16_PC

input clk_i クロック入力
input por_i パワーオンリセット
input [(`YAP16_INST_WIDTH -1):0] instruction_i 命令入力
input [7:0] data_i PCLレジスタのデータ入力(BackDoor)
input pcl_we_i PCLの書き込みストローブ
output [(`YAP16_PC_WIDTH -1):0] pc_addr_o プログラムメモリのアドレスへ
input [(`YAP16_PC_WIDTH -1 -8):0] pclath_i PCLATHレジスタからの分岐の上位アドレス(BackDoor)
input force_call4_i 割り込みの分岐タイミング
input hold_i 割り込み時のアドレスホールド
input sleep_i SLEEP状態入力

     1	//
2 // Program counter
3 // (c) 2007 BakaOyaji
4 // $Id: yap16_pc.v,v 1.8 2007/10/01 07:13:12 BO Exp $
5 //
6 //
7
8
9 `include "yap16_instructions.h"
10 `include "yap16_def.h"
11
12 module yap16_pc(
13 input clk_i,
14 input por_i ,
15 input [(`YAP16_INST_WIDTH -1):0] instruction_i ,
16
17 input [7:0] data_i , // from write bus
18 input pcl_we_i , // write enable for pcl
19 output [(`YAP16_PC_WIDTH -1):0] pc_addr_o , // PC addr
20 input [(`YAP16_PC_WIDTH -1 -8):0] pclath_i ,// from PCLATH reg
21 input force_call4_i, // force call 0004
22 input hold_i, // hold current address
23 input sleep_i
24 );
25
26 reg [(`YAP16_PC_WIDTH -1):0] pc ; // PC addr
27 reg [(`YAP16_PC_WIDTH -1):0] stack[(`YAP16_STACK_DEPTH-1):0] ;
28 integer i ;
29 //synopsys translate_off
30 integer stack_level = 0 ;
31 //synopsys translate_on
32
33 assign pc_addr_o = pc ;
34 // for debug
35 //synopsys translate_off
36 wire [(`YAP16_PC_WIDTH -1):0] stack_top ;
37 assign stack_top = stack[0] ;
38 //synopsys translate_on
39
40 always@( posedge clk_i ) begin
41 if( por_i ) begin
42 pc <= 13'b0 ;
43 `ifdef FULL_RESET
44 for( i = 0 ; i < `YAP16_STACK_DEPTH ; i=i+1) begin
45 sp[i] <=0 ;
46 end
47 `endif // FULL_RESET
48 end // if
49 else if ( sleep_i ) begin
50 // hold
51 end // else if
52 else if( force_call4_i ) begin
53 pc <= 13'h0004 ;
54 // Push note that PC is always advance
55 stack[0] <= pc ;
56 for( i = 0 ; i < (`YAP16_STACK_DEPTH-1) ; i=i+1) begin
57 stack[i+1] <= stack[i] ;
58 end
59 //synopsys translate_off
60 stack_level = stack_level +1 ;
61 if( stack_level >= `YAP16_STACK_DEPTH) begin
62 $display("%m ERROR stack overflow") ;
63 $stop ;
64 end
65 //synopsys translate_on
66 end // else if
67 else if( pcl_we_i ) begin
68 pc <= {pclath_i , data_i } ;
69 end // else if
70 else begin
71 (* parallel_case *)
72 casex ( instruction_i )
73 `YAP16_INST_GOTO : begin
74 pc <= {pclath_i[(`YAP16_PC_WIDTH -1 -8):3],
75 instruction_i[10:0] } ;
76 end
77 `YAP16_INST_CALL : begin
78 pc <= {pclath_i[(`YAP16_PC_WIDTH -1 -8):3],
79 instruction_i[10:0] } ;
80 // Push
81 stack[0] <= pc ;
82 for( i = 0 ; i < (`YAP16_STACK_DEPTH-1) ; i=i+1) begin
83 stack[i+1] <= stack[i] ;
84 end
85 //synopsys translate_off
86 stack_level = stack_level +1 ;
87 if( stack_level >= `YAP16_STACK_DEPTH) begin
88 $display("%m ERROR stack overflow") ;
89 $stop ;
90 end
91 //synopsys translate_on
92 end
93 `YAP16_INST_RETURN ,
94 `YAP16_INST_RETFIE,
95 `YAP16_INST_RETLW : begin // pop & branch
96 pc <= stack[0] ;
97 // pop
98 for( i = 0 ; i < (`YAP16_STACK_DEPTH-1) ; i=i+1) begin
99 stack[i] <= stack[i+1] ;
100 end
101 //synopsys translate_off
102 stack_level = stack_level -1 ;
103 if( stack_level < 0 ) begin
104 $display("%m ERROR stack underflow") ;
105 $stop ;
106 end
107 //synopsys translate_on
108 end
109 default : begin
110 if( hold_i ) begin
111 end // if
112 else begin
113 pc <= pc + 13'b1 ;
114 end // else
115 end // default
116 endcase // Instruction
117 end // else
118 end // always
119 endmodule // yap16_pc
120 // EOF yap16_pc.v

if else if … と case(casex)文について
モジュール中で、if else if …構文と、casex文が混在して使用されていますが、この二つの構文はVerilogの文法上は同一になります。つまり、case文のセレクタは上から順番に評価され ます。しかし、実際のハードウェアでのデコーダは並列(同時)に評価するのが普通です(このようなcaseをパラレルcaseといいます)。そこで、論理 合成のとき、同時に評価してよいことを示すためのプラグマが用意されています。71行目の
 (* parallel_case *)
がそれにあたります。
記述の内容から論理合成ツールが自動的に判別できる場合もありますが、並列デコードしてよい場合、たとえばここでの例のように、各セレクタが「相互排除」 である場合は積極的にこのプラグマを使いましょう。
一方、por_i(パワーオンリセット)/force_call4_i(割り込みによる4番地へのコール)/pcl_we_i(PCLレジスタへの書き込 み)についてはそれぞれの事象は独立して起きる(つまり、相互排除ではない)上、この順番で優先度があること明示的に示すために、if else if …構文を用いています。もちろん、(パラレルcaseのプラグマをつけずに)case文を使うこともできます。
なお、VHDLでは、パラレルcaseがデフォルトです(というかパラレルcaseしかない)。

//synopsys translate_off と //synopsys translate_on について
Verilogの文法上は、コメントですが、多くの論理合成ツールでは、この二つのコメントに挟まれた部分はコメントとして読み飛ばします。ここでは、デ バッグ用のコード(スタックのオーバーフローとアンダーフローのチェック)を入れています。もともとは、SYNOPSYS社の DesignCompilerのローカルルールですが、デファクトスタンダードと思います(本来は、`ifdef 〜 `endif を使うべきと思います。SYNOPSYS社もadohocな仕様を作ったものだとつくづく思います。笑)。

スタックのプッシュ、ポップには、for文を使っていますが、これも論理合成できます。(ソフトウェアの)コンパイラの最適化の手法でいう、ループ・アン フォールディングが行われます。たとえば、プッシュ動作(56行目あたり)は、
	stack[1] <= stack[0] ;
stack[2] <= stack[1] ;
stack[3] <= stack[2] ;
:
:
と等価になります。「これでは、スタックの値が全部stack[0]で書き潰されてしまうんじゃない? for文のインデックスの回し方が逆でしょ?」と思ったあなた、「スルドイ」。しかし修行が足りません(笑)。<=はノンブロッキング代入なので左 辺の変数の値の更新は全部同時に行われます。
また、変数stackは配列として定義していますが、ループアンフォールディングの結果、このモジュール内の記述ではすべてインデックスが定数となりま す。したがって、論理合成の結果としては(アドレッサブルな)RAMではなく、13ビット×8(スタックの深さ)の左右シフト可能なシフトレジスタになる ことにも注意してください。



<<前に戻る   Index へ  次へ>>