SSブログ

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 の設定

データパスの CGRAM は、 Reg0 から Reg3 までの4つの設定を使っています。 それぞれの設定には、 IDLE, GET, ADD, DOUBLE の名前がつけてあり、データフロー図と Verilog 記述に対応させています。

F1 INSEL の設定

そのほかの設定では、 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 の設定

ALU の設定も3種類に減りました。 シフタの操作は、 SHIFT カラムで決定します。

プロジェクトアーカイブ

この記事を書くために作成したプロジェクトは、このファイルの拡張子を ZIP に変更して展開すると再現できます。

関連文献


PSoC 3 で、 FIFO バッファを作った [PSoC]このエントリーを含むはてなブックマーク#

FIFO実験の回路図

データパスには、4段の FIFO が二本入っています。 今回は、この二本の FIFO を接続して8段の FIFO を構成します。

FIFO の使い方

FIFO の概略図

データパスが内蔵している FIFO は、原則として、 CPU などが接続されるバスとデータパスのレジスタを仲介します。 CPU がデータを書込んでデータパスのレジスタに引き渡すものを "Input mode" と、データパスで生成されたデータを CPU に引き渡すものを "Output mode" と呼んでいます。 前回のジェネレータで使用したのは、 "Output mode" の FIFO です。


Input mode

"Input mode" では、 CPU などを使ってバスから FIFO に書き込んだデータをアキュムレータ (Ax) またはデータレジスタ (Dx) に引き渡します。 アキュムレータに渡す場合には、データパスの設定 "Ax WR SRC" で FIFO (Fx) を指定します。 また、データレジスタに渡す場合には、外部制御信号 dx_load の立ち上がりを使用します。

このモードでは、二つのフラグ fx_bus_stat と fx_blk_stat が出力されます。 fx_bus_stat は、 CPU などのバス側の制御に通知を行うための信号で、 FIFO が FULL である時にアサートされます。 このフラグのネゲートにより、新たな値を書込むことが可能である事を示します。 また、 fx_blk_stat は、データパス側の制御に通知を行うための信号で、 FIFO が EMPTY である時にアサートされます。 このフラグのネゲートにより、次のデータを取り出すことが可能である事を示します。

Output mode

"Output mode" では、アキュムレータまたは ALU の出力の値を FIFO の入力とします。 どの値を入力するかは、データパスの設定 "Fx INSEL" で指定します。 データを取り込むタイミングは、外部制御信号 fx_load の立ち上がりを使います。

このモードでも、二つのフラグ fx_bus_stat と fx_blk_stat が出力されますが、 "Input mode" の時とは意味が逆になっています。 fx_bus_stat は、 CPU などのバス側の制御に通知を行うための信号で、 FIFO が EMPTY である時にアサートされます。 また、 fx_blk_stat は、データパス側の制御に通知を行うための信号で、 FIFO が FULL である時にアサートされます。

8段 FIFO の実装

今回の実験では、 FIFO0 を "Input mode" で使用し、 FIFO1 を "Output mode" で使用します。 そして、これらの FIFO の制御を行うステートマシンで FIFO 間のデータ転送を行います。

//`#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/05/2013 at 21:01
// Component: Fifo8Ntan01
module Fifo8Ntan01 (
	input   clock,
	input   en,
	input   reset
);

//`#start body` -- edit after this line, do not edit this line

// State code declaration
localparam      ST_IDLE = 2'b00;
localparam      ST_GET  = 2'b01;
localparam      ST_WAIT = 2'b11;
localparam      ST_PUT  = 2'b10;

// Datapath function declaration
localparam      CS_IDLE = 3'b000;
localparam      CS_FEED = 3'b001;

