愚直なRTLでソートを記述する

FPGAの部屋の記事、 RGB 24ビット・データ入出力対応のメディアン・フィルタをVitis HLS 2021.1で作成する1RGB 24ビット・データ入出力対応のメディアン・フィルタをVitis HLS 2021.1で作成する2にて、HLS記述にてバブルソートを記述し、Interval=1を得ている(1クロック毎にデータを入力できるパイプラインで動作)のを見て、 実はHLSではなく、愚直にRTLで書いてもInterval=1は達成できるのではないかと思い、実験してみました。

ソートをハードウェアで実装するための手法はよく研究されており、ソーティングネットワークと呼ばれているようです。

Wikipediaによれば、3x3(=9)画素のソートを行うためには、7段のソーティングネットワークが最小の段数であるそうです。

“sorting network generator"などと検索すると、ソーティングネットワークの自動生成ツールなどもあり、興味がある方には面白いかもしれません。

書いてみたRTL記述はこちら:

module sort (
    input            CLK,
    input [15:0]     in[0:8],
    output reg[15:0] out
);

    logic[15:0] tmp;
    logic[15:0] in_val[0:8];
    logic[15:0] result;

    int i,j;

    always @(*) begin
        in_val = in;
        for (i = 1; i < 9; i = i + 1) begin
            for (j = 0; j < 9 - i; j = j + 1) begin
                if (in_val[j] < in_val[j+1]) begin
                    tmp         = in_val[j];
                    in_val[j]   = in_val[j+1];
                    in_val[j+1] = tmp;
                end
            end
        end
        result = in_val[4];
    end

    always_ff @(posedge CLK) begin
        out <= result;
    end

endmodule

9個の数値(各16bit)からなる配列inを、上記記事ではC言語で書かれているのと同じ方法でソートしています。 このようなコードは、ある程度RTL記述に慣れた人の方が違和感を感じるかと思います。 ノンブロッキング代入(<=)ではなくブロッキング代入(=)を使っています。

RTLでforループを使用する場合、同じような回路の複数コピーを作成する、というのが用途の大部分を占めると思います。 上記記述もその一例と言えなくは無いですが、どのような回路が合成されるのか、直観的には把握しずらいです。

どのように論理合成が行われるのか、CRC生成回路に実例と共に説明されていますが、要するに、ループ内で使われている変数tmp, in_valに、i, jの値を添え字としてループ毎に別名を付けてあげると思えば、 理論的には展開できるというのが想像できると思います。内側のループ(j)が8,7,6,…,1回回るので、 合計36回ifの部分が展開されることになります。手計算で行おうと思うと気が遠くなりますが、合成ツールは賢いです。

次のようなテストベンチを作ってみます。

module sort_tb;

    logic CLK;
    logic [15:0] in[0:8];
    logic [15:0] out;

    sort dut (.*);

    initial begin
        CLK = 0;
    end

    always #10 CLK <= ~CLK;

    always @(posedge CLK) begin
        in[0]   <=  $urandom();
        in[1]   <=  $urandom();
        in[2]   <=  $urandom();
        in[3]   <=  $urandom();
        in[4]   <=  $urandom();
        in[5]   <=  $urandom();
        in[6]   <=  $urandom();
        in[7]   <=  $urandom();
        in[8]   <=  $urandom();
    end

endmodule

シミュレーションしてみた波形が次です。画面上のsynthはsortに置き換えてください。

シミュレーション画像

緑の入力に対して、1クロック遅れて黄色の出力が得られています。目視でチェックした限り、9つの値の中央値が得られてそうです。

論理合成してみた結果をRTL Viewerで確認してみます。

RTL Viewer

一番右側に一つだけ16bitのレジスタがあって、残りは比較器とマルチプレクサになっています。 何段になっているか、しっかり数えませんでしたが(12段?)、さすがに最小値の7までは行ってなさそうです。

配置配線まで実行してみたところ、Cyclone Vで、ALM 988個、Total registers 16となりました。

ちなみに、久々にQuartusでシミュレーションしようとしたら、ModelSimの起動方法に手間取って、Quartus Prime と ModelSim の NativeLink の使い方を参考にして解決しました。

FMステレオ復調を実装

やっとFMステレオ復調処理を実装しました。処理としては、 ディジタルFMステレオ・チューナの製作 ―― 雑誌の付属基板でここまでできる(ステレオ復調回路)にある通りです。

今回の実装について以下に説明しますが、勘違いしている可能性もありますので、 ご自分で確認してください。

