PSoC 3 で、分周器を作った [PSoC]
前回は、データパスを使って、加算器を作りました。 加算器は、延々と足し算をするだけなので、使用するデータパスの設定は一つだけで、変更はしていません。 今回は、 データパスの設定を2つ使って、分周器を作成します。
Verilog で書いた分周器
今回は、こんな回路を作成しました。 まず、400Hz のクロックを 100 分周し、 4Hz のパルスを作成します。 このパルスを T-FF に通して 2Hz 50% デューティーの信号を生成して LED を点滅させます。
Control Reg は、分周器の起動時期を決めるために追加されています。
Verilog 記述は、以下のようになっています。
//`#start header` -- edit after this line, do not edit this line // ======================================== // // Copyright noritan.org, 2013 // All Rights Reserved // UNPUBLISHED, LICENSED SOFTWARE. // // CONFIDENTIAL AND PROPRIETARY INFORMATION // WHICH IS THE PROPERTY OF NORITAN.ORG. // // ======================================== `include "cypress.v" //`#end` -- edit above this line, do not edit this line // Generated on 04/28/2013 at 18:32 // Component: DividerUdb module DividerUdb ( output tc, input clock, input enable, input reset ); parameter PERIOD = 100; //`#start body` -- edit after this line, do not edit this line wire[7:0] count; // counter value wire zero; // A0==0 (z0) // Divide counter behavior reg[7:0] count_reg; always @(posedge reset or posedge clock) begin if (reset) begin count_reg <= (PERIOD - 1); end else if (!enable) begin count_reg <= (PERIOD - 1); end else if (zero) begin count_reg <= (PERIOD - 1); end else begin count_reg <= count - 1; end end // Signal assignment assign zero = (count[7:0] == 8'd0); assign tc = zero & enable; assign count = count_reg[7:0]; //`#end` -- edit above this line, do not edit this line endmodule //`#start footer` -- edit after this line, do not edit this line //`#end` -- edit above this line, do not edit this line
ちょっとばかり難しくなりましたが、まだまだ、大したことはありません。 wire 宣言のあと、カウンタの動作を決めるステートメントが続いています。 ここで定義されているカウンタの振る舞いは、2つだけです。
- LOAD
カウンタの初期値を設定します。
このカウンタは、ダウンカウンタとして設計されています。 初期値から数えて "0" に達するまで、カウントダウンする仕組みです。 そのため、分周器の周期は、ここで与えた初期値より1大きくなります。 (PERIOD -1) を与えているのは、このためです。
LOAD の条件は、リセット時、ディセーブル時、そして、カウンタが "0" に達した時の3つです。
- DECREMENT
カウンタをデクリメントします。
カウンタは、その値が "0" に達するまでデクリメントを繰り返します。
最後の部分にある assign ステートメントで、制御信号を生成しています。
zero は、カウンタの値が "0" に達したことを示す信号です。 分周器の動作時、この信号は、1クロック周期だけアサートされます。
tc (Terminal Count) は、分周器の出力です。 分周器の動作時には、 zero と同じ動きをしますが、ディセーブル時にはアサートされません。 この信号は、単純にマスクされているだけなので、グリッチを発生する可能性があります。
count の定義は、レジスタとワイヤを分離して、問題が発生しないようにするための工夫です。
テストプログラムは、このようになっています。
/* ======================================== * * Copyright noritan.org, 2013 * All Rights Reserved * UNPUBLISHED, LICENSED SOFTWARE. * * CONFIDENTIAL AND PROPRIETARY INFORMATION * WHICH IS THE PROPERTY OF NORITAN.ORG. * * ======================================== */ #include <device.h> void main() { /* Place your initialization/startup code here (e.g. MyInst_Start()) */ CR1_Write(1); /* CyGlobalIntEnable; */ /* Uncomment this line to enable global interrupts. */ for(;;) { /* Place your application code here. */ } } /* [] END OF FILE */
Control Reg を操作して、分周器をイネーブルするだけです。 これで、分周器は動作を開始します。
データパスで書いた分周器
次は、 Verilog で書いたものをデータパスで書き換えます。 "DividerDp" の記述は、以下の通りです。
//`#start header` -- edit after this line, do not edit this line // ======================================== // // Copyright YOUR COMPANY, THE YEAR // All Rights Reserved // UNPUBLISHED, LICENSED SOFTWARE. // // CONFIDENTIAL AND PROPRIETARY INFORMATION // WHICH IS THE PROPERTY OF your company. // // ======================================== `include "cypress.v" //`#end` -- edit above this line, do not edit this line // Generated on 04/28/2013 at 19:07 // Component: DividerDp module DividerDp ( output tc, input clock, input enable, input reset ); //`#start body` -- edit after this line, do not edit this line localparam CS_LOAD = 3'd0; localparam CS_DECREMENT = 3'd1; wire[2:0] addr; wire zero; // Specify DATAPATH behavior assign addr = (zero|~enable)?(CS_LOAD):(CS_DECREMENT); // TC output assignment assign tc = zero & enable; cy_psoc3_dp8 #(.cy_dpconfig_a( { `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, `CS_SHFT_OP_PASS, `CS_A0_SRC___D0, `CS_A1_SRC_NONE, `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, `CS_CMP_SEL_CFGA, /*CFGRAM0: LOAD D0 into A0*/ `CS_ALU_OP__DEC, `CS_SRCA_A0, `CS_SRCB_D0, `CS_SHFT_OP_PASS, `CS_A0_SRC__ALU, `CS_A1_SRC_NONE, `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, `CS_CMP_SEL_CFGA, /*CFGRAM1: DECREMENT A0*/ `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, `CS_CMP_SEL_CFGA, /*CFGRAM2: */ `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, `CS_CMP_SEL_CFGA, /*CFGRAM3: */ `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, `CS_CMP_SEL_CFGA, /*CFGRAM4: */ `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, `CS_CMP_SEL_CFGA, /*CFGRAM5: */ `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, `CS_CMP_SEL_CFGA, /*CFGRAM6: */ `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0, `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC_NONE, `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA, `CS_CMP_SEL_CFGA, /*CFGRAM7: */ 8'hFF, 8'h00, /*CFG9: */ 8'hFF, 8'hFF, /*CFG11-10: */ `SC_CMPB_A1_D1, `SC_CMPA_A1_D1, `SC_CI_B_ARITH, `SC_CI_A_ARITH, `SC_C1_MASK_DSBL, `SC_C0_MASK_DSBL, `SC_A_MASK_DSBL, `SC_DEF_SI_0, `SC_SI_B_DEFSI, `SC_SI_A_DEFSI, /*CFG13-12: */ `SC_A0_SRC_ACC, `SC_SHIFT_SL, 1'h0, 1'h0, `SC_FIFO1_BUS, `SC_FIFO0_BUS, `SC_MSB_DSBL, `SC_MSB_BIT0, `SC_MSB_NOCHN, `SC_FB_NOCHN, `SC_CMP1_NOCHN, `SC_CMP0_NOCHN, /*CFG15-14: */ 10'h00, `SC_FIFO_CLK__DP,`SC_FIFO_CAP_AX, `SC_FIFO_LEVEL,`SC_FIFO__SYNC,`SC_EXTCRC_DSBL, `SC_WRK16CAT_DSBL /*CFG17-16: */ } )) dp( /* input */ .reset(reset), /* input */ .clk(clock), /* input [02:00] */ .cs_addr(addr[2:0]), /* input */ .route_si(1'b0), /* input */ .route_ci(1'b0), /* input */ .f0_load(1'b0), /* input */ .f1_load(1'b0), /* input */ .d0_load(1'b0), /* input */ .d1_load(1'b0), /* output */ .ce0(), /* output */ .cl0(), /* output */ .z0(zero), /* output */ .ff0(), /* output */ .ce1(), /* output */ .cl1(), /* output */ .z1(), /* output */ .ff1(), /* output */ .ov_msb(), /* output */ .co_msb(), /* output */ .cmsb(), /* output */ .so(), /* output */ .f0_bus_stat(), /* output */ .f0_blk_stat(), /* output */ .f1_bus_stat(), /* output */ .f1_blk_stat() ); //`#end` -- edit above this line, do not edit this line endmodule //`#start footer` -- edit after this line, do not edit this line //`#end` -- edit above this line, do not edit this line
データパスを使った場合の記述は、データパスインスタンスとその入出力制御信号で構成されています。 データパスインスタンスは、前回は "reset" と "clock" だけを使用していましたが、今回は "cs_addr" と "z0" も使用しています。
"cs_addr" は、データパスの振る舞いを選択するためのものです。 用意された振る舞いは、 "LOAD" と "DECREMENT" の2つです。 これを、 Verilog での記述と同様に "enable" と "zero" の状態によって切り替えます。
"z0" は、カウンタが "0" に達したかを示すために使用します。 カウンタは、データパス内の "A0" アキュムレータで実装されています。 "z0" 出力は、この "A0" の値が "0" に達したかどうかをデータパスの振る舞いに関係なく示すものです。
このデータパスインスタンスも、 PSoC Creator の "Datapath Config Tool" で作成しています。
設定 "Reg0" が "LOAD" 動作に相当します。 この設定では、 "D0" に入っている初期値を直接 "A0" に入れます。
設定 "Reg1" は "DECREMENT" 動作に相当します。 "A0" の内容を ALU でデクリメントして、 "A0" に入れます。
データパスで設定を行った箇所は、この2行だけです。 他のレジスタなどは、全く使用していません。
テストプログラムは、以下の通りです。
/* ======================================== * * Copyright noritan.org, 2013 * All Rights Reserved * UNPUBLISHED, LICENSED SOFTWARE. * * CONFIDENTIAL AND PROPRIETARY INFORMATION * WHICH IS THE PROPERTY OF NORITAN.ORG. * * ======================================== */ #include <device.h> void main() { /* Place your initialization/startup code here (e.g. MyInst_Start()) */ CY_SET_REG8(Divider_dp_u0__D0_REG, 99); // Set PERIOD value CR1_Write(1); /* CyGlobalIntEnable; */ /* Uncomment this line to enable global interrupts. */ for(;;) { /* Place your application code here. */ } } /* [] END OF FILE */
Control Reg でイネーブルしているのは、 Verilog で書いたものと同じですが、それに加えて、周期を設定する行が追加されています。 データパスを使用した場合、あらかじめ初期値を "D0" に入れておかなくてはなりません。 通常は、 _Start() などの API を作成しておいて、 main() 関数から呼び出しますが、今回も横着をして API は、作成していません。 その代わりに、データパス内のレジスタ "D0" を直接操作しています。 100分周する場合の初期値には、 "99" を使用します。
消費資源の比較
今回も Verilog を使用した場合とデータパスを使用した場合で、消費資源を比較してみました。
- Verilog での実装
Resource Type : Used : Free : Max : % Used ============================================================ Digital domain clock dividers : 1 : 7 : 8 : 12.50% Analog domain clock dividers : 0 : 4 : 4 : 0.00% Pins : 4 : 68 : 72 : 5.56% UDB Macrocells : 10 : 182 : 192 : 5.21% UDB Unique Pterms : 22 : 362 : 384 : 5.73% UDB Total Pterms : 23 : : : UDB Datapath Cells : 0 : 24 : 24 : 0.00% UDB Status Cells : 0 : 24 : 24 : 0.00% UDB Control Cells : 1 : 23 : 24 : 4.17% Control Registers : 1 DMA Channels : 0 : 24 : 24 : 0.00% Interrupts : 0 : 32 : 32 : 0.00%
- データパスでの実装
Resource Type : Used : Free : Max : % Used ============================================================ Digital domain clock dividers : 1 : 7 : 8 : 12.50% Analog domain clock dividers : 0 : 4 : 4 : 0.00% Pins : 4 : 68 : 72 : 5.56% UDB Macrocells : 2 : 190 : 192 : 1.04% UDB Unique Pterms : 2 : 382 : 384 : 0.52% UDB Total Pterms : 2 : : : UDB Datapath Cells : 1 : 23 : 24 : 4.17% UDB Status Cells : 0 : 24 : 24 : 0.00% UDB Control Cells : 1 : 23 : 24 : 4.17% Control Registers : 1 DMA Channels : 0 : 24 : 24 : 0.00% Interrupts : 0 : 32 : 32 : 0.00%
このように、マクロセルの消費が大幅に減ったことがわかります。
タイミング余裕の比較
今回の実装は、順序回路で組んであるので、クロック周波数が高くなると、タイミング制約を満たしません。 PSoC Creator のタイミングレポートを確認してみました。
- Verilog での実装
-
Clock Domain Nominal Frequency Required Frequency Maximum Frequency Violation CyILO CyILO 1.000 kHz 1.000 kHz N/A CyIMO CyIMO 3.000 MHz 3.000 MHz N/A CyMASTER_CLK CyMASTER_CLK 24.000 MHz 24.000 MHz N/A Clock_400Hz CyMASTER_CLK 400.000 Hz 400.000 Hz 64.123 MHz CyBUS_CLK CyMASTER_CLK 24.000 MHz 24.000 MHz 108.108 MHz CyPLL_OUT CyPLL_OUT 24.000 MHz 24.000 MHz N/A - データパスでの実装
-
Clock Domain Nominal Frequency Required Frequency Maximum Frequency Violation CyILO CyILO 1.000 kHz 1.000 kHz N/A CyIMO CyIMO 3.000 MHz 3.000 MHz N/A CyMASTER_CLK CyMASTER_CLK 24.000 MHz 24.000 MHz N/A Clock_400Hz CyMASTER_CLK 400.000 Hz 400.000 Hz 42.838 MHz CyBUS_CLK CyMASTER_CLK 24.000 MHz 24.000 MHz 45.354 MHz CyPLL_OUT CyPLL_OUT 24.000 MHz 24.000 MHz N/A
このように、 Verilog で書いた場合には 64MHz での動作も可能だが、データパスで書いた場合には、 42MHz が最大周波数になってしまいます。 信号伝達経路を確認した所、データパスの入力 "cs_addr" のセットアップ時間がもっとも厳しいようです。 このあたりは、 PSoC が内蔵する UDB の構成に関わってきますので、いたしかた無いところでしょうか。
Timer でも、同じことができる
ここまでは、カスタムコンポーネントを作成して分周器を構成してきました。 おなじ機能は、 Timer コンポーネントでも実現することができます。
これまでの構成と同様に enable 入力を外部に持たせて、 Control Reg で制御するようにしてあります。
/* ======================================== * * Copyright noritan.org, 2013 * All Rights Reserved * UNPUBLISHED, LICENSED SOFTWARE. * * CONFIDENTIAL AND PROPRIETARY INFORMATION * WHICH IS THE PROPERTY OF NORITAN.ORG. * * ======================================== */ #include <device.h> void main() { /* Place your initialization/startup code here (e.g. MyInst_Start()) */ Divider_Start(); CR1_Write(1); /* CyGlobalIntEnable; */ /* Uncomment this line to enable global interrupts. */ for(;;) { /* Place your application code here. */ } } /* [] END OF FILE */
テストプログラムも、初期化を行うだけで終わりです。
消費資源を調べてみました。
Resource Type : Used : Free : Max : % Used ============================================================ Digital domain clock dividers : 1 : 7 : 8 : 12.50% Analog domain clock dividers : 0 : 4 : 4 : 0.00% Pins : 4 : 68 : 72 : 5.56% UDB Macrocells : 3 : 189 : 192 : 1.56% UDB Unique Pterms : 2 : 382 : 384 : 0.52% UDB Total Pterms : 3 : : : UDB Datapath Cells : 1 : 23 : 24 : 4.17% UDB Status Cells : 1 : 23 : 24 : 4.17% StatusI Registers : 1 UDB Control Cells : 1 : 23 : 24 : 4.17% Control Registers : 1 DMA Channels : 0 : 24 : 24 : 0.00% Interrupts : 0 : 32 : 32 : 0.00%
先にデータパスを使って較正した場合に比べて、ちょっとだけ多めに資源を使っています。 これは、カスタムコンポーネントでは取り入れていないステータスレジスタを装備している事などが原因となっています。 標準コンポーネントを使っていて、どうしても資源が足りなくなってきたら、省資源に徹したカスタムコンポーネントを作成するのも、手段の一つかもしれません。 まあ、あまりお勧めはできませんが。
プロジェクトアーカイブ
この記事を書くために作成したプロジェクトは、このファイルの拡張子を ZIP に変更して展開すると再現できます。
関連書籍
シリーズ最強!PSoC 3ボード+デバッグ・ボード: あのPSoCが生まれ変わった!アナログもディジタルも一新 (トライアルシリーズ)
- 作者: 古平 晃洋
- 出版社/メーカー: CQ出版
- 発売日: 2012/10/23
- メディア: 単行本
コメント 0