// Wire declaration
wire[1: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             f1_load;        // LOAD control to F1

// State machine behavior
reg [1: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_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;
            f1_load = 1'b0;
        end
        ST_GET: begin
            addr = CS_FEED;
            f1_load = 1'b0;
        end
        ST_WAIT: begin
            addr = CS_IDLE;
            f1_load = 1'b0;
        end
        /*ST_PUT*/ default: begin
            addr = CS_IDLE;
            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*/
    `CS_ALU_OP_PASS, `CS_SRCA_A0, `CS_SRCB_D0,
    `CS_SHFT_OP_PASS, `CS_A0_SRC___F0, `CS_A1_SRC_NONE,
    `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
    `CS_CMP_SEL_CFGA, /*CFGRAM1: FEED: A0:=F0*/
    `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__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(1'b0),
        /*  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

ステートマシンは、四つの状態で構成されています。

IDLE
f0_empty (f0_blk_stat) を監視して、 FIFO0 にデータが入るのを待ちます。 ALU は、何もしません。
GET
ALU を駆動して、 FIFO0 の値をアキュムレータ A0 に取り出します。
WAIT
f1_full (f1_blk_stat) を監視して、 FIFO1 に空きができるのを待ちます。 アキュムレータ A0 には、 GET で FIFO0 から取り出した値が入ったままになっています。
PUT
f1_load をアサートして、アキュムレータ A0 の値を FIFO1 に転送します。
FIFO 動作のタイミング図

これら一連の動作を経て、1バイトのデータが FIFO0 から FIFO1 に転送されていきます。 FIFO1 が FULL の状態で FIFO0 にデータが到着した場合には、状態 WAIT で待ち合わせを行います。

データパスの設定

データパスの設定

データパスは、 Reg0 と Reg1 をそれぞれ IDLE と FEED に割り当てています。 IDLE は何もしない状態、 FEED は FIFO0 から A0 にデータを取り出す状態です。

FIFO1 の入力選択

FIFO1 へは、アキュムレータ A0 の値が送り込まれます。 この設定を行うのが、 "F1 INSEL" です。 データパスの設定で、変更されたのは、以上の二点です。

FIFO へアクセスするテストプログラム

動作試験のために作成されたテストプログラムは、今回も、最初の16バイトの出力を LCD に表示させるものとしました。

/* ========================================
 *
 * 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;
    
    CR1_Write(1);
    CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
    CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
    CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
    CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
    CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
    CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
    CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
    CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
    v[ 0] = CY_GET_REG8(Fifo_dp_u0__F1_REG);    // =0
    v[ 1] = CY_GET_REG8(Fifo_dp_u0__F1_REG);    // =1
    v[ 2] = CY_GET_REG8(Fifo_dp_u0__F1_REG);    // =2
    v[ 3] = CY_GET_REG8(Fifo_dp_u0__F1_REG);    // =3
    v[ 4] = CY_GET_REG8(Fifo_dp_u0__F1_REG);    // =4
    v[ 5] = CY_GET_REG8(Fifo_dp_u0__F1_REG);    // =5
    v[ 6] = CY_GET_REG8(Fifo_dp_u0__F1_REG);    // =6
    v[ 7] = CY_GET_REG8(Fifo_dp_u0__F1_REG);    // =7
    v[ 8] = CY_GET_REG8(Fifo_dp_u0__F1_REG);    // =4
    v[ 9] = CY_GET_REG8(Fifo_dp_u0__F1_REG);    // =4
    CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
    v[10] = CY_GET_REG8(Fifo_dp_u0__F1_REG);    // =8
    CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
    CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
    v[11] = CY_GET_REG8(Fifo_dp_u0__F1_REG);    // =9
    CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
    CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
    v[12] = CY_GET_REG8(Fifo_dp_u0__F1_REG);    // =10
    CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
    v[13] = CY_GET_REG8(Fifo_dp_u0__F1_REG);    // =11
    v[14] = CY_GET_REG8(Fifo_dp_u0__F1_REG);    // =12
    v[15] = CY_GET_REG8(Fifo_dp_u0__F1_REG);    // =13
    
    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(Fifo_dp_u0__F0_REG, i++);
    CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
    CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
    CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
        
    /* CyGlobalIntEnable; */ /* Uncomment this line to enable global interrupts. */
    for(;;)
    {
        CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
        CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
        CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
        CY_SET_REG8(Fifo_dp_u0__F0_REG, i++);
        w = CY_GET_REG8(Fifo_dp_u0__F1_REG);
        w = CY_GET_REG8(Fifo_dp_u0__F1_REG);
        w = CY_GET_REG8(Fifo_dp_u0__F1_REG);
        w = CY_GET_REG8(Fifo_dp_u0__F1_REG);
    }
}

/* [] END OF FILE */

初期化を終えたら、まず、 0 から 7 までの値を FIFO に書き込みます。 書込まれたデータは、 0 から 3 までが FIFO1 に、 4 から 7 までが FIFO0 に格納されます。

次にデータを10回読み出します。 FIFO には、8個しか値が入っていませんので、 0 から 7 までの値が出て行ったあとは、ゴミデータが読み出されます。 この例では、 4 という値が出ています。 本来は、 EMPTY フラグ (f1_blk_stat) を実装して、フラグを見ながら値を読み出していくのが、正しい使い方になります。

その後は、数個ずつ FIFO への書き込みと読み出しを行って、順番通りの値が読み出されている事を確認しています。

メインループでは、4個ずつデータの書き込みと読み出しを行っています。 デバッガのステップ動作で、読み出された値を確認することができます。

プロジェクトアーカイブ

この記事を書くために作成したプロジェクトは、このファイルの拡張子を ZIP に変更して展開すると再現できます。

関連文献


PSoC 3 で、ジェネレータを作った [PSoC]このエントリーを含むはてなブックマーク#

ジェネレータ実験回路

今回も、データパスを使った実験です。 データを吐き出してくれる「ジェネレータ」というモジュールを作りました。

ジェネレータとは

ジェネレータ(generator)は、 Python に出てきた用語です。 ジェネレータは、次々にデータを生成して、呼び出し側の要求に応じて引き渡してくれます。 一見すると、呼び出し側とは非同期に動いているように見えるので、逐次型処理にこだわりが無ければ、使いやすい仕掛けです。

今回作成するジェネレータは、データを要求する CPU や DMA とは独立にデータを生成するハードウェアブロックです。 ハードウェアで構成されるから、本当に並列な動作が可能になります。

ジェネレータの出力に FIFO を使う

待ち合わせが必要なジェネレータ

ジェネレータは、 CPU や DMA とは、無関係にデータを生成することができます。 もし、 CPU からのデータ要求が来てからデータの準備をすると、データを準備する時間が CPU の待ち時間となってしまい、処理速度の低下を招きます。


FIFOを使ったジェネレータ

そこで、あらかじめデータを生成するとともにバッファに蓄積しておき、データ要求が発生したら、バッファからデータを引き渡す仕組みを取り入れます。 このとき、引き渡されるデータは、生成された順番に引き渡す必要があります。 このように、生成された順番にデータを引き渡すバッファを FIFO (First In First Out) と呼びます。

データパスのレジスタ部には、この FIFO バッファが内蔵されています。 これを使って、簡単にジェネレータを構成できます。

作成するジェネレータの機能

ここで作成するジェネレータは、 8-bit のデータを延々と生成して FIFO に格納します。 FIFO のデータは、内部データバスを通じて CPU または DMA から取り出すことができます。 肝心の格納されるデータですが、単純に 8-bit カウンタの値を構成して順番に FIFO に格納します。

//`#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/03/2013 at 21:52
// Component: FifoNtan01
module FifoNtan01 (
	input   clock,
	input   en,
	input   reset
);

//`#start body` -- edit after this line, do not edit this line

// Internal wires
wire        state;          // State machine state
wire        f0_blk_stat;    // FIFO status

// Pseudo registers
reg[2:0]    addr;           // Datapath Function
reg         f0_load;        // Put into FIFO

localparam  ST_HOLD = 1'b0;
localparam  ST_GENERATE = 1'b1;

localparam  CS_HOLD = 3'd0;
localparam  CS_GENERATE = 3'd1;

// State machine
reg         state_reg;
always @(posedge reset or posedge clock) begin
    if (reset) begin
                state_reg <= ST_HOLD;
    end else casez (state)
        ST_HOLD: begin
            if (en && !f0_blk_stat) begin
                state_reg <= ST_GENERATE;   // Put into FIFO
            end
        end
        /*ST_GENERATE*/ default: begin
                state_reg <= ST_HOLD;       // Generate LOAD pulse
        end
    endcase
end
assign      state = state_reg;

// Internal signal assignments
always @(state) begin
    casez (state)
        ST_HOLD: begin
            f0_load = 1'b0;
            addr = CS_HOLD;
        end
        /*ST_GENERATE*/ default: begin
            f0_load = 1'b1;
            addr = CS_GENERATE;
        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: HOLD*/
    `CS_ALU_OP__INC, `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: GENERATE*/
    `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__A0,
    `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(f0_load),
        /*  input                   */  .f1_load(1'b0),
        /*  input                   */  .d0_load(1'b0),
        /*  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_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

ジェネレータの内部は、2状態のステートマシンとデータパスから構成されています。 ステートマシンは、待機状態 (HOLD) とデータ生成状態 (GENERATE) の間を遷移します。

ジェネレータの動作

待機状態の時、 FIFO に空きがある (!f0_blk_stat) 事を検出したら、データ生成状態に遷移します。 データ生成状態では、アキュムレータ A0 の値を FIFO に格納すべく f0_load をアサートし、ALU を使用して次のデータを 生成します。 データ生成が終わったら、必ず待機状態に戻り、 f0_load をネゲートします。 これは、 f0_load 信号がレベルではなく、エッジで駆動される信号だからです。 en 信号がネゲートされると、ジェネレータは、データの生成を中断します。

データパスに内蔵されている FIFO は、4レベルのバッファで構成されています。 しかし、ジェネレータでは、 FIFO にいくつデータが入っているかは管理していません。 その代わりに、 FIFO が FULL であるか否かを f0_blk_stat という信号で認識して、状態遷移を行っています。 FULL で無ければ、新しい値を生成して、 FIFO に格納するという仕組みです。

データパスの設定

データパスの設定

ALU の設定では、 A0 をインクリメントする動作を Reg1 に設定しています。 この設定を使用して、ジェネレータは次の値を生成します。


FIFO の設定

データパスは、もう1か所、 F0 SEL の設定を変更しています。 "F0 SEL" は、 FIFO 0 へ格納するデータをどこから取り入れるかを指定しています。 ここでは、 "A0" を指定しています。 これで、アキュムレータ A0 の値が、データパスの入力信号 "f0_load" の立ち上がりタイミングで FIFO に送られます。

FIFO へのアクセス

FIFO に格納された値は、 CPU からレジスタアクセスによって読み出すことができます。 テストプログラムでは、 en 信号をアサート・ネゲートしながら、 FIFO の値を配列に読み出し、 LCD に表示しています。

/* ========================================
 *
 * 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   v[16];
    uint8   i;
    volatile uint8  w;
    
    LCD_Start();
    CY_SET_REG8(Fifo_dp_u0__A0_REG, 0);
    CR1_Write(1);

    /* CyGlobalIntEnable; */ /* Uncomment this line to enable global interrupts. */
    v[ 0] = CY_GET_REG8(Fifo_dp_u0__F0_REG);    // = 0
    v[ 1] = CY_GET_REG8(Fifo_dp_u0__F0_REG);    // = 1
    v[ 2] = CY_GET_REG8(Fifo_dp_u0__F0_REG);    // = 2
    v[ 3] = CY_GET_REG8(Fifo_dp_u0__F0_REG);    // = 3
    v[ 4] = CY_GET_REG8(Fifo_dp_u0__F0_REG);    // = 4
    CR1_Write(0);
    v[ 5] = CY_GET_REG8(Fifo_dp_u0__F0_REG);    // = 5
    v[ 6] = CY_GET_REG8(Fifo_dp_u0__F0_REG);    // = 6
    v[ 7] = CY_GET_REG8(Fifo_dp_u0__F0_REG);    // = 7
    v[ 8] = CY_GET_REG8(Fifo_dp_u0__F0_REG);    // = 8
    v[ 9] = CY_GET_REG8(Fifo_dp_u0__F0_REG);    // = 5
    v[10] = CY_GET_REG8(Fifo_dp_u0__F0_REG);    // = 5
    v[11] = CY_GET_REG8(Fifo_dp_u0__F0_REG);    // = 5
    CR1_Write(1);
    v[12] = CY_GET_REG8(Fifo_dp_u0__F0_REG);    // = 9
    v[13] = CY_GET_REG8(Fifo_dp_u0__F0_REG);    // = 10
    v[14] = CY_GET_REG8(Fifo_dp_u0__F0_REG);    // = 11
    v[15] = CY_GET_REG8(Fifo_dp_u0__F0_REG);    // = 12

    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]);
    }
    for(;;)
    {
        w = CY_GET_REG8(Fifo_dp_u0__F0_REG);
    }
}

/* [] END OF FILE */

v[0] から v[4] までは、ジェネレータが動作した状態で FIFO の値を読み出しています。 CPU は、 0 から順番に 4 までの値を取得します。

v[5] から v[11] までは、ジェネレータを停止させた状態で FIFO の値を読み出しています。 FIFO は、4レベルの値を格納することができますので、 v[5] から v[8] までは、 FIFO に格納されていた 5 から 8 までの値が取り出されます。 ところが、 v[9] から v[11] では、 FIFO に格納された値が無い状態、すなわち FIFO が EMPTY の状態で値を取り出そうとしているため、正しい値が返ってきません。 実際に FIFO を使用する際には、値を読み出す前に FIFO に格納された値が残っているか否かを検査する必要があります。 今回のプロジェクトでは、実装していませんが、 fifo_bus_stat 信号がこの検査のために使用できます。 この信号の状態を、ステータスレジスタなどを通じて CPU に知らせる事が出来れば、あらかじめ FIFO から値を読み出すことができるかを知ることができます。

最後に v[12] から v[15] では、ジェネレータを再開した状態で FIFO から値を読み出しています。 停止前に最後にジェネレータから取得された値が 8 だったので、再開されたジェネレータが生成する値は、 9 から始まっています。

ジェネレータは、 CPU の動作と無関係に動作しますので、ジェネレータのクロックを遅くしても、 CPU がジェネレータから受け取る値に変わりはありません。 ただし、 CPU の読み出しが速すぎて、 FIFO が EMPTY の状態で読み出してしまうような事態が発生すると、ゴミデータが読み出されてしまう場合があります。

プロジェクトアーカイブ

この記事を書くために作成したプロジェクトは、このファイルの拡張子を ZIP に変更して展開すると再現できます。