DDSのIPを置いて、位相を可変とした19kHzのsin, cos信号を生成します。 cos信号とFM復調信号を掛け算し、その結果をLPFに通します。 LPFは、CICでfsを1/20にして(fs=500kHzからfs=25kHzに)、続いてFIRでfc=1kHzのフィルタを掛けました。 そして、この値に応じて、DDSで発振する19kHz信号の位相を進めたり遅らせたりします。

具体的には、パイロット信号をsin 19kHzとすると、三角関数の公式により、 sin x * cos y = 1/2 * {sin(x-y)+sin(x+y)} です。今回は、sin 19kHz * cos 19kHz = 1/2 * {sin(0) + sin(38kHz)} となります。LPFを通すことでsin(38kHz)は0になりますので、sin(0)だけが残ります。 つまり、パイロット信号と同調したcos信号が得られれば、sin(0)=0となります。 ただし、sinが0となるのは、0(x=yのとき)とπ(x=y+πのとき)がありますが、今回は0になるようにします。 それで、sinが正であれば、cosの位相(y)を増やし、sinが負であれば、 cosの位相を減らして、sin(x-y)のx-yが0となるように制御しました。

このようにsinのパイロット信号に対してcosの信号が得られたら、倍角の公式 sin 2x = 2 sin x * cos x により、DDSのsin, cos信号を掛け算することで、パイロット信号に同期した38kHzの信号が得られることになります。

あとは、この38kHzの信号をFM復調信号に掛け算し、LPFを通すことでL-Rの信号が得られます。

この状態で時報信号をキャプチャしたものが次になります。

“FM横浜時報信号”

ちょっと分かりにくいですが、上側のグラフは、左右の音声を別々の色で描画しています。 時報信号だと恐らく左右同じ音声信号になってしまっているので、ほとんど重なってしまい、 分かりにくいです。

実際に聞いてみて、確かにこれまでより奥行きが感じられるようになりました。 ただ、この実装がどれほど正しいかは、上のグラフだけでは分かりません。 本来であれば左だけ、右だけの音声を復調してみて、それが正確に再生されるか確認する必要があります。 ただ、手ごろなFMトランスミッタを持ち合わせていないので、このような確認まではできていません。

Zynqで動作するWebSocketサーバから受信したデータをブラウザにてFFT表示

色々試行錯誤することで、ようやく目的の動作に少し近づきました。

Zynq上でWebSocketサーバを動作させ、ADCからキャプチャしたデータをブラウザに送り込み、 ブラウザにてFFTを行いリアルタイムに表示します。

WebSocketサーバはRustで作成しました。

まだFFTのWindow処理は行っていません。1回描画するために256kBの生データをブラウザに送っています。

FFT自体はこちらを使わせてもらっています(参考: FFTs in JavaScript)。

グラフ表示は、Chart.jsを使用しています。

まず、Zynqで動作するプログラムです。

次の記事がUIO経由のレジスタアクセスの参考になりました: 【Rust】Raspberry Pi 3でGPIOのレジスタを叩いてLチカ

read, mmapなどのシステムコール、メモリアドレスからVecへの変換、 データ取り込みスレッドとサーバスレッドとの同期方法など、 色々と苦労したノウハウが詰まっています。

/dev/memをmmapすることで、Z-turn boardに搭載されている1GBのDDRの後半512MBを仮想アドレス空間にマップしています。 PLから当該領域にAXIバス経由でADCデータは転送されます。転送を行うためのIPの設定を、 /dev/uio0にマップされた制御レジスタを通して行います。/dev/uio0にreadを行うことで、割り込み待ちも行えます。

ただし、まだデータ取り込みとブラウザへのデータ転送はオーバーラップしていません。 シーケンシャルに取り込み->転送と動作しています。

extern crate ws;
extern crate libc;

use ws::{listen, Handler, Result, Message, CloseCode, Handshake};
use ws::Message::Text;
use ws::Message::Binary;
use ws::util::Token;
use std::fs::{OpenOptions, File};
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::{AsRawFd};
use std::io;
use std::io::{Write, Read, Cursor};
use std::ptr::{self, read_volatile, write_volatile};
use std::thread;
use std::sync::mpsc::channel;
use std::sync::mpsc::{Sender, Receiver};
use std::sync::Arc;
use std::slice;
use std::vec::Vec;
use std::mem;
use std::boxed::Box;

