RXマイコンのソフト開発(2)スイッチのチャタリング対策

RXマイコン(RX631)のソフト開発の2回目です。今回は、プッシュスイッチの入力を受けて、フルカラーLEDの点灯色を変更したいと思います。回路ではチャタリング除去をしていないため、ソフト側でチャタリング対策をしたいと思います。前回と同様にRX631のユーザーズマニュアル ハードウェア編を参照することをおススメします。

図1:基板上のプッシュスイッチ
図1:基板上のプッシュスイッチ

環境

  • パソコン: Windows10 64 bit
    • 統合開発環境: e2studio 2021-01をインストール
    • コンパイラ: Renesas CCRX v3.03.00をインストール
    • 書き込みソフト: Renesas Flash Programmer V3.08.01(無償版) をインストール
  • ターゲットデバイス: R5F5631PDDFL(RX631 48ピン)

スイッチの回路

図2:スイッチ部分の回路図
図2:スイッチ部分の回路図

作成したスイッチ部分の回路図は図2です。SWのポートはP35です。回路にはチャタリング除去するためのローパスフィルタがないので、24msごとにタイマー割り込みをしてスイッチの状態を確認したいと思います。スイッチオフからスイッチオンになったときにフルカラーLEDの点灯色を変えます。

プログラムのフロー

プログラムの処理の流れは図3です。レジスタプロテクトの設定は省略しています。

図3:プログラムのフロー
図3:プログラムのフロー

前回の記事でおこなったクロックの初期化とI/Oポートの設定を行い、今回用いるコンペアマッチタイマー0(CMT0)の初期化を行っています。その後、メインルーチンに入り、スイッチの押し込みを検知したらLEDの点灯色を変更します。スイッチの押し込みはCMT0を用いて24ms周期で検知します。

補足チャタリングの期間は長くても 10 数msといわれているので、スイッチ検知の周期は余裕をもって24msとしました。

プログラム

スイッチに関連するプログラム部分だけ示します。全てのソースコードGitHubの「2_sw」フォルダの中にソースコードがありますので、詳細を知りたい場合はご覧ください。

メイン関数

main.c:メイン関数のソースコード

#include "define_wakaba.h"
#include "led_and_sw.h"

void main(void){
    // 0:OFF, 1:赤, 2:青, 3:紫, 4:緑, 5:黄, 6:水色, 7:白
    unsigned char led_color = 0;  // フルカラーLEDの色

    init_rx631();  // RX631の初期化
    while(1){
        if(g_sw_chg){
            led_color++;
            led_color = led_color & 0x07;
            Full_color_LED(led_color);   // フルカラーLEDを点灯
            g_sw_chg = 0;  // スイッチの押し込みフラグを下げる
        }
    }
}

メイン関数では、RX631の初期化を行い、割り込み関数によってスイッチの押し込みフラグ(g_sw_chg)が立ったら、フルカラーLEDの点灯色を変更して、フラグを下げるようにしています。

CMT0の初期化

初期化関数のソースコードが以下です。ここではCMT0の初期化について説明したいと思います。CMT0に関してはユーザーズマニュアルのp.1085「28. コンペアマッチタイマ(CMT)」に記述があります。

init_rx631.c:初期化関数のソースコード

#include "init_rx631.h"
#include "iodefine.h"

/*** Function Declaration ***/
static void init_clock(void);
static void init_IO(void);
static void init_cmt0(void);

/*---- RX631 Initialization ----*/
void init_rx631(void){
    SYSTEM.PRCR.WORD = 0xA503;  // プロテクト解除

    // クロックの初期化
    init_clock();

    // I/O ポートの初期化
    init_IO();

    // CMT0 の初期化
    init_cmt0();

    SYSTEM.PRCR.WORD = 0xA500;  // 再プロテクト
}

~略~

/*---- CMT0 初期化 ----*/
void init_cmt0(void){

    MSTP(CMT0) = 0; // CMT0のモジュールストップを解除

    CMT0.CMCR.WORD = 0x00C1; // 割り込み許可とPCLKを32分周
    CMT0.CMCOR = 37499;      // 割り込み周期を24msに設定

    IPR(CMT0,CMI0) = 10; // 優先レベルを10に設定
    IEN(CMT0,CMI0) = 1;  // 割り込み許可

    CMT.CMSTR0.BIT.STR0 = 1; // CMT0 開始
}

モジュールストップ機能の解除

MSTP(CMT0) = 0; // CMT0のモジュールストップを解除

モジュールストップ機能によって CMT0 が停止しているため、モジュールストップ状態を解除する必要があります。モジュールストップ機能の説明についてはユーザーズマニュアルのp.297「11. 消費電力低減機能」にあります。マクロが用意されていますので、MSTP(CMT0)=0 でモジュールストップを解除します。

CMT0の設定

CMT0.CMCR.WORD = 0x00C1; // 割り込み許可とPCLKを32分周
CMT0.CMCOR = 37499;      // 割り込み周期を24msに設定

CMCRレジスタを設定することで、割り込み許可とPCLK (PCLKB)を32分周しています。

CMCORレジスタには割り込みが入るカウント数を設定します。PCLKB:50MHz、分周比:32、割り込み周期を24ms としたいので、CMCORレジスタの値は以下のようになります。

\displaystyle{
{\rm CMT0.CMCOR} = (50\times10^6) / 32 \times 0.024 - 1 = 37499
}

注意:CMCRレジスタを設定する際は、7ビット目の説明に「書く場合、“1”としてください」と記述がありますので、WORD単位で設定する必要があります。ビット単位で設定すると、レジスタが書き換わりません。

割り込みコントローラの設定

IPR(CMT0,CMI0) = 10; // 優先レベルを10に設定
IEN(CMT0,CMI0) = 1;  // 割り込み許可

割り込みコントローラについてはユーザーズマニュアルのp.361「15. 割り込みコントローラ(ICUb)」に説明があります。割り込み要求を許可するために、「15.2.2 割り込み要求許可レジスタ m(IERm)」を設定して、割り込み優先度を設定するために、「15.2.3 割り込み要因プライオリティレジスタ n(IPRn)」を設定します。これらについてもマクロが用意されていますので、

  • IEN(<割り込み要求発生元>,<割り込みの名称>) = 1
  • IPR(<割り込み要求発生元>,<割り込みの名称>) = 10

で割り込み許可と優先度の設定をします。

最後に、CMSTR0レジスタのSTR0ビットに"1"を設定して、「28.2.4 コンペアマッチタイマカウンタ(CMCNT)」をカウントアップさせます。CMCNTとCMCORが一致することで割り込みが発生します。

割り込み関数の登録

割り込み関数のソースコードは以下です。実処理は別の関数(SW_status_check)で行っていますが、割り込み関数の登録はこのソースコードで行っています。

interrupt.c:割り込み関数のソースコード

#include "led_and_sw.h"
// 関数のセクションを割り込み関数の領域に変更
#pragma section P PIntPRG 

/*** 関数宣言 ***/
static void CMT0_CMI0(void);  // CMT0 CMI0

// CMT0 CMI0
// スイッチの状態を確認する割り込み関数
#pragma interrupt (CMT0_CMI0(vect=28)) // CMT0_CMI0を割り込み関数に登録
void CMT0_CMI0(void){
    SW_status_check();
}

#pragma section P PIntPRG という記述は、この記述以下の関数を割り込み関数のメモリ領域に置くというものです。

#pragma interrupt (CMT0_CMI0(vect=28))という記述は、指定した関数を割り込みインターフェースに登録します。vect=<割り込み要因に対応したベクタ番号>で、割り込み要因を指定します。割り込みベクタテーブルに関してはユーザーズマニュアルのpp.386-393に記述があります。

補足#pragma interrupt で宣言した関数内に直接実処理を記述したほうが、処理時間は短くなりますが、可読性がよくなると思って実処理は別の関数に記述しています。少しでも処理を速くしたいひとは、直接記述してもいいと思います。

注意:このまま、ビルドをすると、ベクタ番号28が複数回定義されているとして、エラーが生じます。generate/vect.hに以下のような記述があるので、コメントアウトする必要があります。

// CMT0 CMI0
#pragma interrupt (Excep_CMT0_CMI0(vect=28))
void Excep_CMT0_CMI0(void);

割り込み関数の内容について

led_and_sw.c:LEDとスイッチに関するソースコード

#include "define_wakaba.h"
#include "led_and_sw.h"

/*** Global variables ***/
volatile unsigned char g_sw_chg = 0;  // スイッチの押し込みフラグ