関連文献


PSoC 3 で、 PWM を作った [PSoC]このエントリーを含むはてなブックマーク#

今日のテーマは、車輪の再発明、 PWM を作ります。

分周器のおさらい

分周器

前回作成した「分周器」は、決められた数のクロックごとに "tc" 出力をアサートするものでした。 内部動作は、以下のタイミング図のようになっています。


分周器のタイミング図

分周器の内部では、カウンタの値が "0" になったかを示す "zero" が周期的にアサートされています。 "tc" 出力のタイミングは、通常動作時には、 "zero" と同時になります。 カウンタは、与えられた周期 (n) よりも1小さい値 (n-1) からデクリメントされていきますので、 "tc" 出力の周期は、 n と等しくなります。 実際の回路では、 "tc" をクロックでサンプリングすることにより、 LED 出力を反転しています。

PWM への応用

PWM出力

今回作成しようとしている PWM 出力は、分周器を応用することで作成することができます。 PWM の動作は、以下のタイミング図で示されます。


PWM のタイミング図

分周器に加えて、カウンタの値とパルス幅の値を比較する回路を取り入れると、 PWM 出力が得られます。 このタイミング図では、パルス幅を "2" と "3" に設定した場合を示しています。 カウンタの値がパルス幅の値(2または3)よりも小さい場合、ディジタル比較器の出力 less0 または less1 がアサートされています。 この信号をクロックでサンプリングすることにより、 PWM の出力 pwm0 および pwm1 が得られます。

Verilog での実装

Verilog での実装

最初は、 Verilog で記述します。 分周器の出力 "tc" の代わりに "pwm1" と "pwm2" が出力されています。

//`#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/29/2013 at 17:34
// Component: PwmUdb
module PwmUdb (
	output  pwm0,
	output  pwm1,
	input   clock,
	input   en,
	input   reset
);
	parameter DUTY0 = 30;
	parameter DUTY1 = 60;
	parameter PERIOD = 100;

//`#start body` -- edit after this line, do not edit this line

    wire[7:0]   count;      // counter value
    wire        zero;       // count == 0
    wire        less0;      // count < DUTY0
    wire        less1;      // count < DUTY1
    
    // 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 (!en) begin
            count_reg <= (PERIOD - 1);
        end else if (zero) begin
            count_reg <= (PERIOD - 1);
        end else begin
            count_reg <= count - 1;
        end
    end
    
    // PWM0 output
    reg     pwm0_reg;
    always @(posedge reset or posedge clock) begin
        if (reset) begin
            pwm0_reg <= 1'b0;
        end else if (!en) begin
            pwm0_reg <= 1'b0;
        end else begin
            pwm0_reg <= less0;
        end
    end
    
    // PWM1 output
    reg     pwm1_reg;
    always @(posedge reset or posedge clock) begin
        if (reset) begin
            pwm1_reg <= 1'b0;
        end else if (!en) begin
            pwm1_reg <= 1'b0;
        end else begin
            pwm1_reg <= less1;
        end
    end
    
    // Signal assignment
    assign      count = count_reg[7:0];
    assign      pwm0  = pwm0_reg;
    assign      pwm1  = pwm1_reg;
    assign      zero  = (count[7:0] == 8'd0);
    assign      less0 = (count[7:0] < DUTY0);
    assign      less1 = (count[7:0] < DUTY1);

//`#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

部品のシンボルが、窮屈になったので、イネーブル入力を "enable" から "en" に変更しましたが、機能は同じです。 "tc" 出力を生成する代わりに、 "pwm0" と "pwm1" を生成するフリップフロップが配置されています。

この動作記述をご覧になってお分かりのように、 PWM の周期もデューティも通常の PWM が可変であるのに対して固定となっています。 この設定では、 "pwm0" が 30%、 "pwm1" が 60% のデューティーを持っています。

テストプログラムは、以下のようになっています。

/* ========================================
 *
 * 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()) */
    CyDelay(11);
    CR1_Write(1);

    /* CyGlobalIntEnable; */ /* Uncomment this line to enable global interrupts. */
    for(;;)
    {
        /* Place your application code here. */
    }
}

/* [] END OF FILE */

初期化部分で、 "en" 出力をアサートしたら、おしまいです。 初期化の前にディレイが入っていますが、これは、 PwmUdb ブロックにクロックを与えて確実に初期化するためです。

PwmUdb ブロックは、非同期な "reset" 入力を持っていますが、慣例に従って、 "0" を接続されているので、使用されていません。 ところが、パワーオンリセット状態では、カウンタの値が "0" になっており、 "reset" を与えた時のリセット状態と異なっています。 そのため、リセット直後、一瞬だけ PWM 出力がアサートされてしまう現象が発生しました。

このブロックでは、 "!en" が同期リセットと同様の効果を持っているため、クロックの立ち上がりが入ったであろう時間を待ち合わせて、ブロックのイネーブルをしています。

データパスで書いた PWM

データパスによる実装