struct Server {
    out: ws::Sender,
    tx: Arc<Sender<i32>>,
    rx: Arc<Receiver<u32>>
}

const KB : libc::size_t = 1024;
const MB : libc::size_t = 1024*1024;
const MEM_SIZE : libc::size_t = 512*MB;
const MEM_OFFSET : libc::off_t = 512*MB as libc::off_t;
const ZERO_OFFSET : libc::off_t = 0;
const PAGE_SIZE : libc::size_t = 4096;

fn memmap() -> io::Result<*mut u32> {
    let mem_file = OpenOptions::new()
                    .read(true)
                    .write(true)
                    .custom_flags(libc::O_SYNC)
                    .open("/dev/mem")
                    .expect("can't open /dev/mem");
    unsafe {
        let ptr = libc::mmap(ptr::null_mut(),
                            MEM_SIZE, libc::PROT_READ | libc::PROT_WRITE,
                            libc::MAP_SHARED,
                            mem_file.as_raw_fd(),
                            MEM_OFFSET);        // second 512MB of the total of 1GB of DDR memory
        if ptr == libc::MAP_FAILED {
            Err(io::Error::last_os_error())
        }
        else
        {
            Ok(ptr as *mut u32)
        }
    }
}

fn uiomap() -> (*mut u32, File) {
    let uio_file = OpenOptions::new()
                    .read(true)
                    .write(true)
                    .custom_flags(libc::O_SYNC)
                    .open("/dev/uio0")
                    .expect("can't open /dev/uio0");
    unsafe {
        let ptr = libc::mmap(ptr::null_mut(),
                            PAGE_SIZE, libc::PROT_READ | libc::PROT_WRITE,
                            libc::MAP_SHARED,
                            uio_file.as_raw_fd(),
                            ZERO_OFFSET);
        if ptr == libc::MAP_FAILED {
            panic!("{}", io::Error::last_os_error())
        }
        (ptr as *mut u32, uio_file)
    }    
}

fn set_axi_dma_reg(reg_adr : *mut u32, size : u32, offset : u32) {
    let status_reg : *mut u32 = unsafe { reg_adr.offset(0) };
    let ien_reg : *mut u32 = unsafe { reg_adr.offset(1) };
    let done_reg : *mut u32 = unsafe { reg_adr.offset(2) };
    let offset_reg : *mut u32 = unsafe { reg_adr.offset(4) };
    let len_reg : *mut u32 = unsafe { reg_adr.offset(6) };

    unsafe {
        let status = read_volatile(status_reg); // clear ap_done
        write_volatile(ien_reg, 1);     // enable
        write_volatile(done_reg, 1);    // IP Interrupt enable reg
        write_volatile(offset_reg, offset/8);
        write_volatile(len_reg, size/8);
    }
}

fn start_axi_dma(reg_adr : *mut u32){
    let start_reg : *mut u32 = unsafe { reg_adr.offset(0) };
    unsafe {
        write_volatile(start_reg, 1);
    }
}

fn clear_interrupt(reg_adr : *mut u32){
    let intcl_reg : *mut u32 = unsafe { reg_adr.offset(3) };
    unsafe {
        write_volatile(intcl_reg, 1);
    }
}

fn wait_interrupt(uio_file : &mut File) -> (){
   let mut tmp = [0; 4];
   if 4 != uio_file.read(&mut tmp).unwrap() {    // wait interrupt
        panic!("failed read");
   }
}

impl Handler for Server {
    fn on_open(&mut self, _shake: Handshake) -> Result<()> {
        println!("on_open");
        Ok(())
    }
    fn on_timeout(&mut self, event: Token) -> Result<()> {
        println!("on_timeout");
        Ok(())
    }
    fn on_message(&mut self, msg: Message) -> Result<()> {
        match msg {
            Text(_) => {
                let vec: Vec<u8> = vec![0; 1920*1080];
                self.out.send(vec)
            },
            Binary(v) => {
                let adr = self.rx.recv().unwrap();
                let mut vec : Vec<u8> = Vec::new();
                let ary : &'static [u8] = unsafe { slice::from_raw_parts(adr as *const u8, 256*KB) };
                vec.write(ary).expect("unable to write");
                let r = self.out.send(Binary(vec));
                self.tx.send(1);
                r
            }
        }
    }
    fn on_close(&mut self, _code: CloseCode, _reason: &str) {
    }
}

