PSoC 3 で、倍増器を作った [PSoC]
前回の FIFO の実験では、 FIFO に入れたデータがそのまま FIFO から出てきました。 このデータを途中で加工してやると、計算機が出来上がります。 今回は、入れたデータの値の二倍の値のデータが出てくる、名付けて倍増器を作ります。
データフロー図
今回は、データパスの動作をデータフローで表現しました。 左端にある "state" が、データパスを制御するステートマシンの状態です。 そして、橙色の破線が、クロックタイミングを表現しています。 つまり、この倍増器の場合、 FIFO0 にデータが到着してから FIFO1 にデータが出てくるまで、4クロックを要するという事になります。
- IDLE
この図では省略されていますが、 IDLE 状態では、 FIFO0 へのデータの到着を待っています。 データパスから出力される制御信号 f0_blk_stat (f0_empty) が、データの到着を知らせてくれます。 データが到着したら、次の GET 状態に遷移します。
- GET
データが到着したら、1クロックで、二つの作業を同時に行います。
一つは、 ALU を使って、アキュムレータ A0 の値を更新することです。 この時に使用される演算は、 A0 XOR A0 で、 A0 をゼロにクリアします。 XOR 演算は、ビットごとの排他的論理和を表現しています。 この XOR 演算に同じ値を与えると、与えた値がどのような値でも、結果が "0" になるという性質を利用したものです。
もうひとつの作業は、 FIFO0 に到着したデータをデータレジスタ D0 にコピーすることです。 制御信号 d0_load をアサートすると、 FIFO0 から D0 にデータがコピーされます。 この図では、 "d" というデータがコピーされてきます。 単に2倍の値を計算するためであれば、アキュムレータにデータをコピーするという選択肢もありますが、ここでは、後の拡張を考慮してデータレジスタを使用しています。
GET 状態の次は、 ADD 状態に遷移します。
- ADD
この状態では、 ALU を使用して A0 + D0 の演算を行い、 A0 に格納します。 GET 状態で、 A0 にはゼロ、 D0 には "d" が格納されましたので、この状態では A0 に "d" が格納されることになります。 次は、 DOUBLE 状態に遷移します。
- DOUBLE
この状態では、 ALU を使用して A0 + A0 の演算を行い、 A0 に格納します。 A0 の値は、 "2d" となります。 つまり、このステップでは、 A0 の値が2倍されたことになります。 次は、 WAIT 状態に遷移します。
- WAIT
この図では省略されていますが、 WAIT 状態では、データパスの制御信号 f1_blk_stat (f1_full) を使って FIFO1 にデータを書き込めるようになるまで、待ち合わせを行います。 FIFO1 が受け入れ可能な状態になったら、 PUT 状態に遷移します。
- PUT
この状態では、データパスの制御信号 f1_load をアサートすることで、 A0 の値 "2d" を ALU を介して FIFO1 に書き込みます。
FIFO1 の書き込みには、 A0 の値を直接書き込む機能もあるのですが、今回は、 ALU を経由する方法を採用しています。 これは、 FIFO1 への書き込み経路が、データパスの機能上、動的に設定できる項目ではないという事情があるためです。 ALU を介する設定を使用することで、柔軟にデータの経路を決めることができます。
データを FIFO1 に書き込んだら、次のデータの到着を待つため、 IDLE 状態に遷移します。
倍増器の実装
以上、倍増器の説明をデータフローで解説しました。 あとは、倍増器の実装を 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 05/18/2013 at 21:08 // Component: DoublerAdd module DoublerAdd ( input clock, input en, input reset ); //`#start body` -- edit after this line, do not edit this line // State code declaration localparam ST_IDLE = 3'b000; localparam ST_GET = 3'b001; localparam ST_ADD = 3'b010; localparam ST_DOUBLE = 3'b011; localparam ST_WAIT = 3'b100; localparam ST_PUT = 3'b101; // Datapath function declaration localparam CS_IDLE = 3'b000; localparam CS_CLEAR = 3'b001; localparam CS_ADD = 3'b010; localparam CS_DOUBLE = 3'b011; // Wire declaration wire[2:0] state; // State code wire f0_empty; // F0 is EMPTY wire f1_full; // F1 is FULL // Pseudo register reg[2:0] addr; // Datapath function reg d0_load; // LOAD D0 from F0 reg f1_load; // LOAD F1 // State machine behavior reg [2:0] state_reg; always @(posedge reset or posedge clock) begin if (reset) begin state_reg <= ST_IDLE; end else casez (state) ST_IDLE: if (en & !f0_empty) begin state_reg <= ST_GET; end ST_GET: state_reg <= ST_ADD; ST_ADD: state_reg <= ST_DOUBLE; ST_DOUBLE: state_reg <= ST_WAIT; ST_WAIT: if (!f1_full) begin state_reg <= ST_PUT; end /*ST_PUT*/ default: state_reg <= ST_IDLE; endcase end assign state = state_reg; // Internal control signals always @(state) begin casez (state) ST_IDLE: begin addr = CS_IDLE; d0_load = 1'b0; f1_load = 1'b0; end ST_GET: begin addr = CS_CLEAR; d0_load = 1'b1; f1_load = 1'b0; end ST_ADD: begin addr = CS_ADD; d0_load = 1'b0; f1_load = 1'b0; end ST_DOUBLE: begin addr = CS_DOUBLE; d0_load = 1'b0; f1_load = 1'b0; end ST_WAIT: begin addr = CS_IDLE; d0_load = 1'b0; f1_load = 1'b0; end /*ST_PUT*/ default: begin addr = CS_IDLE; d0_load = 1'b0; f1_load = 1'b1; end endcase end cy_psoc3_dp8 #(.cy_dpconfig_a( { `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, /*CFGRAM0: IDLE : Do nothing*/ `CS_ALU_OP__XOR, `CS_SRCA_A0, `CS_SRCB_A0, `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: CLEAR : A0<=ALU<=0;*/ `CS_ALU_OP__ADD, `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, /*CFGRAM2: ADD : A0<=ALU<=A0+D0;*/ `CS_ALU_OP__ADD, `CS_SRCA_A0, `CS_SRCB_A0, `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, /*CFGRAM3: DOUBLE : A0<=ALU<=A0+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, /*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__A0, `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), /* input */ .route_si(1'b0), /* input */ .route_ci(1'b0), /* input */ .f0_load(1'b0), /* input */ .f1_load(f1_load), /* input */ .d0_load(d0_load), /* input */ .d1_load(1'b0), /* output */ .ce0(), /* output */ .cl0(), /* output */ .z0(), /* 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(f0_empty), /* output */ .f1_bus_stat(), /* output */ .f1_blk_stat(f1_full) ); //`#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
ステートマシンのそれぞれの状態に応じて、データパスの構成が変更されていきますが、これは、データパスの制御信号 cs_addr に与えるアドレスで指定することができます。
おおまかには、前回の8段 FIFO の実装を踏襲したものになっています。
データパスの設定
データパスの CGRAM は、 Reg0 から Reg3 までの4つの設定を使っています。 それぞれの設定には、 IDLE, GET, ADD, DOUBLE の名前がつけてあり、データフロー図と Verilog 記述に対応させています。
そのほかの設定では、 F1 INSEL を ALU に設定して、 FIFO1 への書き込みを ALU から行うようにしています。
倍増器へ書き込むテストプログラム
動作確認を行うために、テストプログラムを作成しました。 前回の FIFO を扱うプログラムと同様に FIFO0 に値を書き込み、 FIFO1 から値を読み出しています。
/* ======================================== * * 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() { uint8 i = 0; uint8 v[16]; volatile uint8 w = 0; CR1_Write(1); CY_SET_REG8(Doubler_dp_u0__F0_REG, w++); for (i = 0; i < 16; i++) { CY_SET_REG8(Doubler_dp_u0__F0_REG, w++); v[ i] = CY_GET_REG8(Doubler_dp_u0__F1_REG); } LCD_Start(); LCD_Position(0, 0); for (i = 0; i < 8; i++) { LCD_PrintInt8(v[i]); } LCD_Position(1, 0); for (i = 8; i < 16; i++) { LCD_PrintInt8(v[i]); } CY_SET_REG8(Doubler_dp_u0__F0_REG, i++); CY_SET_REG8(Doubler_dp_u0__F0_REG, i++); /* CyGlobalIntEnable; */ /* Uncomment this line to enable global interrupts. */ for(;;) { CY_SET_REG8(Doubler_dp_u0__F0_REG, i++); w = CY_GET_REG8(Doubler_dp_u0__F1_REG); } } /* [] END OF FILE */
前回同様、 0 から 15 までの値を FIFO0 に書き込みながら、 FIFO1 から演算結果を取り出し、 LCD に表示します。 最初の部分をよく見るとわかりますが、 FIFO0 への書き込みが、1バイトだけ先行しています。 これは、 FIFO0 に書き込んでから FIFO1 にデータが準備できるまで、6クロック必要になるので、単純な書き込みと読み出しでは、処理が間に合わないためです。 書き込みを先行させることで、確実に処理が終わってから値を読み出す、いわゆるパイプライン構成にすることができました。
シフタを使った実装
データパスに内蔵されている ALU は、さらに複雑な計算を行わせることができます。 ここでは、 ALU 演算の出口部分にあるシフタを利用して、元の値を二倍にします。
このデータフロー図は、最初のデータフロー図とよく似ていますが、二つの状態 ADD と DOUBLE がひとつの状態 STEP1 にまとめられています。 これは、 ALU の出力にシフタと呼ばれる仕掛けがあるからです。 シフタは、 ALU の出力を1ビットだけ、右または左にシフトします。 値は、右にシフトすると 1/2 になり、左にシフトすると2倍になります。 つまり、2倍の値を得るために ALU の加算を使う必要がありません。
STEP1 状態では、 A0 と D0 の値を足して、シフタで2倍にして、 A0 に格納しています。 結果として、 A0 には、 D0 の値 "d" の2倍の値が入るというわけです。
ALU の設定も3種類に減りました。 シフタの操作は、 SHIFT カラムで決定します。
プロジェクトアーカイブ
この記事を書くために作成したプロジェクトは、このファイルの拡張子を ZIP に変更して展開すると再現できます。
関連文献
シリーズ最強!PSoC 3ボード+デバッグ・ボード: あのPSoCが生まれ変わった!アナログもディジタルも一新 (トライアルシリーズ)
- 作者: 古平 晃洋
- 出版社/メーカー: CQ出版
- 発売日: 2012/10/23
- メディア: 単行本
コメント 0