💭

FPGAでルックアップテーブルをもとに三角関数を出力する

に公開

※にひにひのメモです.

0. はじめに

FPGAにおいて,三角関数の計算はマイコンみたいに三角関数の関数を呼べばいいという単純なものではありません.実際に逐一計算させようとすると[1]のようにそれだけでかなりの論理素子を使ってしまいます.今回は三角関数のルックアップテーブル用のcsvを用いて三角関数を使っていきたいと思います.

  1. 注意点
  2. 読み込むsin.csvについて
  3. simulationにおいて($readmemh)
  4. FPGAに書き込み
  5. まとめ

1. 注意点

  • FPGAに実際に書き込むときとシミュレーションを行うときで,使える関数やルックアップテーブルのファイル形式が異なる場合があります.
  • 今回は一気にファイルを読み込む形にします.一行ずつ読み込む方法もあります.[2]

今回"にひにひ"が行った環境は以下の通りです

  • Windows11 Education
  • Quartus Prime Lite Edition 20.1.1
  • 使用FPGA:10M08SAE144C8GES
  • PulseView(ロジックアナライザ用ソフトウェア)
  • 安物ロジックアナライザ24MHz8ch(詳細不明,これと同じものかと思われる)

2. 読み込むsin.csvについて

ルックアップテーブル用のcsvを作成するコードをpythonで書きました.今回はこれで生成したsin.csvを読み込ませます.

import math
# パラメータ設定
num_points = 256              # データ点数
amplitude = 127               # 振幅(8bit符号なしで 0~255 に収まるようにする)
offset = 128                  # オフセット(中心を0→128にシフト)
filename = "sin.csv"          # 出力ファイル名
with open(filename, "w") as f:
    for i in range(num_points):
        angle = 2 * math.pi * i / num_points
        sin_val = int(amplitude * math.sin(angle) + offset)
        hex_str = f"{sin_val:02X}"  # 2桁の16進表記(大文字)
        f.write(hex_str + "\n")
print(f"{filename} を生成しました.")

生成したcsvは以下の図のような正弦波の値となっています.
生成したsinの値

3. simulationにおいて($readmemh)

sin.csvからデータを配列データsin_memに読み込みます.

$readmemh("sin.csv", sin_mem);

0~99までのデータを読み込みたい場合,

$readmemh("sin.csv", sin_mem, 0, 99);

とすれば良いです.

実際にsinのデータを読み込んで出力するモジュールを記述しました.

module readcsv (
    input wire clk,
    output reg [7:0] sin_out
);
    // パラメータ
    parameter DATA_LEN = 256;
    reg [7:0] sin_mem [0:DATA_LEN-1];
    integer i;	 
    // 出力用カウンタ
    reg [7:0] addr;

    initial begin
        // CSVファイル読み込み
        $readmemh("C:/XXXXX/sin.csv", sin_mem);//sin.csvの絶対パス
        addr = 0;
    end

    always @(posedge clk ) begin
			sin_out <= sin_mem[addr];
			if (addr < DATA_LEN - 1)
				 addr <= addr + 1;
    end
endmodule

このコードを雑に説明すると,initial begin-endでcsvファイルを読み込み,sim_memに格納します.always begin-endでsin_memの要素をひとつづつaddrをインクリメントしてsin_outに格納しています.

ブロック図は以下の通りです.特別なことは何もしていないです.
ブロック図

シミュレーションの実行結果は以下の通りです.
simresult

理想の信号波形が以下のような感じなので,だいたいあってそうですね.
ideal

4. FPGAに書き込み

今回安物のロジアナで測定する関係上,分周器を挟むように修正しました.(以下のブロック図参照,分周器モジュールのコードは付録2に載せています)
そして,実際にMAX10 FPGA 10M08SAE144C8Gに書き込みました.

block2

測定風景↓
FPGA

シミュレーション(上図),FPGAにロジックアナライザをつないで測定(下図)
sim
logana

5. まとめ

csvを読み込んでFPGAのシミュレーションを行うことができた.

参考文献・サイト

付録1

以下FPGAのsin_outの信号波形の理想波形をプロットするコードです.

import numpy as np
import matplotlib.pyplot as plt

# CSVファイルの読み込み
filename = "sin.csv"
with open(filename, "r") as f:
    sin_vals = [int(line.strip(), 16) for line in f]

# 各ビットに対応する信号を生成
sin_bits = np.array([[(val >> bit) & 1 for bit in range(8)] for val in sin_vals])

# プロット
plt.figure(figsize=(10, 6))
time = np.arange(len(sin_vals))

# 各ビットをプロット(縦方向にシフト)
shifted_sin_bits = sin_bits + np.arange(8)  # 各ビットの信号を縦にシフト

for bit in range(8):
    plt.plot(time, shifted_sin_bits[:, bit], label=f'Bit {bit}')

plt.title('Ideal Signal Waveform for sin_out Bits [0-7] (Shifted)')
plt.xlabel('Sample Index')
plt.xlim([0, len(sin_vals)-1])
plt.ylabel('Shifted Bit Value')
plt.legend(loc='upper right')
plt.grid(True)
plt.show()

出力結果↓

付録2

実際にFPGAに書き込んだ時に使った分周器用コード

fd.v

module fd(
    input wire clk_row,       // 50MHz クロック入力
    output reg clk_fd         // 1MHz 出力クロック
);

    // === パラメータとレジスタ定義 ===
    localparam DIVIDE_BY = 25;  // トグルごとのカウント値(1周期で50クロック分)

    reg [5:0] counter = 0;      // カウンタ(6ビットで十分)

    always @(posedge clk_row) begin
        if (counter == (DIVIDE_BY - 1)) begin
            counter <= 0;
            clk_fd <= ~clk_fd;  // トグルして周波数を半分に
        end else begin
            counter <= counter + 1;
        end
    end
endmodule

Discussion