fn main() {
   let mapped_ptr : *mut u32 = memmap().expect("failed mmap");
   let (uio_ptr, mut uio_file) = uiomap();
   let one : [u8; 4] = [1, 0, 0, 0];

   let (tx_web, rx_cap) = channel();
   let (tx_cap, rx_web) = channel();

   let hdl = thread::spawn(move || {
       let tx = Arc::new(tx_web);
       let rx = Arc::new(rx_web);
       listen("192.168.0.90:3012",
                |out| Server { out: out, tx: Arc::clone(&tx), rx: Arc::clone(&rx) })
                .unwrap();
   });
   loop {
        set_axi_dma_reg(uio_ptr, 256*KB as u32, 512*MB as u32);
        uio_file.write(&one).expect("write");            // enable interrupt
        start_axi_dma(uio_ptr);
        wait_interrupt(&mut uio_file);
        clear_interrupt(uio_ptr);
        tx_cap.send(mapped_ptr as u32);    // send start address
        let v = rx_cap.recv().unwrap();
   }
}

ビルド/デプロイ方法は、一つ前の記事を参考にしてください。

一方、ブラウザ側は次のようになりました。Chart.jsのメモリリークの対応など、 こちらもそれなりに苦労しました。一旦Chartが生成されたら、データの更新はupdateを呼び出すことで行います。

decode_valuesは、8bytes(64bits)に12bitのADCデータ5個が詰めてあるのを分解しています。 データは2047から-2048の範囲となります。

<html>
    <head>
        <script
            src="https://code.jquery.com/jquery-3.2.1.js"
            integrity="sha256-DZAnKJ/6XZ9si04Hgrsxu/8s717jcIzLy3oi35EouyE="
            crossorigin="anonymous"></script>
        <script type="text/javascript" src="fft.js"></script>
        <script type="text/javascript" src="windowing.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.bundle.js"></script>
    </head>
    <body>
        <p><div id="time">Time[ms]</div></p>
        <button id="start">Start</button>
        <canvas id="chart" width="1280" height="600"></canvas>
        <script type="text/javascript">
            $(function() {
                const FFT_POINTS = 8192*2;

                var ctx = $('#chart')[0].getContext('2d');
                var chart = null;

                function decode_values(buf, len){
                    var i,j;
                    var data = new Array(len+4);

                    for (i = 0, j = 0; i < len; i += 5, j++){
                        var b0 = buf[j*8+0];
                        var b1 = buf[j*8+1];
                        var b2 = buf[j*8+2];
                        var b3 = buf[j*8+3];
                        var b4 = buf[j*8+4];
                        var b5 = buf[j*8+5];
                        var b6 = buf[j*8+6];
                        var b7 = buf[j*8+7];
                        data[i] = (b0 | ((b1 & 0xF) << 8));
                        data[i + 1] = (((b1 & 0xF0) >> 4) | (b2 << 4));
                        data[i + 2] = (b3 | ((b4 & 0xF) << 8));
                        data[i + 3] = (((b4 & 0xF0) >> 4) | (b5 << 4));
                        data[i + 4] = (b6 | ((b7 & 0xF) << 8));
                    }
                    // convert to signed
                    for (i = 0; i < len; i++){
                        if ((data[i] & 0x800) != 0){    // negative
                            data[i] = data[i] - 0x1000;
                        }
                    }
                    return data;
                }

                function convert_to_complex(data, imag, window, len){
                    for (var i = 0; i < FFT_POINTS; i++){
                        data[i] = data[i] / (1.0 * (1<<(12-1))); // ToDo: use window
                        imag[i] = 0.0;
                    }
                    data.length = imag.length;  // drop trailing data
                }

                function draw_charts(real, imag, scale, sample_freq){
                    var dat = new Array(real.length/2);
                    var s = sample_freq / real.length;
                    var labels = new Array(real.length/2);
                    for (var i = 0; i < real.length/2; i++){
                        real[i] *= scale;
                        imag[i] *= scale;
                        var val = Math.sqrt(real[i]*real[i]+imag[i]*imag[i]);
                        dat[i] = 20.0*Math.log10(val);
                        labels[i] = i*s;
                    }

                    if (chart == null){
                        chart = new Chart(ctx, {
                            type: 'line',
                            data: {
                                labels: labels,
                                datasets: [{
                                    label: 'FFT',
                                    data: dat,
                                    pointRadius: 0.0,
                                    fill: false,
                                    borderColor: "rgba(153,255,51,0.8)",
                                }]
                            },
                            options: {
                                legend: {
                                    display: false,
                                },
                                title: {
                                    display: true,
                                    text: 'FFT',
                                },
                                scales: {
                                    yAxes: [
                                        {
                                            scaleLabel: {
                                                display: true,
                                                labelString: '[dB]'
                                            }
                                        }
                                    ],
                                    xAxes: [
                                        {
                                            scaleLabel: {
                                                display: true,
                                                labelString: 'freq'
                                            }
                                        }
                                        ],
                                },
                                layout: {
                                    padding: {
                                        left: 20,
                                        right: 20,
                                    }
                                },
                                animation: {
                                    duration: 0,
                                },
                                elements: {
                                    line: {
                                        tension: 0, // disable bezier curves
                                    }
                                }
                            }
                        });
                    }else{  // already chart is populated
                        chart.data.datasets[0].data = dat;
                        chart.update();
                    }
                }

                var fft = new FFTNayuki(FFT_POINTS);
                var scale = 1.0 / Math.sqrt(FFT_POINTS);

                $('#start').click(function(){
                    var tm = 0;

                    var start_ms = performance.now();
                    var elapsed_ms;
                    var imag = new Array(FFT_POINTS);
                    var cn = new WebSocket('ws://192.168.0.90:3012/');

                    cn.addEventListener('message', function(ev){
                        var reader = new FileReader();
                        reader.addEventListener("loadend", function(){
                            var ary = new Uint8Array(reader.result);
                            var current = performance.now();

                            var data = decode_values(ary, FFT_POINTS);
                            convert_to_complex(data, imag, null, FFT_POINTS);
                            fft.forward(data, imag);

                            var current2 = performance.now();
                            draw_charts(data, imag, scale, 40.0);

                            var current3 = performance.now();

                            if (tm % 60 == 0){
                                var current = performance.now();
                                elapsed_ms = current - start_ms;
                                $("#time").text(elapsed_ms/60.0 + "[ms]");
                                start_ms = current;
                            }
                            getData();
                        });
                        reader.readAsArrayBuffer(ev.data);
                    });
                    cn.addEventListener('error', function(ev){
                        console.log(ev);
                    });

                    var getData = function(){
                        var abuf = new ArrayBuffer(1);
                        var view = new Uint8Array(abuf);
                        view[0] = tm & 0xFF;
                        cn.send(abuf);
                        tm++;
                    }
                    cn.addEventListener('open', function(){
                        getData();
                    });
                });
            });
        </script>
    </body>