今回も、 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/29/2013 at 18:13
// Component: PwmDp
module PwmDp (
	output  pwm0,
	output  pwm1,
	input   clock,
	input   en,
	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;       // A0 == 0 (z0)
    wire        less0;      // A0 < D0 (cL0)
    wire        less1;      // A0 < D1 (cL1)

    // Specify DATAPATH behavior
    assign      addr = (zero|~en)?(CS_LOAD):(CS_DECREMENT);

    // PWM0 output
    reg     pwm0_reg;
    always @(posedge reset or posedge clock) begin
        if (reset) begin
            pwm0_reg <= 1'b0;
        end else if (!en) begin
            pwm0_reg <= 1'b0;
        end else begin
            pwm0_reg <= less0;
        end
    end
    
    // PWM1 output
    reg     pwm1_reg;
    always @(posedge reset or posedge clock) begin
        if (reset) begin
            pwm1_reg <= 1'b0;
        end else if (!en) begin
            pwm1_reg <= 1'b0;
        end else begin
            pwm1_reg <= less1;
        end
    end
    
    // Signal assignment
    assign      pwm0  = pwm0_reg;
    assign      pwm1  = pwm1_reg;

cy_psoc3_dp8 #(.cy_dpconfig_a(
{
    `CS_ALU_OP_PASS, `CS_SRCA_A1, `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, /*CFGRAM0: LOAD A1 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_A0_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),
        /*  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(less0),
        /*  output                  */  .z0(zero),
        /*  output                  */  .ff0(),
        /*  output                  */  .ce1(),
        /*  output                  */  .cl1(less1),
        /*  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

カウンタ部分の実装に、データパスを使っています。 "zero" でデクリメントの終端を検知するのは、これまでと同様ですが、今回は、ディジタル比較器の出力 "less0" と "less1" を取り出して、 PWM 出力の制御に利用しています。

データパスの設定

データパスは、これまでと同様に "LOAD" と "DECREMENT" の設定だけを定義しています。 ただし、レジスタの使い方が、これまでと異なっています。

これまでは、周期パラメータを "D0" に格納しておき、 "LOAD" 設定でカウンタ "A0" に値を直接入れていました。 ところが、今回は、2つの PWM 出力を得るために、カウンタ "A0" と "D0" および "D1" の値を比較する必要があります。 そのため、 "D0" を周期パラメータを格納するために使用することができません。 そこで、 "LOAD" 状態では、 "A1" に格納された周期パラメータを "ALU" を通して "A0" に入れています。


比較器の設定

データパスに内蔵されているディジタル比較器のうち、一つは、 "A0" と "D0" を比較する機能を持っています。 もう一方は、比較対象を選ぶことができます。 今回は、 "A0" と "D1" を比較したいので、データパスの設定で "CMP SEL" を "CFGA" に設定して、比較器の設定で "CMP SELA" を "A0_D1" としています。 これで、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(Pwm_dp_u0__A0_REG, 99);
    CY_SET_REG8(Pwm_dp_u0__A1_REG, 99);
    CY_SET_REG8(Pwm_dp_u0__D0_REG, 30);
    CY_SET_REG8(Pwm_dp_u0__D1_REG, 60);
    CR1_Write(1);

    /* CyGlobalIntEnable; */ /* Uncomment this line to enable global interrupts. */
    for(;;)
    {
        /* Place your application code here. */
    }
}

/* [] END OF FILE */

これまでと同様に、 API を作成せず、直接データパス内のレジスタを初期化してから、 "en" をアサートしています。 "A1" に格納されている周期と同時に、カウンタ "A0" の値も初期化されています。 これは、 Verilog で記述した時と同様に、初期状態のレジスタの値が "0" にリセットされてしまうため、一瞬だけパルスが発生してしまう事態を防ぐためです。

消費資源の比較

今回も 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                          :    5 :   67 :   72 :   6.94%
UDB Macrocells                :   11 :  181 :  192 :   5.73%
UDB Unique Pterms             :   29 :  355 :  384 :   7.55%
UDB Total Pterms              :   30 :      :      : 
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                          :    5 :   67 :   72 :   6.94%
UDB Macrocells                :    3 :  189 :  192 :   1.56%
UDB Unique Pterms             :    3 :  381 :  384 :   0.78%
UDB Total Pterms              :    3 :      :      : 
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%

Verilog を使用した場合、ディジタル比較器が合成されているため、約10倍もの積項("Pterm")が使われているのがわかります。 この実装では、定数との比較しか行っていないにも関わらす、これだけの資源を消費していますから、周期とデューティーを可変にしたら、もっと多くの資源が消費される事が予想されます。

プロジェクトアーカイブ

この記事を書くために作成したプロジェクトは、このファイルの拡張子を ZIP に変更して展開すると再現できます。

関連書籍


PSoC 3 で、分周器を作った [PSoC]このエントリーを含むはてなブックマーク#

前回は、データパスを使って、加算器を作りました。 加算器は、延々と足し算をするだけなので、使用するデータパスの設定は一つだけで、変更はしていません。 今回は、 データパスの設定を2つ使って、分周器を作成します。

Verilog で書いた分周器

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 での実装

ここまでは、カスタムコンポーネントを作成して分周器を構成してきました。 おなじ機能は、 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 で、 8-bit 加算器を作った [PSoC]このエントリーを含むはてなブックマーク#

タイトルを見ても、何を言っているのか分からないでしょうが、そういう事です。 作成したのは、 カスタムロジックで作成した二つの8-bit入力と一つの8-bit出力を持つ「コ・プロセッサ」です。

Verilog で書いた加算器

Verilog による実装

まずは、 Verilog で記述しています。 テストに使ったのは、こんな回路図です。 二つの Control Reg の出力をカスタム部品 AdderUDB に入力し、その出力を Status Reg で受け取ることにより、 CPU とのインターフェイスを可能にします。 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/27/2013 at 15:30
// Component: AdderUDB
module AdderUDB (
	output [7:0] s,
	input  [7:0] a,
	input  [7:0] b
);

//`#start body` -- edit after this line, do not edit this line

    assign s = a + b;

//`#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

PSoC Creator の場合、 Symbol Wizard で入出力を指定してシンボルを作成し、 Generate Verilog でひな形を作成し、 assign ステートメントを追加するだけです。

以下のようなテストプログラムを作成しました。 入力のすべての組み合わせについて結果を検証し、すべて正しければ二つの LED が同時に点滅し、一つでも間違っていれば LED が交互に点滅します。

/* ========================================
 *
 * Copyright noritan.org, 2013
 * All Rights Reserved
 * UNPUBLISHED, LICENSED SOFTWARE.
 *
 * CONFIDENTIAL AND PROPRIETARY INFORMATION
 * WHICH IS THE PROPERTY OF NORITAN.ORG.
 *
 * ========================================
*/
#include <device.h>

uint16      rega;
uint16      regb;
uint8       sum;
CYBIT       good = 1;

void main()
{
    /* Place your initialization/startup code here (e.g. MyInst_Start()) */

    /* CyGlobalIntEnable; */ /* Uncomment this line to enable global interrupts. */
    for (rega = 0; rega < 256; rega++) {
        CR1_Write(rega);
        for (regb = 0; regb < 256; regb++) {
            CR2_Write(regb);
            sum = SR1_Read();
            if (sum != ((uint8)rega + (uint8)regb)) {
                good = 0;
            }
        }
    }
    // Specify LED status as the inspection results.
    if (good) {
        LED3_Write(0);
        LED4_Write(0);
    } else {
        LED3_Write(0);
        LED4_Write(1);
    }
    // Blink LEDs
    for(;;)
    {
        LED3_Write(!LED3_Read());
        LED4_Write(!LED4_Read());
        CyDelay(200);
    }
}

/* [] END OF FILE */

データパスで書いた加算器

データパスによる実装

単純に Verilog で加算器を書くと、多くの Look Up Table (LUT) を使ってしまいます。 これでは、すぐに資源を喰いつくしてしまいます。 そこで、 LUT の代わりにデータパスを使用して加算器を実装します。

//`#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/27/2013 at 15:55
// Component: AdderDp
module AdderDp (
	input   clock,
	input   reset
);

//`#start body` -- edit after this line, do not edit this line

cy_psoc3_dp8 #(.cy_dpconfig_a(
{
    `CS_ALU_OP__ADD, `CS_SRCA_A0, `CS_SRCB_D0,
    `CS_SHFT_OP_PASS, `CS_A0_SRC_NONE, `CS_A1_SRC__ALU,
    `CS_FEEDBACK_DSBL, `CS_CI_SEL_CFGA, `CS_SI_SEL_CFGA,
    `CS_CMP_SEL_CFGA, /*CFGRAM0: Add A0 and D0 into A1*/
    `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, /*CFGRAM1: */
    `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(3'b0),
        /*  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(),
        /*  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

多くの記述があるように見えますが、中身はデータパスモジュール "cy_psoc3_dp8" のインスタンス "dp" だけです。 しかも、この記述は、 Datapath Config Tool で自動的に生成されたものを "reset" と "clk" の二つの入力を追加したものです。

データパスの設定

データパスの設定のうち使用しているのは、 CFGRAM の一行目 "Reg0" だけです。 この設定では、 A0D0 の内容を足して A1 に格納する動作をクロックごとに繰り返します。 クロックには、 BUS_CLK を与えているため、 CPU と無関係に高速で加算動作を行います。 ただし、データを出し入れしているのは、 CPU なんですけどね。

以下のようなテストプログラムを作成しました。 変更箇所は、レジスタに対する読み書きだけです。

/* ========================================
 *
 * Copyright noritan.org, 2013
 * All Rights Reserved
 * UNPUBLISHED, LICENSED SOFTWARE.
 *
 * CONFIDENTIAL AND PROPRIETARY INFORMATION
 * WHICH IS THE PROPERTY OF NORITAN.ORG.
 *
 * ========================================
*/
#include <device.h>

uint16      rega;
uint16      regb;
uint8       sum;
CYBIT       good = 1;

void main()
{
    /* Place your initialization/startup code here (e.g. MyInst_Start()) */

    /* CyGlobalIntEnable; */ /* Uncomment this line to enable global interrupts. */
    for (rega = 0; rega < 256; rega++) {
        CY_SET_REG8(Adder_dp_u0__A0_REG, rega);
        for (regb = 0; regb < 256; regb++) {
            CY_SET_REG8(Adder_dp_u0__D0_REG, regb);
            sum = CY_GET_REG8(Adder_dp_u0__A1_REG);
            if (sum != ((uint8)rega + (uint8)regb)) {
                good = 0;
            }
        }
    }
    // Specify LED status as the inspection results.
    if (good) {
        LED3_Write(0);
        LED4_Write(0);
    } else {
        LED3_Write(0);
        LED4_Write(1);
    }
    // Blink LEDs
    for(;;)
    {
        LED3_Write(!LED3_Read());
        LED4_Write(!LED4_Read());
        CyDelay(200);
    }
}

/* [] END OF FILE */

カスタム部品を作るときには、専用の API を作成して、配置配線後の違いを吸収するのが定石です。 今回は、横着をして、 API を作成せずにレジスタのアドレスを直接与えて、アクセスをしています。 このレジスタアドレスは、 "cyfitter.h" というファイルの中で宣言されています。

消費資源の比較

加算器をデータパスで実現した動機は、消費する資源を減らすためです。 上で作成したシステムのそれぞれの消費資源を比較してみました。

Verilog での実装
Resource Type                 : Used : Free :  Max :  % Used
============================================================
Digital domain clock dividers :    0 :    8 :    8 :   0.00%
Analog domain clock dividers  :    0 :    4 :    4 :   0.00%
Pins                          :    5 :   67 :   72 :   6.94%
UDB Macrocells                :    8 :  184 :  192 :   4.17%
UDB Unique Pterms             :   16 :  368 :  384 :   4.17%
UDB Total Pterms              :   16 :      :      : 
UDB Datapath Cells            :    0 :   24 :   24 :   0.00%
UDB Status Cells              :    1 :   23 :   24 :   4.17%
             Status Registers :    1 
UDB Control Cells             :    2 :   22 :   24 :   8.33%
            Control Registers :    2 
DMA Channels                  :    0 :   24 :   24 :   0.00%
Interrupts                    :    0 :   32 :   32 :   0.00%
データパスでの実装
Resource Type                 : Used : Free :  Max :  % Used
============================================================
Digital domain clock dividers :    0 :    8 :    8 :   0.00%
Analog domain clock dividers  :    0 :    4 :    4 :   0.00%
Pins                          :    5 :   67 :   72 :   6.94%
UDB Macrocells                :    0 :  192 :  192 :   0.00%
UDB Unique Pterms             :    0 :  384 :  384 :   0.00%
UDB Datapath Cells            :    1 :   23 :   24 :   4.17%
UDB Status Cells              :    0 :   24 :   24 :   0.00%
UDB Control Cells             :    0 :   24 :   24 :   0.00%
DMA Channels                  :    0 :   24 :   24 :   0.00%
Interrupts                    :    0 :   32 :   32 :   0.00%

このように、単純な Verilog での実装の場合はマクロセルを8個消費していましたが、データパスの実装の場合はデータパスがセル一個だけで済んでいます。 データパスは、多機能な ALU の機能とそれに付随するレジスタから構成されていますので、資源の消費を少なくできます。 とはいえ、データパスを使って、これだけの事しかできないのではさみしいので、次回は、もっと違う事をしてみましょう。

プロジェクトアーカイブ

この記事を書くために作成したプロジェクトは、このファイルの拡張子を ZIP に変更して展開すると再現できます。

関連書籍


サイプレス PSoC(R) オープンワークショップ 2013年第二四半期 [PSoC]このエントリーを含むはてなブックマーク#

2013年の第二四半期も、「サイプレス PSoC® オープンワークショップ」が予定されています。

ワークショップ会場と日程

Level 1 in 大阪
期日 : 2013年4月17日(水) 13:30~17:00
会場 : ネット・カンファレンス大阪 会議室I
場所 : 〒532-0003 大阪市淀川区宮原3-4-30 ニッセイ新大阪ビル18階
Level 1 in 東京
期日 : 2013年4月23日(火) 13:30~17:00
会場 : 日経ホール&カンファレンスルーム 日経・大手町セミナールーム2
場所 : 〒100-8066 東京都千代田区大手町1-3-7日経ビル6階
Level 2 in 横浜
期日 : 2013年5月21日(火) 13:30~17:00
会場 : 株式会社マクニカ ブリリアント テクノロジー カンパニー セミナールーム
場所 : 〒222-8561 横浜市港北区新横浜1-6-3 マクニカ第1ビル
Level 2 in 大阪
期日 : 2013年5月28日(火) 13:30~17:00
会場 : 株式会社マクニカ 西日本支社 セミナールーム
場所 : 〒532-0003 大阪市淀川区宮原3-4-30 ニッセイ新大阪ビル 17階
Level 3 in 新宿
期日 : 2013年7月2日(火) 13:30~17:00
会場 : 東京エレクトロン デバイス株式会社 新宿オフィス セミナールーム
場所 : 〒163-1034 東京都新宿区西新宿3-7-1 新宿パークタワー S34階
Level 3 in 大阪
期日 : 2013年7月4日(木) 13:30~17:00
会場 : 東京エレクトロン デバイス株式会社 大阪支社/大阪オフィス セミナールーム
場所 : 〒540-6033 大阪府大阪市中央区城見1-2-27 クリスタルタワー(33F)

確定情報は、公式ページをご覧ください。

参考サイト

サイプレス PSoC® オープンワークショップ

2013年3月22日更新

  • Level 1 の場所と時間を確定させました。
  • Level 2 の時間を変更しました。

2013年3月27日更新

  • Level 3 の場所と時間を変更しました。

2013年3月29日更新

  • 全日程が、確定しました。

2013年4月5日更新

  • 「Level 3 in 横浜」が、「Level 3 in 新宿」に変更されました。

参考文献


PSoC 3 で、 "H" な出力 [PSoC]このエントリーを含むはてなブックマーク#

CY8CKIT-030

PSoC 3 を使って、 "H" な出力をします。 使用するハードウェアは、 CY8CKIT-030 PSoC® 3 Development Kit です。 さて、どんな "H" が出てくるんでしょうか。

"H" は、 Look Up Table で作る

今回作成する "H" は、 プロトコルに UART を使います。 「だうも、すびばせん。」 単に UART で "H" を出力させるだけです。

ただし、単純に "H" を出力するだけでは、面白くありません。 そこで、この企画では、

  • UART コンポーネントは、使いません。
  • ファームウェア・ソースコードは、まったく記述しません。
  • Look Up Table (LUT) と呼ばれるロジックを使います。
つまり、 LUT だけで、 "H" な出力をします。

"H" 生成装置のスケマティック

H生成装置の回路図

"H" 生成装置は、 LUT を中心に入出力ポートとクロックで構成されています。 プルアップ付き設定の入力ポート (P6[2]) は、 CY8CKIT-030 の SW2 に接続されています。 また、出力ポート (P12[6]) は、ジャンパ線で UART レベル変換チップの TX に接続されています。 このプロジェクトは、 SW2 を一回押すと、 "H" をひとつ出力するという動作をします。

UART のプロトコルには、 9600bps, 8bit, non-parity, 1stop-bit になっています。 ボーレートは、クロックの周波数で設定することができます。 9600Hz のクロックを要求しましたが、実際のクロックは、 3MHz の IIMO を 313 分周した 9585Hz に設定されました。

LUT は、レジスタ付きの設定になっていて、 out(3:0) の部分が 4-bit の状態コードを表すステートマシンになっています。 また、 out4 は、完全に out(3:0) の値に依存している、いわゆる、ムーア型ステートマシンとして構成されています。

ステートマシンの中身

状態遷移図

4-bit の状態コードで表現される16の状態のうち、使用されているのは12状態で、残りは未使用状態として無条件に初期状態に遷移するようになっています。 それぞれの状態は、 UART 出力の各ビットに相当し、その値は状態遷移図に記述されています。 "H" (16進数で $48) という固定された出力を得るため、ステートマシンは、 SW2 の状態を検出する以外は、単純に無条件遷移を繰り返しています。


状態遷移表は、以下のようになっています。

Input Hex Valuein4in3in2in1in0out4out3out2out1out0Output Hex Value
0x0000000100000x10
0x0100001000100x02
0x0200010000110x03
0x0300011001000x04
0x0400100001010x05
0x0500101101100x16
0x0600110001110x07
0x0700111010000x08
0x0801000110010x19
0x0901001010100x0A
0x0A01010110110x1B
0x0B01011100000x10
0x0C01100100000x10
0x0D01101100000x10
0x0E01110100000x10
0x0F01111100000x10
0x1010000100010x11
0x1110001100010x11
0x1210010000110x03
0x1310011001000x04
0x1410100001010x05
0x1510101101100x16
0x1610110001110x07
0x1710111010000x08
0x1811000110010x19
0x1911001010100x0A
0x1A11010110110x1B
0x1B11011100000x10
0x1C11100100000x10
0x1D11101100000x10
0x1E11110100000x10
0x1F11111100000x10

プロジェクトの実行

以上で、このプロジェクトは完了です。 予告したように、ソース・コードは記述していません。 あとは、コンパイルして書き込んだら、 PSoC 3 は、ステートマシンに従って動作します。

プロジェクト・アーカイブ

このプロジェクトのアーカイブは、このファイルの拡張子を "zip" に変更すると、得られます。 余裕があったら、 github にでも置きたいのですが、いつになるやら。

関連文献


PSoC 3 で多重割り込みを試す [PSoC]このエントリーを含むはてなブックマーク#

PSoC 3 は、 8051 という CPU を搭載しています。 他の CPU と同じように 8051 も割り込みという仕掛けをもっていて、 PSoC 3 の場合には、複数の割り込み要求に対して 8 レベルの優先順位を割り当てることができます。 この記事では、優先順位を設定することによって、多重割り込みの実験を行います。

実験プロジェクト

多重割り込み実験回路

今回の実験では、このような回路を作成しました。 回路は、二つの周期割り込みと状態表示用の LED 出力と LCD 出力から構成されており、 CY8CKIT-030 でそのまま試すことができます。

周期割り込み ISR_slow は、 1Hz 周期のクロック Clock_slow の立ち上がりで駆動されます。 また、周期割り込み ISR_fast は、 10Hz 周期のクロック Clock_fast の立ち上がりで駆動されます。

クロックの設定

クロックの設定

これらのクロックは、バスクロックに比べるとはるかに遅い周波数のクロックです。 デフォルトの設定にすると、 Internal Low-speed Oscillator (ILO) と呼ばれる 1kHz のクロックを使用することになりますが、 ILO クロックは精度が著しく低くなっています。 そこで、 Design-wide クロックと呼ばれる 60kHz のクロックを作成して、このクロックを分周して 1Hz と 10Hz のクロックとしています。 こうすると、比較的高精度な Internal Main Oscillator (IMO) をクロック源とするクロックを得ることができます。 クロックは、 CYDWR ファイルの Clock タグから設定することができます。

ソースコード

ソースコードは、二つの Interrupt Service Routine (ISR) と main 関数で構成されています。

/* ========================================
 *
 * Copyright noritan.org, 2012
 * All Rights Reserved
 * UNPUBLISHED, LICENSED SOFTWARE.
 *
 * CONFIDENTIAL AND PROPRIETARY INFORMATION
 * WHICH IS THE PROPERTY OF NORITAN.ORG.
 *
 * ========================================
*/
#include <device.h>

uint16      n_ISR_slow = 0;
uint16      n_ISR_fast = 0;

CY_ISR(ISR_slow_func) {
    n_ISR_slow++;
    LED4_Write(1);
    CyDelay(500);   // Stay for 500msec
    LED4_Write(0);
}

CY_ISR(ISR_fast_func) {
    n_ISR_fast++;
    LED3_Write(1);
    CyDelay(10);    // Stay for 10msec
    LED3_Write(0);
}

void main()
{
    /* Place your initialization/startup code here (e.g. MyInst_Start()) */
    ISR_slow_StartEx(ISR_slow_func);
    ISR_fast_StartEx(ISR_fast_func);
    LCD_Start();

    CyGlobalIntEnable; /* Uncomment this line to enable global interrupts. */
    for(;;)
    {
        /* Place your application code here. */
        LCD_Position(0, 0);
        LCD_PrintDecUint16(n_ISR_slow);
        LCD_PrintString("  ");
        LCD_Position(1, 0);
        LCD_PrintDecUint16(n_ISR_fast);
        LCD_PrintString("  ");
    }
}

/* [] END OF FILE */

割り込み処理ルーチン ISR_slow_func と ISR_fast_func は、割り込みが受付られた時に呼び出されます。 割り込みが実行されると、変数 n_ISR_slow または n_ISR_fast がインクリメントされ、割り込みが受け付けられた回数を示します。 ISR では、特別な処理を行っていませんが、処理を行うかわりに CyDelay 関数を呼び出して、時間稼ぎをおこなっています。 ISR_slow_func の場合、 CyDelay の処理時間は 500ミリ秒に設定されていますので、 CPU の実行時間の 50% を消費するということになります。 同じように、 ISR_fast_func は 10ミリ秒に設定されていますので、 10% の CPU 時間を消費します。 さらに、これらの ISR では、処理中に LED3 または LED4 を点灯させる処理を行っています。 LED の点灯状態をみると、割り込み処理の状態を知ることができます。

main 関数では、初期設定を行った後は、延々と LCD に変数 n_ISR_slow と n_ISR_fast の値を表示しています。 main 関数に許された CPU 時間は全体の 40% ですが、 LCD に数値を表示するには、十分な時間です。

デフォルトの優先順位を使った場合

デフォルト設定でのシーケンス図

デフォルトの状態では、二つの割り込みの優先順位はどちらも "7" に設定されています。 これは、片方の割り込みが処理されているときには、もう片方の割り込みは受け付けられないということを意味します。

この状態での処理をシーケンス図に描くとこのようになります。 ISR_slow が処理されている 500ミリ秒の間、 ISR_fast は受け付けられません。 そのため、一秒間に10回、 ISR_fast 割り込みの要求があるにもかかわらず、実際に受け付けられるのは、5回だけです。

LCD の表示を見ると、下段の n_ISR_fast が上段の n_ISR_slow の5倍の値を示します。 これは、一秒間に5回分の割り込みが受け付けられなかったことを示しています。

LED の点灯状態を見ると、 LED4 が消灯している時にかぎり、 LED3 が点灯(点滅)することがわかります。 また、 LCD の表示は main 関数本体で行っているため、 LED4 が消灯している時に更新されています。

以上のように、 ISR_slow の処理時間が長くなると、 ISR_fast が受け付けられず、割り込み要求をとりこぼしてしまうことがわかりました。

ISR_fast の優先順位を上げた場合

優先順位を設定する

このままでは、 ISR_fast が想定通りに実行されないので、 ISR_slow が処理されている間でも ISR_fast 割り込みが受け付けられるようにします。 そのためには、 ISR_fast の優先順位を ISR_slow よりも高くしてやります。

この優先順位の割り当ては、もちろん PSoC Creator の GUI から行います。 CYDWR ファイルの Interrupt タグを開くと、優先順位を設定するための画面が現れます。 デフォルトの状態では、優先順位は "7" になっています。 数字が小さいほうが優先順位が高くなりますので、ここでは ISR_fast の優先順位を一つ上げて "6" に設定します。


優先順位を変更した場合のシーケンス図

すると、 ISR_slow 割り込みの実行中でも、さらに ISR_fast 割り込みの処理が行われるようになります。 ISR_fast の処理は、一秒間に10回受け付けられます。

LCD の表示を見ると、下段の n_ISR_fast が上段の n_ISR_slow の10倍の値を示します。 これは、 ISR_fast 割り込みが 10Hz の周期で受け付けられたことを示しています。 また、 LED4 が点灯している間でも LED3 が点滅する様子がわかります。 ISR_slow 割り込みの処理中でも ISR_fast 割り込みが受け付けられているからです。

本日の結論

以上のように、割り込みの優先順位を変更することで、割り込み処理中でもほかの割り込みが受け付けられる様子が観測できました。

しかしながら、本来は、割り込み処理に今回の例のように重い処理を書くことは推奨されません。 それは、 ISR_slow が受けつけられている間、 LCD の表示が更新されなかったことからわかるように、割り込みが受け付けられると、メインループの処理が完全に止まってしまうからです。

こういった問題点を解決するための方法については、また、いずれ。

プロジェクト・アーカイブ

関連文献


続・PSoC 3 で RS フリップフロップを作ろう [PSoC]このエントリーを含むはてなブックマーク#

RS フリップフロップ

コメント欄で質問をもらいましたので、今回は、その解答編です。

質問: Verilog 記述に assign を使ったらどうなりますか。

前回の記事では、ゲートレベルは回路図で、 RTL レベルは Verilog で記述しました。 Verilog にも、ゲートレベルで記述する機能はあります。 この時に使用される構文が、 assign を使う continuous assignment と呼ばれる代入文です。 この代入文は、 wire に対して値を割り当てますので、事実上組み合わせ論理として合成されることになります。 具体的には、以下のような記述になります。

//`#start header` -- edit after this line, do not edit this line
// ========================================
//
// Copyright noritan.org, 2012
// 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
module RSFF (
	q,
	qb,
	rb,
	sb
);

//`#start body` -- edit after this line, do not edit this line

    output  q;
    output  qb;
    input   rb;
    input   sb;
    
    assign  qb = ~(q  & rb);
    assign  q  = ~(qb & sb);

//`#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
WS000258.png

この記述を使って論理合成を行うとこのような構成の Look Up Table (LUT) が生成されました。 これは、前回、ゲートを使って回路図を書いた場合と同じ結果です。 論理合成では、回路図でも、 Verilog でも、内部で論理式を生成して、その論理式と等価な回路を合成していきます。 そのため、論理的に等価な内容であれば、回路図でも Verilog 記述でも同じ結果が得られます。

前回と今回の記事に作成したアーカイブは、このリンクにあります。

関連文献


PSoC 3 で RS フリップフロップを作ろう [PSoC]このエントリーを含むはてなブックマーク#

RS フリップフロップ

ひょんなことから、 Verilog HDL で RS フリップフロップを記述してみました。

記事を書くのは、久しぶりだね。 ちゃんと、書けるかな。

Verilog で RS フリップフロップを書きたい

Twitter で、「 Verilog で書いた RS フリップフロップが、うまく合成できない。」という声があがりました。 非同期順序回路を論理合成するのは、苦労ばかり多いのですが、試しに挑戦してみます。 使用する論理合成ツールは、大人の事情により、 PSoC Creator です。

ゲートで書いてみた

ゲートで書いた RS-FF

お題は、 Verilog の HDL で記述することだったのですが、まずは、 RS フリップフロップをゲートで書いて、どんな回路が合成されるのかを確認してみました。 ハードウェアには、 CY8CKIT-030 を使用しています。 二つのタクトスイッチを入力に、二つの LED を出力にしています。

合成中に以下のような警告が表示されました。


Warning-1361: The design contins a combinational loop. Check the design for unintentional latches. Breaking the loop at QB/main_2 --> QB/q

The static timing analyzer reported a warning. See the warning message for details. Additional information may be available in the timing report file.

これは、組み合わせ回路にループが存在するから、意図しないラッチが合成されるかも知れない、という警告です。 今回は、 RS-FF というラッチを合成するために書いたのですから、もちろん「意図しない」というわけではありません。 論理合成ツールは、ループが存在するままでは、タイミング制約を解決することができません。 そのため、勝手にループを切って処理してくれたようです。

合成後のゲート記述

合成された回路は、 Look Up Table (LUT) で実装され、その論理式は RPT ファイルにレポートされます。 この論理式を Programmable Logic Array (PLA) 形式に書き起こすと、このような回路になっています。 もとの回路からみると、かなり無駄に見えますが、論理積項数 (product term: pterm) は、 2 で収まっているので、良い合成結果と言えるでしょう。

HDL で書いてみた

Verilog で書いた RS-FF

では、本題に戻って、 Verilog HDL で RS フリップフロップを記述してみます。 最上位の回路図は、こんな風になっています。 箱の中身は、 Verilog で記述されています。



//`#start header` -- edit after this line, do not edit this line
// ========================================
//
// Copyright noritan.org, 2012
// 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
module RSFF (
	q,
	qb,
	rb,
	sb
);

//`#start body` -- edit after this line, do not edit this line

    output  q;
    output  qb;
    input   rb;
    input   sb;
    reg     q;
    reg     qb;
    
    always @(rb or sb or q or qb) begin
        casez ({rb,sb})
            2'b01:      {q,qb} = 2'b01;     // Reset
            2'b10:      {q,qb} = 2'b10;     // Set
            2'b11:      {q,qb} = {q,qb};    // Hold
            2'b00:      {q,qb} = 2'b11;     // Prohibited
        endcase
    end

//`#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

端子名を大文字で書くと、定数と認識されてしまったので、小文字で書きました。 動作記述のキーになっている部分は、 casez 構文です。 入力 rbsb の値によって、 RS-FF の動作が変わります。

動作のうち、 Reset と Set は、わかりやすいので説明は不要でしょう。 Hold の場合には、出力 qqb の値をフィードバックします。 このため、 always のリストには、 q および qb も追加してあります。 Prohibited は、通常の RS-FF では使用不可の組み合わせです。 この回路では、 NAND を使った場合の動作に合わせて、 q および qb の双方が 1 になるとしました。

論理合成すると、やはり、組み合わせ回路がループしている旨の警告が表示されます。 しかも、今回は2か所の警告が出てきます。 ループが二つできているということですが、二つもループがありましたっけ?

合成後の HDL 記述

論理合成した結果、こうなりました。 ありゃ? なんだか余分な回路が付いていますね。 どうやら、 QQB に対して、個別にラッチを合成してくれたようです。

この回路、ラッチが二つあるために、問題が発生する可能性があります。 それは、二つのラッチが独立しているために、別々の状態をとることがあるということです。 電源投入直後は、電源の立ち上がり方によって、初期状態が異なってきます。 また、 rbsb がほぼ同時に立ち上がる場合には、内部遅延の差によって、二つのラッチの動作が異なってくる可能性があります。

いずれにしても、これでは、 RS フリップフロップとしては使えないと考えられます。

本日の結論

Verilog HDL で記述した RS フリップフロップを PSoC Creator に合成させてみましたが、ラッチが二つ合成されてしまいました。 このままでは、実際の RS フリップフロップと同じ動作にはなりません。 やはり、ゲートで記述するか、別のロジックで実現するか、考えたほうがよさそうです。

関連文献


PSoC 3 Kit Exchange Program やってます [PSoC]このエントリーを含むはてなブックマーク#

WS000158a-384.png

Cypress は、 "PSoC 3 Kit Exchange Program" という活動を行っています。 これは、 PSoC3 ES チップが搭載された評価キットを量産版の PSoC3 が搭載された評価キットと無償で交換するというものです。 古い CY8CKIT-003 PSoC First Touch Kit が、最新の CY8CKIT-030 PSoC3 Development Kitに交換してもらえるそうです。 これは、利用せねば。

新しいキットが選べません

WS000158a.png "PSoC 3 Kit Exchange Program" は、このサイトで受け付けています。 メールアドレス (Email Address) を入れて、手持ちのキットの種類 (Kit You Own) を指定して、11けたの ID (Enter your board number) を入れて、代わりキットの種類 (Replacement kit) を指定して、……
WS000158b.png

おや? "Replacement kit" のリストに何も出てきません。 これでは、受け付けてもらえません。 困ったな。

日本語に対応し損なっていた

何が起こっているのか、調査したところ、 WEB ページが読み込んでいる JavaScript ファイルが日本語に対応していないことがわかりました。 どこから見ても、中国語に見えます。

WS000161.png

日本語設定がまずいのだから、と、日本語でない設定にしてみることにしました。 私が使っている FireFox の場合、メニューの Tools→Options→Content→Languages から Choose... ボタンをクリックして、言語設定を変更します。 English [en] をリストの最上位に持ってきて、 OK をクリックします。 これで、英語優先の設定になりました。

リストにキットが並びました

WS000160.png

もう一度、 "PSoC 3 Kit Exchange Program" を開くと、みごとに "Replacement kit" のリストに交換先キットが並びました。 これで、申し込みができます。

無償で交換といっても、送料は請求されます。 さて、先に進んでみますか。

関連文献

いちおう、 PSoC なので。

はじめてのPSoCマイコン―PSoC‐Programmable System‐on‐Chip‐Primer (マイコン活用シリーズ)

はじめてのPSoCマイコン―PSoC‐Programmable System‐on‐Chip‐Primer (マイコン活用シリーズ)

  • 作者: 桑野 雅彦
  • 出版社/メーカー: CQ出版
  • 発売日: 2010/04
  • メディア: 単行本

周期割り込みなんて、簡単ですよ [PSoC]このエントリーを含むはてなブックマーク#

本日の話題は、PSoC3を使って、周期割り込みを実現する方法です。 今回の例を見ていただけると、PSoCって、普通言われるようなマイコンじゃあないんだなと理解していただけると思います。

周期割り込みって、何だ?

唐突に周期割り込みという言葉が出てきました。 周期割り込みは、周期的に発生する割り込みのことです。 たとえば、1m秒ごとに周期的に割り込みを発生させてとある処理ルーチンを実行させます。 すると、割り込みの回数を数えるだけで、ミリ秒単位の時間を知ることができます。 また、割り込み周期が一定なので、処理ルーチンの処理時間に関係なく、各処理の開始間隔を一定にすることできます。

一定時間ごとに処理ができるようになると、A/D変換のサンプリングやD/A変換の出力タイミングも精確になるので、より性能が高くなります。

周期割り込みといえば、タイマを使うでしょう

WS000047.png

従来のマイコンで、周期割り込みを実現しようとすると、通常はタイマモジュールを使用します。 今どきのタイマには、周期が設定できて、タイマカウンタが周期レジスタの値に達したらタイマカウンタを0に戻します。 このときに、出力を変化させたり、割り込みを発生させたりすることができます。 PSoC3にも、タイマがあり、「周期レジスタ」で周期的に割り込みを発生することができます。


Timer_1の設定
ParameterValue
Resolution8-bit
Period250
Trigger ModeNone
Capture ModeNone
Enable ModeSoftware Only
Run ModeContinuous
InterruptsOn TC

タイマには、1kHzのILO (Internal Low-frequency oscillator)が接続されています。 このクロック・パルスを250個数えることによって、250m秒ごとにinterrupt出力をアサートしています。 interrupt出力の信号は、isr_1インスタンスに伝わり、割り込み処理ルーチンを起動します。

Pin_1は、ハードウェア接続の無い、出力専用端子として定義されています。

使用したリソースは、ざっとこんなところです。

                Resource Type : Used : Free :  Max :  % Used
============================================================
                   Macrocells :    1 :  191 :  192 :   0.52%
                Unique Pterms :    1 :  383 :  384 :   0.26%
                 Total Pterms :    1 :      :      : 
               Datapath Cells :    1 :   23 :   24 :   4.17%
                 Status Cells :    1 :   23 :   24 :   4.17%
         Control/Count7 Cells :    1 :   23 :   24 :   4.17%
                   Sync Cells :    1 :   91 :   92 :   1.09%
                   Interrupts :    1 :   31 :   32 :   3.13%

main.cの中核は、このようになっています。

CY_ISR(isr_1_isr) {
    (void)Timer_1_ReadStatusRegister(); // to clear flag
    Pin_1_Write(Pin_1_Read()?(0):(1));
}

void main()
{
    Timer_1_Start();
    isr_1_StartEx(isr_1_isr);

    CYGlobalIntEnable;
    for(;;)
    {
        // Nothing to do
    }
}

isr_1インスタンスとTimer_1インスタンスは、回路図上で接続されていますが、それぞれ独立した存在です。 しかしながら、Timer_1interrupt出力が自動的にクリアされないため、割り込み処理ルーチンから割り込みフラグをクリアする関数Timer_1_ReadStatusRegister()を呼び出さざるを得ませんでした。 う~ん、美しくない。

処理ルーチンの中では、LEDが接続されたPin_1の出力を反転させています。 250m秒ごとに反転するので、LEDが2Hzで点滅する様子が観測できます。

無理にinterrupt出力を使うことないじゃん

WS000048.png

上の例は、interrupt出力を使ったために、割り込み処理ルーチンでフラグをクリアしなくてはなりませんでした。 isr_1インスタンスは、エッジをつかまえて割り込みを発生させることができるので、隣にあるtc (Terminal Count) 出力を接続しても、同じ動作をするはずです。 しかも、フラグをクリアする必要がありません。


Timer_1の設定
ParameterValue
Resolution8-bit
Period250
Trigger ModeNone
Capture ModeNone
Enable ModeSoftware Only
Run ModeContinuous
Interrupts-

すでに、interrupt出力は、まったくアサートされなくなりました。

                Resource Type : Used : Free :  Max :  % Used
============================================================
                   Macrocells :    2 :  190 :  192 :   1.04%
                Unique Pterms :    1 :  383 :  384 :   0.26%
                 Total Pterms :    2 :      :      : 
               Datapath Cells :    1 :   23 :   24 :   4.17%
                 Status Cells :    1 :   23 :   24 :   4.17%
         Control/Count7 Cells :    1 :   23 :   24 :   4.17%
                   Sync Cells :    1 :   91 :   92 :   1.09%
                   Interrupts :    1 :   31 :   32 :   3.13%

消費したリソースは、マクロセルがひとつ増えました。 割り込み出力が必要なくなったので、減るかと期待していたんですけどね。

CY_ISR(isr_1_isr) {
    Pin_1_Write(Pin_1_Read()?(0):(1));
}

プログラムは、割り込み処理ルーチンからフラグのクリアが消えました。 このプログラムも、2Hzで出力を反転させます。

周期的な信号とそれを受ける割り込み機能があれば十分

WS000049.png

これまで、周期割り込みにタイマを使ってきましたが、タイマの機能を十分に使っているとは言えません。 単なる分周器でも十分です。 いっそのこと、クロックを直接つないでしまったら、どうでしょう。 というわけで、4Hzのクロックをつないでみました。


                Resource Type : Used : Free :  Max :  % Used
============================================================
Digital domain clock dividers :    1 :    7 :    8 :  12.50%
                   Interrupts :    1 :   31 :   32 :   3.13%

必要なリソースも、4Hzのクロックを生成するための分周器と割り込みモジュールだけです。

CY_ISR(isr_1_isr) {
    Pin_1_Write(Pin_1_Read()?(0):(1));
}

void main()
{
    isr_1_StartEx(isr_1_isr);

    CYGlobalIntEnable;
    for(;;)
    {
        // Nothing to do
    }
}

プログラムも、さらに簡単になりました。 Timer_1は、Start()を呼び出して起動する必要がありますが、Clock_1は、最初から起動しているからです。

このプログラムでも、2HzでLEDが点滅します。

余談1:割り込みコストって、高すぎる

で、よせばいいのに、コンパイル結果を見てしまいました。

             ; FUNCTION isr_1_isr (BEGIN)
0000 C0E0              PUSH    ACC
0002 C0F0              PUSH    B
0004 C083              PUSH    DPH
0006 C082              PUSH    DPL
0008 C085              PUSH    DPH1
000A C084              PUSH    DPL1
000C C086              PUSH    DPS
000E 758600            MOV     DPS,#00H
0011 C000        E     PUSH    ?C?XPAGE1SFR
0013 750000      E     MOV     ?C?XPAGE1SFR,#?C?XPAGE1RST
0016 C0D0              PUSH    PSW
0018 75D000            MOV     PSW,#00H
001B C000              PUSH    AR0
001D C001              PUSH    AR1
001F C002              PUSH    AR2
0021 C003              PUSH    AR3
0023 C004              PUSH    AR4
0025 C005              PUSH    AR5
0027 C006              PUSH    AR6
0029 C007              PUSH    AR7
                                           ; SOURCE LINE # 14
                                           ; SOURCE LINE # 15
002B 120000      E     LCALL   Pin_1_Read

なんですか?これは。 割り込み処理ルーチンの入口に、これでもかっていうぐらいにレジスタをスタックに押し込む命令が並んでいます。 もちろん、出口にも同じだけスタックからレジスタの値を復帰させる命令が並んでいます。

これは、レジスタの本数が増えたからにほかならないのですが、理由はもう一つあります。 それは、割り込み処理ルーチンの中で関数を呼び出したことです。 このコンパイラは、割り込み処理ルーチンの中で関数を呼び出した場合には、無条件で全レジスタを退避させるコードを生成するらしく、関数の処理内容にかかわらず、このように大げさなスタック操作が行われるようになります。

余談2:オーバヘッドを減らすためにポーリングを使う?

これに対する解決策が、サイプレス社のアプリケーションノートAN60630 - Optimizing 8051 Code in PSoC® 3に書かれています。 解決方法は、割り込み処理ルーチンの中では、フラグをセットするだけにして、実際の作業は、メインルーチンの中で行わせることだそうです。 えっと、つまり、それは、ポーリングってことですか?

uint8           isr_1_flag = 0;

CY_ISR(isr_1_isr) {
    isr_1_flag = 1;
}

void main()
{
    isr_1_StartEx(isr_1_isr);

    CYGlobalIntEnable;
    for(;;)
    {
        if (isr_1_flag) {
            isr_1_flag = 0;
            Pin_1_Write(Pin_1_Read()?(0):(1));
        }
    }
}

コンパイル後のコードは、こうなりました。

             ; FUNCTION isr_1_isr (BEGIN)
0000 C0E0              PUSH    ACC
0002 C083              PUSH    DPH
0004 C082              PUSH    DPL
0006 C085              PUSH    DPH1
0008 C084              PUSH    DPL1
000A C086              PUSH    DPS
000C 758600            MOV     DPS,#00H
000F C000        E     PUSH    ?C?XPAGE1SFR
0011 750000      E     MOV     ?C?XPAGE1SFR,#?C?XPAGE1RST
                                           ; SOURCE LINE # 16
                                           ; SOURCE LINE # 17
0014 900000      R     MOV     DPTR,#isr_1_flag
0017 7401              MOV     A,#01H

確かにスタッキングの数は減りましたが、まだまだ多いようです。

余談3:フラグに最適な変数クラスがある

アプリケーションノートAN60630 - Optimizing 8051 Code in PSoC® 3には、続きがあります。 フラグには、bitクラスを使え。

PSoC3に使われているCPU 8051 は、ある限られたメモリ空間で、低コストにビット操作を行うことができます。 そのときに使用される変数クラスが、bitです。

bit             isr_1_flag = 0;

変更したのは、ここだけです。 コンパイルしてみると、こんなコードが生成されていました。

             ; FUNCTION isr_1_isr (BEGIN)
                                           ; SOURCE LINE # 16
                                           ; SOURCE LINE # 17
0000 D200        R     SETB    isr_1_flag
                                           ; SOURCE LINE # 18
0002 32                RETI    
             ; FUNCTION isr_1_isr (END)

今までの嵐のようなスタッキングは、何だったんだろうという簡単さです。 スタッキングを一切必要としていません。 フラグをセットしたら、割り込み処理ルーチンから戻ります。 ただ、それだけです。

これで、割り込み発生からの遅延が少なくなりますが、ポーリングを使っている時点で、遅延が発生するようにも思います。 はてさて、どっちがお得なんでしょうね。


PSoC® 3/5実習形式のワークショップ開催決定! [PSoC]このエントリーを含むはてなブックマーク#

ワークショップ会場と日程

大 阪: 2011年2月3日(木) 10:00~17:00
ブリーゼプラザ 7F 小ホール
(〒550-0004 大阪府大阪市北区梅田2丁目4番9号)
http://www.breeze-plaza.com/
東 京: 2011年2月9日(水) 10:00~17:00
富士ソフト アキバホール
(〒101-0022 東京都千代田区神田練塀町3)
http://www.fsi.co.jp/akibaplaza/cont/hall/index.html

参考サイト

http://www.cypress-japan.co.jp/megaworkshop2/


CY8CKIT-001 PSoC DEVELOPMENT KIT [PSoC]このエントリーを含むはてなブックマーク#

100929_213720.jpg

新しいオモチャ、入手しました。


nice!(1)  コメント(1)  トラックバック(0) 
共通テーマ:moblog このエントリーを含むはてなブックマーク#

初めてのPSoCマイコン - CY8C29466-24PXI [PSoC]このエントリーを含むはてなブックマーク#

3234566

色々とありましたが、手を尽くして初めてのPSoCマイコンを入手しました。 書き換え回数5万回もあれば、存分に遊べることでしょう。

サンプルリクエストが出せない件は、まだ、片付いておりません。


2830971

同じ28Pパッケージなら、600milよりも300milの方が場所とらないもんね。

参考になりそうな文献

まずは、ここから始めます。

トランジスタ技術 (Transistor Gijutsu) 2009年 01月号 [雑誌]

トランジスタ技術 (Transistor Gijutsu) 2009年 01月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/12/10
  • メディア: 雑誌
これならわかる!PSoCマイコン活用術

これならわかる!PSoCマイコン活用術

  • 作者: 小林 一行
  • 出版社/メーカー: オーム社
  • 発売日: 2009/07
  • メディア: 単行本

コメント関連文献

定本 OPアンプ回路の設計―再現性を重視した設計の基礎から応用まで

定本 OPアンプ回路の設計―再現性を重視した設計の基礎から応用まで

  • 作者: 岡村 廸夫
  • 出版社/メーカー: CQ出版
  • 発売日: 1990/09
  • メディア: 単行本

私が持っているのは、ハードカバーです。

OPアンプ回路の設計―再現性の重視と統計的手法による回路設計 (1973年)

OPアンプ回路の設計―再現性の重視と統計的手法による回路設計 (1973年)

  • 作者: 岡村 迪夫
  • 出版社/メーカー: CQ出版
  • 発売日: 1973
  • メディア: -

cypress.com から注文が出せない [PSoC]このエントリーを含むはてなブックマーク#

PSoCのサンプル・チップを入手しようと、cypress.comにユーザ登録しました。 サンプル・チップを選んで、手数料の$9.95を支払うためのクレジットカード番号を入れて、さあ、発注、と "Place Your Order" をクリックしたところ、エラー画面が出てきました。

Element USERID is undefined in ARGUMENTS. 

見たところ、 Java の StackTrace が表示されていて、先頭は coldfusion.runtime.UndefinedElementException: Element USERID is undefined in ARGUMENTS. at となっています。 おそらく、 coldfusion (略してCF) というフレームワークを使用した受注システムのようですが、うまく動いていないようです。 HTMLを読んだところ、"UserID"というフィールドが確かに存在しています。

ところが、某所では、うまく発注ができたとの情報ももらいました。 先方の環境も、 Windows XP と Firefox 3.5.3 の組み合わせという点で、私の環境と相違ありません。 ファイアー・ウォールや Spybot が原因かと思い、一時的にディセーブルしてみましたが、効果ありませんでした。

cypress.com に問い合わせても、返答がありません。 google さんに聞いても同様の事例が見つけられません。 一週間要しても、どうにも解決できないので、このBLOGで騒いでみることにしました。 同様の事例をご存知の方、同様の事例でお困りの方、ぜひ、ご一報ください。

参考になりそうな文献

開発キットを購入すれば、再現性は抜群ですが、なにぶん、発注ができないもので。

これならわかる!PSoCマイコン活用術

これならわかる!PSoCマイコン活用術

  • 作者: 小林 一行
  • 出版社/メーカー: オーム社
  • 発売日: 2009/07
  • メディア: 単行本

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。