// フルカラーLEDの点灯
void Full_color_LED(char color){
    switch(color){
        case OFF:
            LED_RED   = LED_OFF;
~~略~~

// 割り込み関数の実処理
// スイッチの状態を確認をする
void SW_status_check(void){
    // 前のスイッチの状態
    static unsigned char pre_sw_status = SW_OFF;

    // スイッチが押し込まれたとき
    if(PUSH_SW==SW_ON && pre_sw_status==SW_OFF){
        g_sw_chg = 1;  // フラグを上げる
    }
    // 前のスイッチの状態に現在の状態を代入
    pre_sw_status = PUSH_SW;
}

割り込み関数の実処理(SW_status_check)では、24ms 前のスイッチの状態をstatic変数(pre_sw_status)で保持して、

  • 現在のスイッチの状態(PUSH_SW) = ON
  • 24ms前のスイッチの状態(pre_sw_status) = OFF

のときに、スイッチの押し込みフラグ(g_sw_chg)を立てます。

最後にpre_sw_statusに現在の状態を代入して、処理を終了します。

補足グローバル変数g_sw_chg)に型修飾子volatileを付けているのは、割り込み関数によるグローバル変数の変更が必ず反映されるようにするためです。「A.1.4 通常時と割り込み時に使用する変数を定義する」によると、コンパイラによってメモリアクセスが省略されることがあるので、割り込み関数がメモリのデータを書き換えても、メイン関数ではメモリにあるデータを取りにいかないことが起きるそうです。

補足:PSWについて

CMT0の初期化では割り込み許可を以下のようにしました。

  • CMCRレジスタのCMIEビットを"1"として、コンペアマッチの割り込み許可
  • IER03.IEN4ビットを”1”として、CMT0に対してCPUへの割り込み要求を許可

最後にCPU自体の割り込み許可が必要です。割り込みを許可するには、ユーザーズマニュアル p.135「2.2.2.4 プロセッサステータスワード(PSW)」の割り込み許可ビットを"1"にしないといけません。しかし、これについては generate/resetprg.c の中にあるスタートアッププログラム PowerON_Reset_PC で設定がされています。スタートアッププログラム中の CC-RXの組み込み関数 set_psw(PSW_init)PSW の割り込み許可ビットを"1"にしています。もし、割り込みが発生しない場合は、PSWを確認してみてもいいかもしれません。

おわりに

プログラムを書き込んだところ、プッシュスイッチを押すとLEDの点灯色が変わるようになりました。 次回はスピーカーから音を鳴らしてみたいと思います。

参考文献

ZYBOでシンセサイザー作成(5)音を出力する

前回と前々回でパラシリ変換器(sin_gen)と正弦波生成 IP(para2serial) が作成できました。今回は、これらを組み合わせて、ZYBOから音を出力したいと思います。

環境

  • パソコン:Windows10 64 bit
    • Vivado 2020.2 をインストール
    • Xilinx Vitis 2020.2 をインストール
  • ボード:Zybo Z7-20
  • 出力デバイス:スピーカー

システムの構成

システムの構成を図1に示します。

図1:システムの構成
図1:システムの構成

データをスピーカーに出力する手順は以下です。

  1. 「AXI GPIO」で正弦波生成 IPに周波数値を与える
  2. 正弦波生成 IPの出力値をパラシリ変換 IPでシリアルに変換する
  3. コーデックでシリアルデータをDA変換してスピーカーに出力

また、MMCMで50MHzから96MHzのクロックを生成して、カウンタを用いて4分周することで24MHzのMCLKを生成します。コーデックが12MHzのBCLKを生成しますので、BCLKに同期してパラシリ変換 IPでデータを出力します。

IP インテグレータの編集

IP インテグレータで IP どうしの接続を行います。ZYBOでシンセサイザー作成(2)オーディオコーデックの使用 のプロジェクトzybo_synthesizerを編集していきます。

まず、プロジェクトのリポジトリリストに「ip_repo_synth」を追加します。「PROJECT MANAGER」→「Settings」で「Settings」画面を開き、「Project Settings」→「IP」→「Repository」の「+」で「ip_repo_synth」を追加して、「OK」を押します(図1)。

図1:IPリポジトリの追加
図1:IPリポジトリの追加

次に、「Add IP」から以下の IP を追加します。

  • sin_gen_v1_0
  • para2serial_v1_0
  • Clocking Wizard
  • AXI GPIO
  • Binary Counter
  • Slice

AXI GPIO は以下のように設定します(図2)。

  • All Outputs:チェックON
  • GPIO Width:15

図2:AXI GPIOの設定
図2:AXI GPIOの設定

Clocking Wizard は以下のように設定します。

  • Input Frequency:50 MHz
  • Output Freq:96 MHz
  • locked:チェックOFF

図3:Clocking Wizardの設定(Clocking Options)
図3:Clocking Wizardの設定(Clocking Options)

図4:Clocking Wizard の設定(Output Clocks)
図4:Clocking Wizard の設定(Output Clocks)

Binary Counter は以下のように設定します。

  • Output Width:2

図5:Binary Counterの設定
図5:Binary Counterの設定

Slice は以下のように設定します。

  • Din Width:2
  • Din From:1
  • Din Down To:1
  • Dout Width:1

図6:Sliceの設定
図6:Sliceの設定

配線は図7のように接続します。

図7:配線後のIPインテグレーション
図7:配線後のIPインテグレーション

配線ができたら、「Validate Design」で回路チェックをして、「Create HDL Wrapper...」でラッパーを更新します。

制約ファイルについては、「ac_bclk」と「ac_mclk」と「ac_pbdat」と「ac_pblrc」の行のコメントアウトを外します。

##Audio Codec
set_property -dict { PACKAGE_PIN R19   IOSTANDARD LVCMOS33 } [get_ports { ac_bclk }]; #IO_0_34 Sch=ac_bclk
set_property -dict { PACKAGE_PIN R17   IOSTANDARD LVCMOS33 } [get_ports { ac_mclk }]; #IO_L19N_T3_VREF_34 Sch=ac_mclk
set_property -dict { PACKAGE_PIN P18   IOSTANDARD LVCMOS33 } [get_ports { ac_muten }]; #IO_L23N_T3_34 Sch=ac_muten
set_property -dict { PACKAGE_PIN R18   IOSTANDARD LVCMOS33 } [get_ports { ac_pbdat }]; #IO_L20N_T3_34 Sch=ac_pbdat
set_property -dict { PACKAGE_PIN T19   IOSTANDARD LVCMOS33 } [get_ports { ac_pblrc }]; #IO_25_34 Sch=ac_pblrc
#set_property -dict { PACKAGE_PIN R16   IOSTANDARD LVCMOS33 } [get_ports { ac_recdat }]; #IO_L19P_T3_34 Sch=ac_recdat
#set_property -dict { PACKAGE_PIN Y18   IOSTANDARD LVCMOS33 } [get_ports { ac_reclrc }]; #IO_L17P_T2_34 Sch=ac_reclrc
set_property -dict { PACKAGE_PIN N18   IOSTANDARD LVCMOS33 } [get_ports { iic_scl_io }]; #IO_L13P_T2_MRCC_34 Sch=ac_scl
set_property -dict { PACKAGE_PIN N17   IOSTANDARD LVCMOS33 } [get_ports { iic_sda_io }]; #IO_L23P_T3_34 Sch=ac_sda

制約ファイルを保存したら、「Generate Bitstream」をします。

非同期クロック間の制約

「Generate Bitstream」が完了すると、「write_bitstream_Complete」が出ると思いますが、「Open Implemented Design」をして、「Timing」タブの Worst Negative Slack を確認すると負の値になっています(図8)。また、Critical Warning もあるので好ましくありません。

図8:Worst Negative Slack の値
図8:Worst Negative Slack の値

原因は、非同期クロック間で不必要なタイミング解析を行っているからです。MMCMに入力される50MHzのクロックとMMCMから出力される96MHzのクロックは非同期ですが、今回は同期しているとしてタイミング解析がされてしまいました。

非同期クロック間でタイミング解析を行わないように制約を与えます。「Timing」タブ→「Inter-Clock Paths」を選択して、クロックのペアを右クリックしてから「Set Clock Groups...」で設定できます(図9)。

図9:「Set Clock Groups」を開く
図9:「Set Clock Groups」を開く

「Set Clock Groups」では、「Group name」を記入して、「OK」を押します。私の場合は、「Group name」を「50MHz_and_96MHz」としました(図10)。

図10:「Set Clock Groups」の設定
図10:「Set Clock Groups」の設定

設定を終えたら、「Timing Constraints」タブの「Apply」で設定を適用します(図11)。

図11:「Timing Constraints」の設定を「Apply」する
図11:「Timing Constraints」の設定を「Apply」する

制約を適用できたら、再び「Generate Bitstream」でビットストリームを作成します。

制約ファイルを保存するか聞かれますので、「Save」を押します(図12)。

図12:制約ファイルの保存のダイアログ
図12:制約ファイルの保存のダイアログ

次に、「論理合成と配置配線が古くなりますよ」という警告が出ますが、そのまま「OK」でいいです(図13)。

図13:論理合成と配置配線が古くなる警告
図13:論理合成と配置配線が古くなる警告

制約ファイルを更新するか、新しく作成するか聞かれますので、「Update」を押して更新します(図14)。

図14:制約ファイルを「Update」する
図14:制約ファイルを「Update」する

「Select an existing file」が選択されていることと制約ファイル名が正しいことを確認して、「OK」を押します(図15)。

図15:既存の制約ファイルに保存する
図15:既存の制約ファイルに保存する

あとはいつもの設定画面が出ますので入力すると、「Generate Bitsteam」が始まります。

「write_bitstream_Complete」が出たら、一応、タイミングを満たしているか確認します。「Reload」を押して、配置配線後のデザインをリロードします(図16)。

図16:配置配線後のデザインを「Reload」する
図16:配置配線後のデザインを「Reload」する

「Timing」タブからWorst Negative Slackを確認すると正の値になっていて、タイミングを満たしていることが確認できます。

図17:Worst Negative Slack の改善
図17:Worst Negative Slack の改善

最後に、「Export Hardware...」でハードウェア情報であるXSAファイルを作成します。以上でVivadoでの作業は終わりです。

Vitisでプログラムの作成

Vivadoで「Launch Vitis IDE」を押して、Vitisを起動します。ハードウェアの更新をVitisに反映させるために、プラットフォームプロジェクトを選択して、マウス右ボタンから「Update Hardware Specification」を実行します。

次に、ソースコードを書き換えます。書き換えたプログラムが以下です。一応、ソースコードGitHubの「5th」にもあります。

#include "xparameters.h"
#include "xiic.h"
#include "xil_printf.h"
#include "xgpio.h"
#include <sleep.h>

enum adauRegisterAddresses {
    R0_LEFT_ADC_VOL     = 0x00,
    R1_RIGHT_ADC_VOL    = 0x01,
    R2_LEFT_DAC_VOL     = 0x02,
    R3_RIGHT_DAC_VOL    = 0x03,
    R4_ANALOG_PATH      = 0x04,
    R5_DIGITAL_PATH     = 0x05,
    R6_POWER_MGMT       = 0x06,
    R7_DIGITAL_IF       = 0x07,
    R8_SAMPLE_RATE      = 0x08,
    R9_ACTIVE           = 0x09,
    R15_SOFTWARE_RESET  = 0x0F,
    R16_ALC_CONTROL_1   = 0x10,
    R17_ALC_CONTROL_2   = 0x11,
    R18_ALC_CONTROL_2   = 0x12
};

int CodecWrite(XIic*, u8 Address, u16 data);
XStatus CodecInit(XIic *Iic);

int main(void){
    XIic Iic;
    XGpio Gpio0, Gpio1;
    int status;

    xil_printf("IIC Start\n");

    // Codec Initialization
    status = CodecInit(&Iic);
    if(status != XST_SUCCESS) {
        xil_printf("Error Codec Initialization");
        return XST_FAILURE;
    }

    // AXI GPIO Initialization
    status = XGpio_Initialize(&Gpio0, XPAR_GPIO_0_DEVICE_ID);
    if(status != XST_SUCCESS) {
        return XST_FAILURE;
    }
    status = XGpio_Initialize(&Gpio1, XPAR_GPIO_1_DEVICE_ID);
    if(status != XST_SUCCESS){
        return XST_FAILURE;
    }

    // Port Direction
    XGpio_SetDataDirection(&Gpio0, 1, 0x0);  // output
    XGpio_SetDataDirection(&Gpio1, 1, 0x00); // output

    // Output Level
    XGpio_DiscreteWrite(&Gpio0, 1, 0xF);   // All lights up
    XGpio_DiscreteWrite(&Gpio1, 1, 0x1B8); // Set to 440Hz

    xil_printf("IIC Finished\n");
    return XST_SUCCESS;
}

// Write Audio Codec Register
int CodecWrite(XIic* Iic, u8 Address, u16 data){
    u8 Device_Address = 0x1A;       // Device ID
    UINTPTR BaseAddress = Iic->BaseAddress; // AXI IIC BaseAddress
    int num;                        // Number of Data sent

    // set write date
    u8 WR_data[2];
    Address = ((Address<<1) & 0xFE);
    WR_data[0] = Address + ((data>>8)&0x01);
    WR_data[1] = (data&0xFF);

    // send data
    num = XIic_Send(BaseAddress, Device_Address, WR_data, 2, XIIC_STOP);
    if(num!=2){
        xil_printf("Writing data Failed\r\n");
        return XST_FAILURE;
    }
    return XST_SUCCESS;
}

// Audio Codec Initialization
XStatus CodecInit(XIic* Iic){
    int status;

    // Initializes XIic instance.
    status = XIic_Initialize(Iic, XPAR_IIC_0_DEVICE_ID);
    if (status != XST_SUCCESS){
        return XST_FAILURE;
    }

    // Codec register settings
    CodecWrite(Iic, R6_POWER_MGMT,    0x77); // power on
    CodecWrite(Iic, R0_LEFT_ADC_VOL,  0x97);
    CodecWrite(Iic, R1_RIGHT_ADC_VOL, 0x97);
    CodecWrite(Iic, R2_LEFT_DAC_VOL,  0x79);
    CodecWrite(Iic, R3_RIGHT_DAC_VOL, 0x79);
    CodecWrite(Iic, R4_ANALOG_PATH,   0x10);
    CodecWrite(Iic, R5_DIGITAL_PATH,  0x00);
    CodecWrite(Iic, R7_DIGITAL_IF,    0x53);
    CodecWrite(Iic, R8_SAMPLE_RATE,   0x41);
    usleep(80000);  // wait for charging capacity on the VMID pin t=C*25000/3.5
    CodecWrite(Iic, R9_ACTIVE,        0x01);  // digital core active
    CodecWrite(Iic, R6_POWER_MGMT,    0x67);
    return XST_SUCCESS;
}

AXI GPIOの設定を追加して、コーデックのレジスタの値を変更しています。コーデックのレジスタ設定で重要な箇所は以下です。

  • DIGITAL AUDIO I/F Register (0x07) の設定
    • MSビットに"1"を設定して、マスターモードにする
    • LRPビットに"1"を設定して、DSP Submode2にする
    • WLビットに"00"を設定して、16ビットにする
    • Formatビットに"11"を設定して、DSPモードにする
  • Sampling Rate Register (0x08) の設定
    • MCLK=24MHzでDACサンプリングレートが48kHzとなるように設定

ビルドして、実行するとLEDは点灯しますが、音はでないと思います。ミュートスイッチによってミュートされていますので、スイッチを図18のようにすることでミュートがOFFになり、音が出力されます。

図18:ミュートをOFF
図18:ミュートをOFF

上手くできると以下のような音が出ます。出力される波形とスペクトログラムは図19のようになります。

図19:出力される波形とスペクトログラム
図19:出力される波形とスペクトログラム

440Hz以外にも成分があると思いますが、おそらく原因は正弦波生成 IP (sin_gen) だと思います。sin_genについてはあとで作りなおしたいと思います。

変な音がでる

上の例だと出力される音が綺麗なのですが、これは良い例です。7,8割は上手くいかず、以下のような音が出ます(イヤホンでは聴かないほうがいいです)。出力される波形とスペクトログラムは図20のようになります。

図20:変なときの音の波形とスペクトログラム
図20:変なときの音の波形とスペクトログラム

原因はいまだわかっていないです。プログラムの実行順番を変えたりしたら治ったり、プログラムを元に戻しても変にはならなかったりして、原因がわかりません。ロジックアナライザで確認しても、シリアルデータはフォーマット通りのため、FPGAの外の問題なのかもしれません。

おわりに

音の出力が上手くいかず気持ち悪いですが、他のことをやりながら原因をつきとめようと思います。次の記事では、フィルタの作成を行いたいと思います。

RXマイコンにプログラムを書き込む方法

RXマイコンにプログラムを書き込む方法を紹介したいと思います。本記事では、ターゲットデバイスにRX631を用いますが、他のRXマイコンでも同様の方法で書き込みができます。統合開発環境にはe2studioを用います。

開発環境

  • パソコン: Windows10 64 bit
    • 統合開発環境: e2studio 2021-01をインストール
    • コンパイラ: Renesas CCRX v3.03.00をインストール
    • 書き込みソフト: Renesas Flash Programmer V3.08.01(無償版) をインストール
  • ターゲットデバイス: R5F5631PDDFL

e2studioでプロジェクトの作成

e2studioを起動するとワークスペースを選択する画面が出ますので、ワークスペースとして使用するディレクトリを選択して、「起動」ボタンを押します(図1)。

図1:ワークスペースの選択画面
図1:ワークスペースの選択画面

新しいワークスペースを選択して起動すると、「ログ/使用状況データ収集」の画面が出てきますが、そのまま「Apply」でいいです(図2)。

図2:ログ/使用状況データ収集画面
図2:ログ/使用状況データ収集画面

e2studioの操作画面が出てきたら、[ファイル]→[新規]→[Renesas C/C++ Project]→[Renesas RX] をクリックしてプロジェクトを新規に作成します(図3)。

図3:プロジェクトの作成
図3:プロジェクトの作成

最初の画面では、CCRX toolchainを使用するため、「Renesas CC-RX C/C++ Executable Project」を選択して、「次へ」をクリックします(図4)。

図4:プロジェクトのテンプレートの選択
図4:プロジェクトのテンプレートの選択

次の画面ではプロジェクト名を記入します。この記事ではプロジェクト名を「1_LED」としました。記入を終えたら、「次へ」をクリックします(図5)。

図5:プロジェクト名の記入
図5:プロジェクト名の記入

最後の画面では、ツールチェインに Renesas CCRX が選択されていることを確認して、ターゲットデバイスを選択します(図6)。私の場合、「R5F5631PDDFL」を使っているため、「R5F5631PDxFL」を選択しました。ツールチェイン・バージョンについては「v2.08.00」でも大丈夫だと思います。選択したら「終了」でプロジェクトが作成されます。

図6:ツールチェーンとデバイスの選択
図6:ツールチェーンとデバイスの選択

プログラムファイルの出力設定

プロジェクトが作成できたら、プログラムファイルが出力されるように設定します。プロジェクト「1_LED」を選択して、[プロジェクト]→[プロパティ] をクリックすると、プロパティ画面が開きます(図7)。

図7:プロパティ画面を開く
図7:プロパティ画面を開く

開いたら、[C/C++ビルド]→[設定] を選択して、[ツール設定] タブ→[Converter]→[出力] をクリックします。「ロードモジュールコンバータを実行する」にチェックを付けて、出力ファイル形式が「モトローラS形式ファイルを出力する」に設定されていることを確認します。設定を終えたら、「適用して閉じる」で画面を閉じます(図8)。

補足. 出力ファイル形式に「インテルHEX形式ファイルを出力する」を選択してもいいです。

図8:プロジェクトファイルを出力するように設定
図8:プロジェクトファイルを出力するように設定

プログラムをビルドして書き込み

次にソースコードをプロジェクトに追加していきます。エクスプローラーでソースコードのコピーを行い、[1_LED]→[src] を右クリックして「貼り付け」でプロジェクトに追加できます。元からある「1_LED.c」は削除します。

ソースコードをプロジェクトに追加したら、「1_LED」を選択して、左上のトンカチマークをクリックすることで、ビルドが始まります。コンソールに「Build Finished. 0 errors」と表示されれば OK です(図9)。

図9:プロジェクトのビルド
図9:プロジェクトのビルド

ビルドが終わっていれば、「HardwareDebug」フォルダの下に「1_LED.mot」というプログラムファイルが作成されています。Renesas Flash Programmer (RFP) を起動して、プログラムファイルに「1_LED.mot」を選択します。前回の記事のRX631と接続する方法でPCとマウスを接続して、「スタート」で書き込みを行います。「操作が成功しました。」と表示されれば書き込みは成功です(図10)。

補足. 出力形式にHEX形式を選択した場合は、プログラムファイルに「1_LED.hex」を選択してください。

図10:RFPによるプログラムの書き込み
図10:RFPによるプログラムの書き込み

シングルチップモードで起動すれば、書き込んだプログラムが実行されると思います。

参考文献

[1] e² studio 2020-04、e² studio v7.8 ユーザーズマニュアル入門ガイド
[2] Renesas Flash Programmer V3.08 フラッシュ書き込みソフトウェア ユーザーズマニュアル

RXマイコンのソフト開発(1)クロック設定とLチカ

今回からRXマイコン(RX631)のソフト開発を行いたいと思います。まずはクロック設定とLチカからはじめたいと思います。主にこの記事は以下のマウス本を参考にしています。また、RX631のユーザーズマニュアル ハードウェア編もかなり参考にしているので、参照することをおススメします。

開発環境

  • パソコン: Windows10 64 bit
    • 統合開発環境: e2studio 2021-01をインストール
    • コンパイラ: Renesas CCRX v3.03.00をインストール
    • 書き込みソフト: Renesas Flash Programmer V3.08.01(無償版) をインストール
  • ターゲットデバイス: R5F5631PDDFL(RX631 48ピン)

プログラム

早速、記述したプログラムが以下です。初期化関数はinit_rx631.cinit_rx631.h、使用するポートの再定義はdefine_wakaba.hに書いています。メインルーチンはmain.cに書いています。GitHubの「1_led」フォルダの中にソースコードがありますので、ダウンロードしてご利用ください。

init_rx631.c:初期化関数のソースファイル

#include "init_rx631.h"
#include "iodefine.h"

void init_rx631(void){
    unsigned short i=0;
    SYSTEM.PRCR.WORD = 0xA503;  // Unprotect

    /*---- SUBCLK and RTC Initialization ----*/
    // SUBCLK Initialization
    SYSTEM.SOSCCR.BIT.SOSTP = 1;    // Sub CLK STOP
    while(SYSTEM.SOSCCR.BIT.SOSTP != 1); // Wait until SOSTP=1
    RTC.RCR3.BIT.RTCEN      = 0;    // Sub CLK STOP
    while(RTC.RCR3.BIT.RTCEN != 0); // Wait until RTCEN=0

    // Main CLK setting
    RTC.RCR4.BIT.RCKSEL     = 1;    // Select main CLK as count source
    SYSTEM.MOSCCR.BIT.MOSTP = 0;    // Main CLK ON
    for(i=0;i<1000;i++);            // Wait about 50ms

    // SYSTEM CLK setting
    SYSTEM.PLLCR.BIT.PLIDIV = 0x0;  // Main CLK divided by 1
    SYSTEM.PLLCR.BIT.STC    = 0x13; // Main CLK multiplied by 20
    SYSTEM.PLLCR2.BIT.PLLEN = 0;    // PLL Enable
    for(i=0;i<1000;i++);            // wait about 50ms
    // FCK: 4div, ICK:  2div, BCK:  stop, SDCLK stop,
    // BCK: 4div, PCKA: 2div, PCKB: 4div
    SYSTEM.SCKCR.LONG       = 0x21C21211;
    SYSTEM.SCKCR3.BIT.CKSEL = 0x4;  // Select PLL as CLK source
    SYSTEM.LOCOCR.BIT.LCSTP = 1;    // LOCO stop

    // RTC stop and reset
    RTC.RCR2.BIT.START      = 0;    // RTC stop
    while(RTC.RCR2.BIT.START != 0); // Wait until START=0
    RTC.RCR2.BIT.RESET      = 1;    // RTC reset
    while(RTC.RCR2.BIT.RESET != 0); // Wait until RESET=0

    // Disable RTC interrupt requests
    RTC.RCR1.BYTE        = 0x00;    // Alarm interrupt disable
    while(RTC.RCR1.BYTE != 0x00);   // wait until RCR1=0x00
    /*---- End of SUBCLK and RTC Initialization ----*/

    // I/O setting
    PORT1.PDR.BIT.B7 = 1;  // LED_Red:   Out
    PORT2.PDR.BIT.B7 = 1;  // LED_Green: Out
    PORTE.PDR.BIT.B3 = 1;  // LED_Blue:  Out

    PORT1.PODR.BIT.B7 = 1; // LED_Red:   HIGH
    PORT2.PODR.BIT.B7 = 1; // LED_Green: HIGH
    PORTE.PODR.BIT.B3 = 1; // LED_Blue:  HIGH

    PORT1.PMR.BIT.B7 = 0;  // LED_Red:   I/O
    PORT2.PMR.BIT.B7 = 0;  // LED_Green: I/O
    PORTE.PMR.BIT.B3 = 0;  // LED_Blue:  I/O

    SYSTEM.PRCR.WORD = 0xA500;  // Reprotect
}

init_rx631.h:初期化関数のヘッダーファイル

#ifndef __INITRX631_HEADER__
#define __INITRX631_HEADER__

void init_rx631(void);

#endif

define_wakaba.h:使用するポートを再定義するヘッダーファイル

#ifndef __DEFINE_WAKABA_H__
#define __DEFINE_WAKABA_H__

#include "iodefine.h"

// reg define
#define LED_RED    PORT1.PODR.BIT.B7
#define LED_GREEN  PORT2.PODR.BIT.B7
#define LED_BLUE   PORTE.PODR.BIT.B3

// others
#define LED_ON   0
#define LED_OFF  1

#endif /* __DEFINE_WAKABA_H__ */

main.c:メインルーチンのソースファイル

#include "define_wakaba.h"

void main(void){
    unsigned short i=0;
    unsigned short j=0;

    init_rx631();
    while(1){
        LED_RED = LED_ON;
        for(i=0;i<10000;i++)
            for(j=0;j<100;j++);
        LED_RED = LED_OFF;
        for(i=0;i<10000;i++)
            for(j=0;j<100;j++);
    }
}

プログラムのフロー

プログラムの流れは図1のようになっています。サブクロックとリアルタイムクロック(RTC)の初期化後に、I/Oポートの設定をして、Lチカさせます。経験的にはサブクロックとRTCは初期化しなくても問題ないのですが、「電源投入時に設定してください」とマニュアルに書いてあるので設定します。

図1:プログラムのフローチャート
図1:プログラムのフローチャート

RTCの初期化の流れは図2のようになっています。RTCは使う予定がないので、RTCの停止や割り込み要求の禁止をします。

図2:リアルタイムクロックの初期化のフローチャート
図2:リアルタイムクロックの初期化のフローチャート

プログラムの解説

プログラムの各処理ごとの説明をしていきます。詳細についてはRX631のユーザーズマニュアル ハードウェア編を参照してほしいと思います。

レジスタライトプロテクトの設定

SYSTEM.PRCR.WORD = 0xA503;  // Unprotect
~略~
SYSTEM.PRCR.WORD = 0xA500;  // Reprotect

RXマイコンにはプログラムが暴走したときに、重要なレジスタの値が書き換わらないようにレジスタライトプロテクション機能があります(参照 マニュアル p.350)。この機能によって、クロック発生回路関連レジスタの値を書き換えることができないため、PRCRのPRC0ビットを”1”としてプロテクトを解除します。また、将来的に消費電力低減機能関連レジスタにも書き込みたいため、PRCRのPRC1ビットを”1”としています。初期化関数の最後には、再度レジスタをプロテクトしてます。

注意点としては、PRCRレジスタを書き換える場合、上位8ビットに”A5h”を書き込むことを忘れないでください。はじめ、私はPRC0ビットだけを書きかえていたので、PRCRが書き換わっていませんでした。マウス本のプログラムを見て、書き換わっていないことに気づきました。

サブクロックの初期化

// SUBCLK Initialization
SYSTEM.SOSCCR.BIT.SOSTP = 1;    // Sub CLK STOP
while(SYSTEM.SOSCCR.BIT.SOSTP != 1); // Wait until SOSTP=1
RTC.RCR3.BIT.RTCEN      = 0;    // Sub CLK STOP
while(RTC.RCR3.BIT.RTCEN != 0); // Wait until RTCEN=0

48ピンではサブクロックを使用できませんが、サブクロック関連のレジスタの中にはリセット後に値が不定となっているものがあるので、ビットを設定する必要があります(参照 マニュアル p.288-p.289)。SOSCCRのSOSTPビットに"1"、RCR3のRTCENビットに"0"を設定することで、サブクロック発振器を停止させています。

プログラムを見ると、while文で意味のないことをやっているように見えます。これは、レジスタの書き込みの完了を待ってから後続の命令を実行するために挿入しています。どうやら、レジスタへの書き込みの場合、レジスタの値はすぐに書き換わらないようです。マニュアルに「書き換えた後は、書き込みの完了を待ってから後続の命令を実行する」という旨の記述があるときは、このようにwhile文で待っています(今回の場合、マニュアル p.283に記載されている)。

リアルタイムクロックの初期化

RTCについても48ピンでは使えませんが、電源投入時にRTC内のレジスタの値は不定となっているため、ビットを設定する必要があります。設定手順の詳細は、マニュアル p.1138の「リアルタイムクロック電源投入時の初期化に関する注意事項」における「(d) RTC を使用しない場合(カウントソースにメインクロックが使用できる場合)」を参照してほしいと思います。ここでは、「メインクロックの設定」と「システムクロックの設定」だけ説明したいと思います。

メインクロックの設定

// Main CLK setting
RTC.RCR4.BIT.RCKSEL     = 1;    // Select main CLK as count source
SYSTEM.MOSCCR.BIT.MOSTP = 0;    // Main CLK ON
for(i=0;i<1000;i++);            // Wait about 50ms

メインクロックの設定手順は以下です。

  1. RCR4のRCKSELビットを"1"にして、カウントソースにメインクロックを選択
  2. MOSCCRのMOSTPビットを"0"にすることで、メインクロックを発振させる
  3. for文でメインクロック発振安定待機時間が経過するまで待つ

経験的にはメインクロックの安定まで待たなくても動作はしますが、念のため待機します。 メインクロック発振安定待機時間  t_{\rm MAINOSCWT} は以下の式で計算できます(参照 マニュアル p.312)。

\displaystyle{
 t_{\rm MAINOSCWT}= t_{\rm MAINOSC} + \frac{n+16384}{f_{MAIN}}
}


私の使用しているセラロック(CSTNE10M0G550000R0)の場合、

  • 発振安定時間: t_{\rm MAINOSC}=15000 [us]
  • 発振周波数: f_{\rm MAIN}=10 [MHz]

であり、MSTSビットは初期値のため、 待機時間は n=262144 サイクルです。

したがって、

\displaystyle{
\begin{align}
 t_{\rm MAINOSCWT}&=15000 [{\rm us} ] + \frac{262144 +16384}{10[{\rm MHz}]}  \\
 &=42853 [{\rm us} ]
\end{align} 
}


になって、余裕をもって 50ms 待っています。

補足1. セラロックの発振安定時間についてはどこにも見当たりませんでしたが、セラロックアプリケーションマニュアルに掲載されていた立ち上がり時間のグラフの最大値が約15ms だったので、発振安定時間: t_{\rm MAINOSC}=15000 [us] としています。

補足2. [for(i=0;i<1000;i++);] だと100msくらい待機しているかもしれません。待機時間が気になる人は繰り返し回数を減らしてもいいと思います。

システムクロックの設定

// SYSTEM CLK setting
SYSTEM.PLLCR.BIT.PLIDIV = 0x0;  // Main CLK divided by 1
SYSTEM.PLLCR.BIT.STC    = 0x13; // Main CLK multiplied by 20
SYSTEM.PLLCR2.BIT.PLLEN = 0;    // PLL Enable
for(i=0;i<1000;i++);            // wait about 50ms
// FCK: 4div, ICK:  2div, BCK:  stop, SDCLK stop,
// BCK: 4div, PCKA: 2div, PCKB: 4div
SYSTEM.SCKCR.LONG       = 0x21C21211;
SYSTEM.SCKCR3.BIT.CKSEL = 0x4;  // Select PLL as CLK source
SYSTEM.LOCOCR.BIT.LCSTP = 1;    // LOCO stop

システムクロックの設定手順は以下です。

  1. PLLCRのPLIDIVビットに"00"を設定して、PLLの入力分周比を1とする
  2. PLLCRのSTCビットに"010011"を設定して、PLLの周波数逓倍率を20倍にする
  3. PLLCR2のPLLENビットに"0"を設定して、PLLを動作
  4. for文でPLLクロック発振安定待機時間が経過するまで待つ
  5. SCKCRを設定して、システムクロックなどを最大動作周波数とする
  6. SCKCR3のCKSELビットに"100"を設定して、クロックソースをPLL出力とする
  7. LOCOCRのLCSTPビットに"1"を設定して、LOCOを停止

PLLクロック発振安定時間の求め方はマニュアル p.315 にあります。計算すると 22126us だったため、メインクロックのときと同じようにfor文で待ちます。

SCKCRの設定では、各クロックの動作周波数が最大動作周波数となるように分周比を決めます(参照 マニュアル p.255)。各クロックの動作周波数と分周比は以下のようになっています。

  • システムクロック (ICLK) = 200 MHz / 2 = 100 MHz
  • 周辺モジュールクロックA (PCLKA) = 200 MHz / 2 = 100 MHz
  • 周辺モジュールクロックB (PCLKB) = 200 MHz / 4 = 50 MHz
  • FlashIFクロック (FCLK) = 200 MHz / 4 = 50 MHz
  • 外部バスクロック (BCLK) = 200 MHz / 2 = 100 MHz

システムクロックと周辺モジュールクロックBだけ設定すればいいのですが、他のクロックも念のため設定しています。 また、SDCLKとBCLKについては使用しないため、端子出力を停止させています。

注意点1. SCKCRレジスタを書き換える場合、下位8ビットに”11h”を書き込むことを忘れないでください。はじめ、私はビットごとに書きかえていたので、SCKCRが書き換わっておらず、そのせいかクロックソースがLOCOのままでした。かふぇルネの質問を見て、書き換わっていないことに気づきました。

注意点2. 設定の順番には気をつけてください。例えば、マニュアル p.261には「PLLCR2.PLLEN ビットが “0”(PLL 動作)のとき、PLLCR レジスタへの書き込みは禁止です。」と書いてあるため、PLLの設定はPLL動作前にする必要があります。

注意点3. マニュアル p.258に「PLL選択時は1分周は設定禁止です」とあることに気をつけてください。私の場合、PLLの周波数逓倍率を20倍にすることでクロックソースを200MHzとして、これを2分周または4分周しています。

I/Oポートの設定

 // I/O setting
PORT1.PDR.BIT.B7 = 1;  // LED_Red:   Out
PORT2.PDR.BIT.B7 = 1;  // LED_Green: Out
PORTE.PDR.BIT.B3 = 1;  // LED_Blue:  Out

PORT1.PODR.BIT.B7 = 1; // LED_Red:   HIGH
PORT2.PODR.BIT.B7 = 1; // LED_Green: HIGH
PORTE.PODR.BIT.B3 = 1; // LED_Blue:  HIGH

PORT1.PMR.BIT.B7 = 0;  // LED_Red:   I/O
PORT2.PMR.BIT.B7 = 0;  // LED_Green: I/O
PORTE.PMR.BIT.B3 = 0;  // LED_Blue:  I/O

LEDを制御する端子 (P17, P27, PE3) の設定手順は以下です。

  1. PDRを設定して、端子を出力に設定する
  2. PODRを設定して、端子をHIGH出力とする
  3. PMRを設定して、端子を汎用入出力ポートとして使用する

LED部分の回路図を図3に示します。

図3:LED部分の回路図
図3:LED部分の回路図

回路図がこのようになっているため、端子をHIGH出力とすることでLEDを消灯させています。

I/OポートのH出力とL出力の繰り返し

while(1){
    LED_RED = LED_ON;
    for(i=0;i<10000;i++)
        for(j=0;j<100;j++);
    LED_RED = LED_OFF;
    for(i=0;i<10000;i++)
        for(j=0;j<100;j++);
}

最後にLEDの点灯と消灯を繰り返すプログラムを書いています。このプログラムでおよそ0.3秒の周期でLEDが点滅します。システムクロックの設定が上手くできていなくてクロックソースにLOCOが用いられている場合、約30秒周期でLEDが点滅しますので、気をつけてください。

ハマったところ

// RTC stop and reset
RTC.RCR2.BIT.START      = 0;    // RTC stop
while(RTC.RCR2.BIT.START != 0); // Wait until START=0
RTC.RCR2.BIT.RESET      = 1;    // RTC reset
while(RTC.RCR2.BIT.RESET != 0); // Wait until RESET=0

はじめ、私はマニュアル p.1964 の「48 ピン製品の使用上の注意事項」をもとにサブクロックとRTCを初期化していましたが、どうしてもRCR2のRESETビットが"0"にならず、while文で動作が止まってしまいました。

原因としては以下の2つがありました。

  • メインクロックが動作していなかった。
  • システムクロックの設定がうまくできていなかった。

マニュアル p.1964 の「48 ピン製品の使用上の注意事項」には、メインクロックを動作させることやシステムクロックを設定することなどは書かれていないので、上記でも述べたように以下を参照するほうがいいと思います。

  • マニュアル p.288-p.289の「サブクロック発振器に関する注意事項」における「(d) サブクロックを使用しない場合
  • マニュアル p.1138の「リアルタイムクロック電源投入時の初期化に関する注意事項」における「(d) RTC を使用しない場合(カウントソースにメインクロックが使用できる場合)

また、マニュアル p.256の「クロック発生回路のブロック図」を見て、RTCで使用するクロックはシステムクロックとは関係ないと思っていましたが、マニュアル p.1140「LOCO クロックの周波数はメインクロックの周波数より低いため、システムクロックを変更する必要があります」やマニュアル p.1134「周辺モジュールクロック(PCLK)周波数はカウントソースの周波数以上に設定する」と記述があるので、ICLKやPCLKも関係するみたいです。

とにかく、メインクロックの設定とシステムクロックの設定をすることで、リセットが動作するようになりました。私と同じようにリセットが完了しない人は、メインクロックの設定とシステムクロックの設定がきちんとできているか確認するべきだと思います。

プログラムの動作

RXマイコンにプログラムを書き込む方法の手順でプログラムを書き込みます。 書き込みが完了すれば、シングルチップモードで動作させることで図4のようにLチカが動作します。

図4:Lチカの様子
図4:Lチカの様子

おわりに

結構長くなってしまいましたが、参考にしていただければ幸いです。次回はスイッチ入力を受けつけるプログラムを書こうと思います。

ZYBOでシンセサイザー作成(4)パラシリ変換 IP の作成


今回は、前回の記事で作成した正弦波生成 IP(sin_gen)の出力をパラシリ変換して、オーディオコーデック(SSM2603)で受け取れるデータ形式にします。

環境

  • パソコン:Windows10 64 bit
    • Vivado 2020.2 をインストール
    • Xilinx Vitis 2020.2 をインストール
  • ボード:Zybo Z7-20

パラシリ変換 IP の作成

Vivadoでパラシリ変換 IP のpara2serialを製作していきます。作成の仕方は前回のIPの作成とほとんど同じです。

パラシリ変換 IP について

コーデックへのデータの与え方はいくつかありますが、今回は図1の方法でシリアル変換します。コーデックから図1のBCLKとPBLRCの波形がくるので、これらに同期させて、PBDATに波形を送ります。

図1:コーデックへのデータの与え方(SSM2603のデータシートから引用)
図1:コーデックへのデータの与え方(SSM2603のデータシートから引用)

パラシリ変換 IPのフォルダの用意

前回と同じように以下のようにフォルダとファイルを配置していきます。para2serial.svはGitHubの「4th」フォルダにあります。

ip_repo_synth
└── para2serial
    └── HDL
        └── para2serial.sv

para2serial.svの内容を以下に示します。para2serial.svの内容については別の記事でしたいと思います。

para2serial.sv

module para2serial(
    input logic clk96M,
    input logic reset,
    input logic bclk,
    input logic pblrc,
    input logic [15:0] din_L,
    input logic [15:0] din_R,
    output logic pbdat
    );
    
    logic [1:0] cnt4;
    logic load;
    logic shift;
    logic [31:0] sreg_ff;
    
    // cnt4
    always_ff @(posedge clk96M) begin
        if(reset)
            cnt4 <= 2'd0;
        else if(bclk)
            cnt4 <= cnt4 + 2'd1;
        else
            cnt4 <= 2'd0;
    end
                  
    // shift register
    assign shift = (cnt4==2'd3);
    assign load = shift & pblrc;
    
    always_ff @ (posedge clk96M) begin
        if(reset)
            sreg_ff <= 32'd0;
        else if(load)
            sreg_ff <= {din_L, din_R};
        else if(shift)
            sreg_ff <= {sreg_ff[31:0], 1'b0};
    end

assign pbdat = sreg_ff[31];      
    
endmodule

VivadoでIPの製作

プロジェクトを以下のようにして作成します。

  • 作業フォルダ:para2serial
  • プロジェクト名:para2serial

このプロジェクトでIPの製作を行い、IPのシミュレーションも行います。

プロジェクトができたら、前回のVivadoでIPの製作の手順通りに途中まで進めて、IP編集用のVivado画面を起動します。

前回は編集用のVivado画面でいろいろとやりましたが、今回は「Package IP」タブ →「Review and Package」の「Package IP」を押すだけでいいです(図3)。

図2:「Package IP」を押す
図2:「Package IP」を押す

以上でIPが作成され、元のVivadoプロジェクト画面に戻ります。

パラシリ変換 IP のシミュレーション

パラシリ変換 IPであるpara2serialのシミュレーションをしていきます。

IP インテグレータで回路作成

IPインテグレータで回路を作っていきます。

  1. 「IP INTEGRATOR」→「Create Block Design」で「Diagram」を作成する。
  2. 「PROJECT MANAGER」→「Settings」で「Settings」画面を開き、「Project Settings」→「IP」→「Repository」の「+」で「sin_gen」IPを追加して、「OK」を押す(図3)。

    図3:プロジェクトにIPを追加する
    図3:プロジェクトにIPを追加する

  3. 「Add IP」で「sin_gen_v1_0」と「para2serial_v1_0」を追加する。

  4. 図4のようにして、回路を作成する。

図4:作成する回路
図4:作成する回路

回路ができたら、「Validate Design」をして、回路のチェックを行います。また、「Create HDL Wrapper」でラッパーを作成します。ラッパーの作成では「Copy generated wrapper to allow user edits」を選択します。

テストベンチの追加

テストベンチsim_para2serial.svを前回の記事のテストベンチの追加と同じように追加します。sim_para2serial.svはGitHubにあり、内容は以下です。(2021/5/29 追記:PBLRCの周期が96kHzだったため、48kHzになるようにテストベンチを修正しました)。sim_para2serial.svの説明についても別の記事で書こうと思います。

sim_para2serial.sv

`timescale 1ps / 1ps

module sim_para2serial;

localparam [63:0] STEP = 10416;
localparam [63:0] CLKNUM = 960000*4;

logic clk96M;
logic [14:0] freq;
logic reset;
logic bclk;
logic pbdat;
logic pblrc;

logic [1:0] cnt4;
logic [8:0] cnt500;

design_1_wrapper design_1_wrapper(
    .bclk(bclk),
    .clk96M (clk96M),
    .freq (freq),
    .pbdat (pbdat),
    .pblrc (pblrc),
    .reset (reset)
    );

// clk96M
always begin
    clk96M = 0; #(STEP/2);
    clk96M = 1; #(STEP/2);
end     

// cnt4
always_ff @(posedge clk96M) begin
    if(reset)
        cnt4 <= 2'd0;
    else
        cnt4 <= cnt4 + 2'd1;
end

// cnt500
always_ff @(posedge clk96M) begin
    if(reset)
        cnt500 <= 9'd0;
    else if(cnt4==2'd2)
        if(cnt500==9'd499)
            cnt500 <= 9'd0;
        else
            cnt500 <= cnt500 + 9'd1;
end

// pblrc
always_ff @(posedge clk96M) begin
    if(reset)
        pblrc <= 1'b0; 
    else if(cnt500==9'd499 | cnt500==9'd498)
        pblrc <= 1'b1;
    else
        pblrc <= 1'b0;
end

// bclk
always_ff @(posedge clk96M) begin
    if(reset)
        bclk <= 1'b0;
    else if(cnt4==2'd3)
        bclk <= ~bclk;
end

initial begin
    reset = 0;
    freq = 15'd440;
    #(STEP*600);
    reset = 1;
    #(STEP*20);
    reset = 0;
    #(STEP*CLKNUM);
    freq = 15'd600;
    #(STEP*CLKNUM);
    $stop;
end

endmodule

シミュレーションの実行

シミュレーションを実行すると、図6のようになっています。

図6:シミュレーションの結果
図6:シミュレーションの結果

PBDATが図1のデータ形式通りになっていることが確認できると思います。

おわりに

今回でパラシリ変換 IP を作成して、シミュレーションができました。次回はいよいよ音を出力したいと思います。

参考文献

[1] SSM2603のデータシート
[2] 小林 優、「FPGAプログラミング大全 Xilinx編 第2版」、秀和システム、2021.

マイクロマウスのRX631(48ピン)に書き込めない

前回の記事から10日後に図1の基板が届きました。

図1:届いた基板
図1:届いた基板

早速、全ての電子部品をハンダ付けして、プログラムを書き込もうとしたのですが、接続すらできませんでした。 DCマウスを途中まで作ったときにRX631(64ピン)にプログラムを書き込んだ経験があったので、できて当然と思っていたのが間違いでした。やはり、ロボット製作では一つずつ動作確認しなければならないことを痛感しています。

結果的には、明確な原因はわからなかったのですが、発振子の配線部分を変更して、新しい基板を再発注することで接続できるようになりました。

この記事では、接続できるようになるまでの経緯を書きます。

環境

  • パソコン:Windows10 64 bit
    • 統合開発環境 e2studio 2021-01をインストール
    • コンパイラ Renesas CCRX v3.03.00をインストール
    • 書き込みソフト Renesas Flash Programmer V3.08.01(無償版) をインストール
  • ターゲットデバイス:R5F5631PDDFL

RX631と接続する方法

RX631とPCとの接続はUSBシリアル変換モジュールを用いて図2のように行いました。 USBシリアル変換モジュールは秋月で購入したFT234Xです。

図2:RX631とPCとの接続図
図2:RX631とPCとの接続図

書き込み基板は図3のようになっています。

図3:書き込み基板の写真
図3:書き込み基板の写真

RX631はリセット解除時のMD端子とPC7端子の電圧によって表のように動作モードが決まります。

動作モード
MD
PC7
内容
シングルチップモード
HIGH
---
プログラムが動作するモード
ブートモード
LOW
LOW
プログラムを書き換えられるモード

電源投入時に書き込み基板側でMD端子をLOWにすることで、動作モードをブートモードにします。

Renesas Flash Programmerを起動して、「ファイル」→「新しいプロジェクトを作成…」を選択して、以下を入力します(図4)。

  • マイクロコントローラ:RX63x
  • プロジェクト名:wakaba
  • 作成場所:(適当な場所)
  • ツール:COM port

図4:RFPのプロジェクトの作成
図4:RFPのプロジェクトの作成

これで「接続」を押すことで、RX631と接続ができます。

そのはずなのですが、図5のような画面になってしまい、接続が失敗します。 マジかよ…。

図5:接続ができないメッセージ
図5:接続ができないメッセージ

RX631とPCが接続できない原因について

RX631とPCが接続できない原因を頑張って調べました。 とりあえず、ブログ記事などを参考にしながら、原因となりうる箇所を一つずつ確認していきました。

  1. RXDとTXDが逆になっていないか確認
    → 接続確認ができているDCマウスと同じようにしているため、問題ないはず

  2. MD端子とPC7端子の電圧の確認
    → テスターで確認したところ、MD端子は0.0V、PC7端子は0.0Vとなっていたため、問題ないはず

  3. マイコンの電源電圧の確認
    → テスターで確認したところ、VCCは約3.3V、VSSは0.0Vとなっていたため、問題ないはず

  4. ハンダ不良がないか確認
    → 書き込みに関係ある配線についてテスターで導通しているか調べたため、問題ないはず

  5. メインクロックが発振しているか確認
    オシロスコープがないため、確認ができない

  6. RX631自体が破損している
    確認ができない

RX631が破損している以外では、メインクロックが発振していないことくらいしか原因が思いつかなかったので、メインクロックのことについて調べました。

メインクロックに関する調査

メインクロックは書き込みに関係ないと思っていたのですが、関係あるんですね。 たしかに、ユーザーズマニュアルのp.1873にフラッシュ書き換えにはメインクロックの入力が必要であると書いていました。

メインクロックに関して調べるうちに問題ありそうな箇所がいくつか見つかったので、列挙していきます。

  1. セラロックの向きが反対
    回路図で見るとセラロックは左右対称なので、接続方向がないと勝手に思っていたのですが、セラロックの仕様書を見ると向きがあるようです。 ハンダ付けしたセラロックを確認すると、EXTAL(入力)にセラロックの入力が接続されていました。 これが原因かと思って、セラロックを外して、接続方向を正して新しいセラロックを付けなおしました。 しかし、相変わらず接続は失敗しました。

  2. 内臓容量が参考値ではない
    ユーザーズマニュアルのp.261を見ると、内臓容量の参考値が10pF~22pFなのですが、自分のセラロック(CSTNE10M0GH5L000)は33pFでした。 ただ、昔書き込めたマウスのセラロックも33pFだったので、問題ないかなとは思うのですが...。

  3. セラロックがハンダ付けのときに破損した
    セラロックの仕様書を見ると、リフロー対応であり、ハンダごてを使用したハンダ付けは対応していないそうです。それなので、ハンダ付けのときに破損した可能性があります。 ただ、セラロックを付けなおしたとき、かなり丁寧にハンダ付けしたので、問題ないかなとは思うのですが...。

  4. 発振回路の近くに信号線が通過していた
    ユーザーズマニュアルのp.270を見ると、「発振回路の近くには信号線を通過させないでください」(図6)と書いていました。 以前のDCマウス製作時には意識していたのですが、今回は意識していませんでした。 配線図をみると、たしかに図7のように信号線が通過しています。

図6:RX631のユーザーズマニュアルp.270からの引用
図6:RX631のユーザーズマニュアルp.270からの引用

図7:発振回路に信号線が通過している
図7:発振回路に信号線が通過している

これを修正するには、基板を再発注して、部品も注文し直す必要があるので、少し踏ん切りがつきませんでした。

オシロスコープで発振子の動作確認

本当にメインクロックが原因か確かめるために、オシロスコープを購入して、発振回路の動作確認をしました。購入したオシロスコープは以下です。

かなり高かったですが、社会人になりましたし、オシロスコープは今後も使えると思いましたので購入しました。 表面実装用のプローブはあったほうがいいかなと思いましたが、付属のプローブでも測定はできました。 セラロックは以前使っていたCSTCE10M0G55に付けなおして、オシロスコープで電圧を測定しました。 結果を以下の表に示します。

基板
XTAL
EXTAL
書き込めたマウス基板 約10MHzで発振 約1.6 Vで定電圧
今回のマウス基板 約10MHzで発振 約1.6 Vで定電圧

また、今回のマウス基板のXTALとEXTALの測定結果を図8と図9に示します。

図8:XTALの測定結果
図8:XTALの測定結果

図9:EXTALの測定結果
図9:EXTALの測定結果

あれ…書き込めた回路と同じ動作をしているので、正常に動作しているようです。それなら、なぜ書き込めないのだろうか。

発振回路も正常に動作しているようなので、あとはRX631が破損している可能性しか残っていません。ハンダ付けのときにRX631を破損させてしまったのか。 明確な原因はわかりませんが、RX631などの部品を再度注文して、作りなおすことにしました。また、発振回路を通過する信号線についても気になったので、 基板についても再発注しました。

回路図と配線図の変更

回路図と配線図をいくつか変更して、再発注しました。

回路図

図10:再発注した基板の回路図
図10:再発注した基板の回路図
回路図PDF版

回路図の主な変更点は以下です。

  • プルアップ抵抗などの抵抗値の変更
  • CS信号線にプルアップ抵抗を付ける
  • セラロックを変更

ブートモード中にCS信号線がLOWになり、ICがアクティブな状態になって、PC7がHIGHになってしまうという記事を見たので、 CS信号線にプルアップ抵抗を付けました。また、セラロックを以前使っていたCSTCE10M0G55の代替品であるCSTNE10M0G550000に変更しました。 このセラロックは入出力が逆になっても問題ないようです。また、内臓容量は33pFですが、過去のDCマウスでは動いたし大丈夫かなと思い、これを選択しました。

配線図

図11:再発注した基板の配線図
図11:再発注した基板の配線図
配線図PDF版(縮尺:2.5倍)

配線図の主な変更点は以下です。

  • 発振回路の近くに信号線を通過させない
  • いくつかのパッドをハンダ付けしやすいようにする

発振回路の近くに信号線を通過させないこと以外はほとんど変えていないです。

新しい基板に部品を実装して接続

再発注して2週間後くらいに基板が届いたので、書き込みができる最低限の部品をハンダ付けしました。 はじめ、PCと接続したときに相変わらず「接続は失敗しました」と出て、マジかよ…と思いましたが、PC7のプルダウン抵抗をつけ忘れていました。 プルダウン抵抗をつけたら、図12のようになり、接続に成功しました。

図12:接続成功時のメッセージ
図12:接続成功時のメッセージ

本当によかった…。

おわりに

明確な原因がわからなくて、少し腑に落ちないですが、書き込めて本当によかったです。結局、RX631の破損だったのでしょうか? 謎です。次回は L チカやシリアル通信などの記事を書こうと思います。

ZYBOでシンセサイザー作成(3)波形テーブル(BRAM)による正弦波の合成

今回は、波形テーブル(BRAM)を使って正弦波を合成するIP (Intellectual Property) を製作します。前回まではハードウェア記述言語(HDL)を用いずにFPGAを使っていましたが、今回からHDLのSystemVerilog を用いていきたいと思います。

今回の記事で参考にした文献は以下の2つです。

[1] 波形テーブルの参考文献

[2] IP作成の参考文献

環境

  • パソコン:Windows10 64 bit
    • Vivado 2020.2 をインストール
    • Xilinx Vitis 2020.2 をインストール
  • ボード:Zybo Z7-20

波形テーブルによる合成について

SystemVerilogでは三角関数でさえ計算するのに苦労します。そこで、波形テーブル(BRAM)を使用して正弦波を合成します。一応、Xilinx社が三角関数を計算する IP を提供していますが、波形テーブルによる合成は様々な状況で応用が効くと思いますので、こちらの方法で合成します。

波形テーブルによる合成の例

波形テーブルによる合成では、図1のような1周期の波形をRAMの中に保存しておきます。図1の場合、2048個のデータを保存する必要があります。この波形テーブルから順次データを取り出すことによって任意周波数の波形を合成することができます。

図1:正弦波の波形テーブル
図1:正弦波の波形テーブル

例えば、サンプリングレート  f_s が48kHzの場合、周波数 f が12kHzの波形を合成したいなら、0番目、512番目、1024番目、1536番目、0番目...と取り出せばよいです。

任意周波数の正弦波の合成

周波数  f の信号を合成したい場合、データを取り出す波形テーブルの番号 p は以下の式で計算されます [1]。

\displaystyle{
p = {\rm mod} \left(\frac{nfN}{f_s}, N\right)
}

ここで、 f_sはサンプリング周波数、 N は波形テーブルのデータ数、nはサンプル番号です。 {\rm mod}(x, N) というのは「 x Nで割ったときの余り」です。この式をSystemVerilogで計算する必要があります。

正弦波合成 IP の製作

VivadoでBRAMを使った正弦波合成 IP を製作していきます。

正弦波合成 IPのフォルダの用意

まず、以下のようにフォルダを作成して、ファイルを配置していきます。 sin_gen.sv と SinTbl.coe はこちらの GitHub の「3rd」フォルダの中にあります。

ip_repo_synth
└── sin_gen
    ├── HDL
    │   └── sin_gen.sv
    └── ROMDATA
        └── SinTbl.coe

sin_gen.svの内容を以下に示します。sin_gen.svの内容については別の記事でしたいと思います。

module sin_gen(
    input logic clk96M,
    input logic reset,
    input logic [14:0] freq,
    output logic [15:0] dout
    );
    
    logic [20:0] cnt;      // Int 21bit
    logic [10:0] cnt2000;
    logic cnt2000_flag;
    logic [14:0] freq_ff;  // Int 15bit
    logic chg_freq_flag;
    logic [35:0] p_int;    // Int 36bit
    logic [39:0] p_int_ff; // Int 40bit 
    logic [50:0] p;        // Int 32bit  Decimal 19bit
    logic [10:0] p_ff;     // Int 11bit
    
    localparam tmp = 11'd1398; // Int 1bit, Decimal 10bit
    
    // cnt2000
    always_ff @(posedge clk96M) begin
        if(reset)
            cnt2000 <= 11'd0;
        else if(cnt2000_flag)
            cnt2000 <= 11'd0;
        else
            cnt2000 <= cnt2000 + 11'd1;
    end
    
    // cnt2000_flag
    assign cnt2000_flag = (cnt2000 == 11'd1999);
    
    // freq_ff
    always_ff @(posedge clk96M) begin
        if(reset)
            freq_ff <= 15'd0;
        else
            freq_ff <= freq;
    end
            
    // chg_freq_flag
    assign chg_freq_flag = (freq != freq_ff);
    
    // cnt
    always_ff @(posedge clk96M) begin
        if(reset)
            cnt <= 21'd0;
        else if(chg_freq_flag)
            cnt <= 21'd0;
        else if(cnt2000_flag)
            cnt <= cnt + 21'd1; 
        else
            cnt <= cnt;
    end 
    
    // p_int
    assign p_int = cnt*freq;
    
    // p_int_ff
    always_ff @(posedge clk96M) begin
        if(reset)
            p_int_ff <= 40'd0;
        else
            p_int_ff <= {p_int, 4'd0};
    end
            
    // p
    assign p = p_int_ff * tmp;
    
    // p_ff
    always_ff @(posedge clk96M) begin
        if(reset)
            p_ff <= 11'd0;
        else
            p_ff <= p[29:19];
    end
    
    // BRAM
    WAVE_TBL WAVE_TBL(
        .addra (p_ff),
        .douta (dout),
        .clka (clk96M)
    );
endmodule

SinTbl.coeの内容を以下に示します。振幅16384、波形テーブルのデータ数  N=2048 の正弦波の波形テーブルとなっています。Pythonを用いて符号付き整数で出力しました。

memory_initialization_radix=16;
memory_initialization_vector=
0000,
0032,
0065,
0097,
00c9,
…
ffce;

VivadoでIPの製作

プロジェクトを以下のようにして作成します。FPGAに書き込むためのプロジェクトではないので、制約ファイルは作らなくていいです。

  • 作業フォルダ:sin_wave_gen
  • プロジェクト名:sin_wave_gen

このプロジェクトでIPの製作を行い、IPのシミュレーションも行います。

プロジェクトができたら、「Tools」→「Create and Package New IP...」を実行します。

図2:「Create and Package New IP...」を実行
図2:「Create and Package New IP...」を実行

最初の画面(図3)では、そのまま「Next」をクリックします。

図3:そのまま「Next」をクリックする
図3:そのまま「Next」をクリックする

「Packaging Options」(図4)では、「Package a specified directrory」を選択して、「Next」をクリックします。これでフォルダを指定することでIPが作成されます。

図4:「Package a specified directory」を選択する
図4:「Package a specified directory」を選択する

IPのフォルダはさきほど作成した「sin_gen」を選択して、「Next」をクリックします(図5)。自分は「ip_repo_synth」というシンセサイザー用のIPリポジトリに「sin_gen」を置いてありますので、それを選択しました。

図5:IPのフォルダを選択
図5:IPのフォルダを選択

次の画面(図6)では、IPを編集するプロジェクト名の入力をします。ここはデフォルトのまま「Next」をクリックします。

図6:IPを編集するプロジェクト名の入力
図6:IPを編集するプロジェクト名の入力

最後の画面(図7)では、そのまま「Finish」をクリックします。HDLのSystemVerilogファイルにエラーがある場合、エラーが生じて、IPが作成できないので気をつけてください。

図7:「Finish」をクリックする
図7:「Finish」をクリックする

IP編集用のVivado画面が図8のように起動され、「sin_gen/HDL」フォルダ内の回路記述が自動で読み込まれます。起動した画面の「Sources」ペインでは「WAVE_TBL」に?マークがあり、プロジェクトにBRAMが不足しています。そのため、BRAMを追加していきます。

図8:IP編集用のVivado画面
図8:IP編集用のVivado画面

BRAMを追加するには「PROJECT MANAGER」→「IP Catalog」を実行します。IPカタログ内の「Memories & Storage Elements」→「RAMs & ROMs & BRAM」→「Block Memory Generator」をダブルクリックします。

図9:「Block Memory Generator」を実行
図9:「Block Memory Generator」を実行

BRAM生成用のウィザードではタブごとに設定を変更していきます。まず、「Basic」タブ(図10)では以下のように設定を変更します。

  • Component Name: WAVE_TBL
  • Memory Type: Single Port ROM

「Single Port ROM」とすることで、1ポートのROMとなります。RAMでもよいのですが、信号線が多くなるのでROMにしています。

図10:「Basic」タブでの設定
図10:「Basic」タブでの設定

「Port A Options」タブでは、以下のように設定を変更します。

  • Port A Depth: 2048
  • Enable Port Type: Always Enabled
  • Primitives Output Register: チェックOFF

「Port A Depth」で波形テーブルのデータ数を設定しています。「Enable Port Type」では「Always Enabled」にすることでイネーブル信号を不要としています。また「Primitives Output Register」をチェックOFFすることで出力にレジスタ(FF)を付けないようにしています。

図11:「Port A Options」タブでの設定
図11:「Port A Options」タブでの設定

「Other Options」タブでは、以下のように設定を変更します。

  • Load Init File: チェックON
  • Coe File: sin_gen/ROMDATA/SinTbl.coe を選択

これで、SinTbl.coeに記述してあるデータがBRAMにロードされます。

図12:「Other Options」タブでの設定
図12:「Other Options」タブでの設定

最後に、「Summary」タブで設定を確認します。以下のようになっていることを確認してください。

  • Memory Type: Single Port ROM
  • Block RAM resource(s)(18K BRAMs): 0
  • Block RAM resource(s)(36K BRAMs): 1
  • Total Port A Read Latency: 1 Cock Cycle(s)
  • Address Width A: 11

波形テーブルのデータ数を2048にすることで、36K BRAMsをちょうど1個使うようにしています。

図13:「Summary」タブで確認
図13:「Summary」タブで確認

保存場所を確認して(図14)、「OK」をクリックします。

図14:保存場所を確認
図14:保存場所を確認

また、「Create Output Products」の画面では、「Out of context per IP」を選択して、「Generate」をクリックします。「Out of context per IP」の場合、個々のIPごとに論理合成を行うので、全体の論理合成時間が短いというメリットがあります。一方、「Global」の場合、全体の論理合成時にIPを再合成するので、論理合成時間は長いが、生成ファイルが少ないという特徴があります。

図15:「Out of context per IP」を選択
図15:「Out of context per IP」を選択

これで、「Package IP」→「Files Groups」の「Merge changes from File Groups Wizard」をクリックして(図16)、「Synthesis(3)」となれば、問題ないです。

図16:「Package IP」の「File Groups」
図16:「Package IP」の「File Groups」

ただ、基本的にはVivadoの不具合のため「Synthesis(2)」となり、WAVE_TBLが読み込まれません(図17)。

図17:「Merge changes from File Groups Wizard」した後
図17:「Merge changes from File Groups Wizard」した後

対策方法としては、一度削除し追加することです。

  1. WAVE_TBLを「Remove File from Project...」する
  2. 「PROJECT MANAGER」→「Add Sources」からWAVE_TBLを追加
  3. 「Merge changes from File Groups Wizard」をクリックすると、「synthesis(3)」となる(図19)

「Add Sources」するときは「Add or create design sources」を選択して、「sin_gen/src/WAVE_TBL/WAVE_TBL.xci」を追加してください。また、Copy sources into IP DirectoryはチェックOFFしてください(図18)。

図18:「Copy sources into IP Directory」をチェックOFF
図18:「Copy sources into IP Directory」をチェックOFF

図19:「Synthesis(3)」となる
図19:「Synthesis(3)」となる

最後に「Package IP」→「Review and Package」の「Package IP」をクリックすることでIPが作成されます(図20)。

図20:「Package IP」をクリック
図20:「Package IP」をクリック

プロジェクトを閉じていいかのダイアログが出ますので(図21)、「OK」をクリックしてください。

図21:プロジェクトを閉じるかのダイアログ
図21:プロジェクトを閉じるかのダイアログ

以上でIPが作成され、元のVivadoプロジェクト画面に戻ります。

IP のシミュレーション

「sin_wave_gen」プロジェクトで IP「sin_gen」のシミュレーションをしていきます。

IP インテグレータで回路作成

IPインテグレータで回路を作っていきます。

  1. 「IP INTEGRATOR」→「Create Block Design」で「Diagram」を作成
  2. 「Add IP」で「sin_gen_v1_0」を追加
  3. 「sin_gen_v1_0」の全てのポートに対して「Make External」を実行
  4. ポート名を「_0」がないように変更

最終的な回路は図22のようになります。

図22:最終的な IP インテグレータ
図22:最終的な IP インテグレータ

回路ができたら、「Validate Design」をして、回路のチェックを行います。また、「Create HDL Wrapper」でラッパーを作成します。いつも通り「Copy generated wrapper to allow user edits」にしてください。

テストベンチの追加

回路のシミュレーションを行うためのファイルであるテストベンチをプロジェクトに追加します。追加するテストベンチ「sim_sin_gen.sv」はGitHubにあります。

このテストベンチをプロジェクトディレクトリの中に置いて、読み込みます。読み込む手順は以下です。

  1. 「PROJECT MANAGER」→「Add Sources」を実行
  2. 「Add or create simulation sources」を選択
  3. 「Add Files」で「sim_sin_gen.sv」を選択
  4. 「Scan and add RTL include files into project」と「Include all design sources for simulation」をチェックON(図23)
  5. 「Finish」をクリックする

図23:ファイル追加時のチェック
図23:ファイル追加時のチェック

シミュレーションの実行

シミュレーションを実行をします。シミュレーションは「SIMULATION」→「Run Simulation」→「Run Behavioral Simulation」で実行できます(図24)。シミュレーションファイルにエラーがある場合は、実行できないので気をつけてください。

図24:シミュレーションの実行
図24:シミュレーションの実行

シミュレーションの実行が上手くいくと図25のようになります。1nsまではシミュレーションが実行されています。「Run All」ボタンをクリックすることで、全てのシミュレーションが実行されます。

図25:シミュレーション時の画面
図25:シミュレーション時の画面

今回の場合は80msまで実行されます。「Zoom Fit」をクリックすることで、図26のように全体の結果をみることができます。

図26:全体のシミュレーション結果
図26:全体のシミュレーション結果

このままでは、doutが正弦波になっているかわかりづらいので、表示の仕方を以下のように変更します。

  • 「dout」を右クリックして、「Waveform Style」→「Analog」を選択
  • 「Waveform Style」→「Analog Setting」で「Y Range」を「Fixed」にして、Minを-18000、Maxを18000に変更(図27)
  • 「dout」を右クリックして、「Radix」→「Signed Decimal」を選択

図27:「Analog Setting」の画面
図27:「Analog Setting」の画面

表示の仕方を変更すると図28のようになります。正弦波になっていることがわかると思います。

図28:設定変更後の結果
図28:設定変更後の結果

おわりに

今回で正弦波を作成することができました。次回はパラシリ変換器を作成してスピーカーから音を出したいと思います。また、SystemVerilogの説明を全くしていないので、補足記事を書きたいと思います。

お問い合わせフォーム プライバシーポリシー

© 2021 Setoti All rights reserved.