</html>

まだ機能検証レベルのプログラムで、これからいろいろ調整が必要ですが、 とりあえずブラウザで目的が達成できそうだということが分かりました。

RustでWebSocketサーバをクロスコンパイルしてZynqで動かした

以前に、 HaskellでWebSocketのサーバを動作させ、バイナリデータをブラウザで受け取ってCanvasで描画する実験を行いました。

最終的には、ADCのデータをブラウザに送って、ブラウザ上で各種処理を行うことを目論んでいます。

現在はADCのデータは次のように取得しています。

Cで作成したTCPのサーバプログラムをZynq上で動作させ、ADCのデータをmmap等により取得し、TCP経由で送信します。 クライアント側は、C#で作成したプログラムでデータを受信して、各種処理を行っています。

これだと、どうしてもWindows専用のプログラムになってしまいます(Monoを使ってLinuxでも動くのかも知れませんが)。 近頃はかなりの事がブラウザでできるようになっていますし、 そうすればクロスプラットフォームで使えますので、そちらの方が望ましいと思います。

それで上記のような実験を行っていました。 ただ、Haskellでarmv7にクロスコンパイルするのは少し面倒そうでした。

Rustは、かなり簡単にクロスコンパイルが行えるということが分かり、 上記の目的が達成できるか、少しずつ試しています。

Rustでクロスコンパイル

上記の記事に従うことで、簡単にクロスコンパイル環境が構築できました。

次に、前回Haskellで作成したプログラムとほぼ同機能のプログラムを作りました。


extern crate ws;

use ws::{listen, Sender, Handler, Result, Message, CloseCode, Handshake};
use ws::Message::Text;
use ws::Message::Binary;
use ws::util::Token;

struct Server {
    out: Sender,
}

