2011年11月24日木曜日

PicoBlaze を使ってみる(その3)


by yosikawa » 2011年2月14日(月) 01:37
さて、PicoBlaze にロータリスイッチを繋いでみる。 :ugeek: 

以前作ったロータリスイッチの処理回路を再利用する。
コード: 
   // ロータリスイッチのトリガ信号を作る。
   wire rotary_event;            // ロータリスイッチが1段階回転したとき、1パルスだけアクティブになる。
   wire rotary_right;            // rotary_event がアクティブのとき、回転方向を示す。ハイが右方向。
   S3E_Rotary S3E_Rotary(clk, reset, ~rotary_a, ~rotary_b, rotary_event, rotary_right);

しかし良く考えると、rotary_eventは1クロックしか出力されない。 :o 
このままポートに接続してもPicoBlaze 側は読み込みが間に合わない。
かといって、単純にフリップフロップに覚えただけだと、2回目のイベントが取れない。
PicoBlaze が読み込んだらクリアする必要がある。ハンドシェークが必要だ。
イベントが発生したら、フリップフロップに覚える。ポート01に何かが書き込まれたら、クリアする。
以下のようにしてみた。
コード: 
   // ロータリスイッチのトリガ信号を作る。
   wire rotary_event;            // ロータリスイッチが1段階回転したとき、1パルスだけアクティブになる。
   wire rotary_right;            // rotary_event がアクティブのとき、回転方向を示す。ハイが右方向。
   S3E_Rotary S3E_Rotary(clk, reset, ‾rotary_a, ‾rotary_b, rotary_event, rotary_right);
   
   // ロータリイベントを保持する。
   reg [1:0] rotary_latch;
   always @(posedge clk or negedge reset) begin
      if(!reset) begin
         rotary_latch <= 0;
      end else if(rotary_event) begin
         rotary_latch <= { rotary_event, rotary_right };
      end else if(write_strobe & port_id[0]) begin
         rotary_latch <= 0;
      end
   end

これに合わせて、入出力ポートの接続は以下のようにした。
コード: 
   // スイッチ入力を入力ポートに接続する
   // port 00 : switch, btn
   // port 01 : 6'b0, rotary_event, rotary_right
   always @(posedge clk or negedge reset) begin
      if(!reset) begin
         in_port <= 0;
      end else begin
         case (port_id)
            8'h00 : in_port <= { switch, btn };
            8'h01 : in_port <= { 6'b0, rotary_latch };
            default : in_port <= 0;
         endcase
      end
   end
   
   // 出力ポートから信号を取り出す
   reg [7:0] out1;
   always @(posedge clk or negedge reset) begin
      if(!reset) begin
         out1 <= 0;
      end else if(write_strobe & ‾port_id[0]) begin
         out1 <= out_port;
      end
   end

アセンブラの方は、だんだんプログラムらしくなってきた。
rotary_event が発生すると、カウントを増減させる。rotary_right=1のときは+1、rotary_right=0のときは-1する。
C言語なら簡単に書けるがマシン語だと大変。
定数定義や、レジスタに名前を定義する機能を使って、読みやすくしよう。
以下のようになった。
コード: 
; files
                    VHDL      "ROM_form.v", "rom.v", "rom"
; input port
i_switch            DSIN      $00
i_rotary            DSIN      $01
; output port
o_led               DSOUT     $00
o_rotary_ack        DSOUT     $01
; bit pattern
b_rotary_event      EQU       $02
b_rotary_right      EQU       $01
; register
s_switch_value      EQU       s0
s_rotary_count      EQU       s1
s_rotary_value      EQU       s2

; initialize

                    ORG       0
                    LOAD      s_rotary_count, 0

; main loop

BEGIN: 
; input rotary events and test
                    IN        s_rotary_value, i_rotary
                    TEST      s_rotary_value, b_rotary_event
                    JUMP      Z, SKIP
; clear event flag
                    OUT       s_rotary_value, o_rotary_ack
; rotate right?
                    TEST      s_rotary_value, b_rotary_right
                    JUMP      Z, GOLEFT
; +1 when right
                    ADD       s_rotary_count, 1
                    JUMP      SKIP
GOLEFT:
; -1 when left
                    SUB       s_rotary_count, 1
SKIP:
                    IN        s_switch_value, i_switch
                    ADD       s_switch_value, s_rotary_count
                    OUT       s_switch_value, o_led
                    JUMP      BEGIN


動かしてみると、、、よしよし。ロータリスイッチに反応してLEDで表現した2進数が増減する。 :D 

デバッグがしやすいように、ソフト・リセット機能も付けておこう。
コード: 
   // リセット信号を作る。
   reg [1:0] reset_count;
   always @(posedge clk) begin
      if(&reset_count == 0)
         reset_count <= reset_count + 1;
   end
   wire hard_reset = reset_count[0];   // ハード・リセット信号。
   
   // 1KHz, 4Hz の内部クロックを作成する。
   // 分周比 50MHz / 50000 = 1KHz
   parameter integer CLK1MS_SCALE = 50000;   // 分周比
   defparam prescaler1.SCALE = CLK1MS_SCALE;
   wire clk1ms;   // 1ms 内部クロック
   Prescaler prescaler1(
      clk,         // 入力クロック
      hard_reset,   // リセット
      clk1ms      // 分周結果。シングルパルス。
   );
   parameter integer CLK250MS_SCALE = 250;   // 分周比
   defparam prescaler2.SCALE = CLK250MS_SCALE;
   wire clk250ms;   // 250ms 内部クロック
   Prescaler_Enabled prescaler2(clk, hard_reset, clk1ms, clk250ms);

   // ロータリ・スイッチ長押しでソフト・リセットする(4秒)
   wire soft_reset, nsoft_reset;
   defparam PushButton_LongTerm.WAIT = 12;   // 0.25s * 12 = 4s
   PushButton_LongTerm PushButton_LongTerm(clk, hard_reset, clk250ms, rotary_press, nsoft_reset);
   assign soft_reset = ~nsoft_reset;   // ソフト・リセット信号
   
   // リセット信号を作る
   wire reset = hard_reset & soft_reset;