impl Handler for Server {
    fn on_open(&mut self, _shake: Handshake) -> Result<()> {
        println!("on_open");
        Ok(())
    }
    fn on_message(&mut self, msg: Message) -> Result<()> {
        let mut vec: Vec = vec![0; 1920*1080];
        match msg {
            Text(_) => {
                let vec: Vec = vec![0; 1920*1080];
                self.out.send(vec)
            },
            Binary(v) => {
                for j in 0..1080 {
                    for i in 0..1920 {
                        vec[j*1920+i] = (i+j+v[0] as usize) as u8;
                    }
                }
                self.out.send(Binary(vec))
            }
        }
    }
}

fn main() {
   listen("192.168.0.90:3012", |out| Server { out: out }).unwrap();
}

Cargo.tomlには、

[dependencies]
ws = "*"

を追加します。

$ cargo build --target armv7-unknown-linux-gnueabihf --release

とすることで、x86環境のUbuntuでarmv7ターゲットのバイナリができます。

ちなみに、Rustはdebugビルドとreleaseビルドでの実行速度の差がかなり大きいようです。 自分の印象だと、よほどカリカリに数値計算などを行っていなければ、 C/C++などの言語では、体感2,3倍程度の速度差ですが、Rustは10倍位に感じます (あくまで印象です)。

$ cp target/armv7-unknown-linux-gnueabihf/debug/ws-rust /srv/root/

出来上がったバイナリを、NFSマウントされた場所にコピーし、Zynq上で実行しました。

HTMLは少し修正して、次のようにしました。

<html>
    <body>
        <script
            src="https://code.jquery.com/jquery-3.2.1.js"
            integrity="sha256-DZAnKJ/6XZ9si04Hgrsxu/8s717jcIzLy3oi35EouyE="
            crossorigin="anonymous"></script>
        <p><div id="time">Time[ms]</div></p>
        <canvas id="canvas" width="1920" height="1080"></canvas>
        <script type="text/javascript">
            $(window).on('load', function(){
                var tm = 0;
                var canvas = $('#canvas').get(0);
                var width = canvas.width;
                var height = canvas.height;
                var ctx = canvas.getContext('2d');
                var imageData = ctx.getImageData(0, 0, width, height);

                var buf = new ArrayBuffer(imageData.data.length);
                var buf8 = new Uint8ClampedArray(buf);
                var data = new Uint32Array(buf);
                var start_ms = performance.now();
                var elapsed_ms;
                var cn = new WebSocket('ws://192.168.0.90:3012/');

                cn.addEventListener('message', function(ev){
                    var reader = new FileReader();
                    reader.addEventListener("loadend", function(){
                        var ary = new Uint8Array(reader.result);
                        for (var j = 0; j < height; j++){
                            for (var i = 0; i < width; i++){
                                var val = ary[j*width+i];
                                data[j*width+i] = (255<<24)| (val<<16) | (val<<8) | val;
                            }
                        }
                        imageData.data.set(buf8);
                        ctx.putImageData(imageData, 0, 0);
                        if (tm % 60 == 0){
                            var current = performance.now();
                            elapsed_ms = current - start_ms;
                            $("#time").text(elapsed_ms/60.0 + "[ms]");
                            start_ms = current;
                        }
                    });
                    reader.readAsArrayBuffer(ev.data);
                    getData();
                });
                cn.addEventListener('error', function(ev){
                    console.log(ev);
                });

                var getData = function(){
                    var abuf = new ArrayBuffer(1);
                    var view = new Uint8Array(abuf);
                    view[0] = tm & 0xFF;
                    cn.send(abuf);
                    tm++;
                }
                cn.addEventListener('open', function(){
                    getData();
                });
            });
        </script>
    </body>
</html>

こちらをFirefoxで開くと、データ転送が始まって、画像が表示されました。

“動作画面”

200Mbps(25MB/s)程度のデータレートで転送されています。まぁまぁでしょうか。

これでWebSocketのサーバは簡単に作成できることが分かりましたので、 後はmmapやuioの制御のためのreadなどのsyscallがRustで実行できれば、 ADCのデータをブラウザまで送り込むことができそうです。

画像処理回路の基礎(ラインメモリの構成法)

FPGAで各種画像フィルタを実装するというのは良く行われます。 ちょっと検索した範囲では、フィルタに必要な3x3のデータ等を作るのに必要な回路の構成法について、 詳しく記載されているページを見つけることができませんでした。 FPGA使いには当たり前なのかもしれませんが、あまり画像処理回路に詳しくない場合、 その方法が分かりにくいことも考えられます。

それで、参考までに資料を作成しました。

とにかく、Block RAMの使い方とレイテンシ調整がキモになります。

1/7 »