起動後、1回だけhard_reset がアクティブ(ロー)になる。
へんな回路だが、この方がシミュレーションのとき便利だったので。
ロータリ・スイッチ長押しでソフト・リセットする(4秒)
PushButton_LongTerm は長押しを検出する回路。
コード: 
module PushButton_LongTerm(clk, reset, enable, in, out);
   parameter integer WAIT = 1;   // 長押し検出時間。(wclkの周期)*WAIT の時間長押しすると反応する。最低1。

   input wire clk;         // クロック
   input wire reset;         // リセット
   input wire enable;      // ウェイト用クロック。1パルスだけアクティブになる信号でなければならない。
   input wire in;            // 入力信号。アクティブ・ハイ。チャタを含まない同期信号。
   output reg out;         // inがWAIT時間以上アクティブのときアクティブになり、inの立ち下がりでインアクティブになる。
   
   function integer get_width(input integer value);
      integer v2;
      begin
         v2 = value;
         for(get_width = 0; v2 > 0; get_width = get_width + 1) begin
            v2 = v2 >> 1;
         end
      end
   endfunction

   parameter count_width = get_width(WAIT - 1);
   reg [count_width - 1:0] count;
   
   always @(posedge clk or negedge reset) begin
      if(!reset) begin
         count <= 0;
         out <= 0;
      end else if(!in) begin
         count <= 0;
         out <= 0;
      end else if(enable) begin
         if(count == (WAIT - 1'b1)) begin
            count <= 0;
            out <= 1;
         end else begin
            count <= count + 1;
         end
      end
   end

endmodule


ついでに、ソフト・リセットがかかったらロータリスイッチを押している間、LEDがフラッシュするようにしよう。 :mrgreen: 
コード: 
   // ソフト・リセット中は点滅させる。
   reg flash_timming;
   always @(posedge clk or negedge hard_reset) begin
      if(!hard_reset) begin
         flash_timming <= 0;
      end else if(clk250ms) begin
         flash_timming <= ~flash_timming;
      end
   end
   assign led = (!soft_reset) ? (flash_timming ? 8'hff : 8'h00) : out1;


最後に、PicoBlaze の割り込みを使ってみよう。
今回のアプリでは割り込みを使わなくても十分に間に合うが、
今後作成予定のLCDモジュールでは時間待ちの処理などを入れるので、
割り込みで処理したい。
ロータリスイッチのイベントは、くりっ、くりっ、と回転するごとに発生するから、速く回せばそこそこの頻度で発生する。
イベント発生のタイミングで割り込みをかけて、割り込みルーチンの中でロータリスイッチのカウントをするようにする。

では、top.vに割り込みの回路を付ける。
コード: 
   // ロータリイベントで割り込みをかける。
   always @(posedge clk or negedge reset) begin
      if(!reset) begin
         interrupt = 0;
      end else if(rotary_event) begin
         interrupt = 1;
      end else if(interrupt_ack) begin
         interrupt = 0;
      end
   end


割り込み処理ルーチン付きのPicoBlaze プログラムは最終的に以下のようになった。
コード: 
; files
                    VHDL      "ROM_form.v", "rom.v", "rom"
; input port
i_switch            DSIN      $00
i_rotary            DSIN      $01
; output port
o_led               DSOUT     $00
o_rotary_ack        DSOUT     $01
; bit pattern
b_rotary_event      EQU       $02
b_rotary_right      EQU       $01
; register
s_switch_value      EQU       s0
s_rotary_count      EQU       s1
s_rotary_value      EQU       s2

; initialize

                    ORG       0
                    LOAD      s_rotary_count, 0
                    EINT                          ; enable interrupt

; main loop

BEGIN: 
                    IN        s_switch_value, i_switch
                    ADD       s_switch_value, s_rotary_count
                    OUT       s_switch_value, o_led
                    JUMP      BEGIN

; ISR

ISR: 
; input rotary events and test
                    IN        s_rotary_value, i_rotary
; clear event flag
                    OUT       s_rotary_value, o_rotary_ack
; rotate right?
                    TEST      s_rotary_value, b_rotary_right
                    JUMP      Z, GOLEFT
; +1 when right
                    ADD       s_rotary_count, 1
                    JUMP      SKIP
GOLEFT: 
; -1 when left
                    SUB       s_rotary_count, 1
SKIP: 
                    RETI      ENABLE
; interrupt vector
                    ORG       $3FF
                    JUMP      ISR


これで、入出力と割り込みまでPicoBlaze の使い方を一通りマスターした。と思う。

0 件のコメント:

コメントを投稿