LoginSignup
0
0

電光掲示板を作ろう(PYNQ-Z2シリーズ)

Posted at

ハードとソフト

hub75eとは?

本題に入る前に,今回使用したマトリクスLEDについてざっくり説明します.
hub75はマトリクスLEDのインターフェースの規格で,hub75eはそれに「e」ピンが追加されたものです.hub75では行の指定が「a,b,c,d」ピンの4bitしかないので32行までしか点灯させられません(注: 16行ではない)が,hub75eなら5bitあるので64行表示できます(注: 32行ではない).
以降はhub75eの説明になりますが,行を指定するビットが一つ減るだけで,本質的にはhub75も同じ感じです.
hub75eでは行を指定して一度に二行ずつ光らせます.例えば5'b00000のときは0行目と32行目が,5'b00001のときは1行目と33行目が,,,5'b11111のときは31行目と63行目が光ります.
各列の色はどう指定するかというと,RGBそれぞれで上側の色と下側の色(5'b00000のときは0行目の色と32行目の色)を指定するためのピンがあり,それぞれをhighまたはlowにします.その後クロックを立ち上げると一列分書き込まれ,また次の列のデータを指定してクロックを立ち上げ,,,を繰り返して64列のデータを指定します.
なお,点灯はRGBそれぞれを光らせる・光らせないの8色しかなく,一度に点灯させられるのも2行だけです.素早く色や行を切り替えてPWM表示・ダイナミック点灯することで,マトリクス全体にいろいろな色が表示されているように見せます.

目標

  1. PYNQ-Z2のSDカードに画像データをのせよう
  2. 画像データをPSで読み取ろう
  3. 読み取った画像データをPLへ送ろう
  4. 画像データをマトリクスLEDへ送ろう

PYNQ-Z2のSDカードに画像データをのせよう

使用したマトリクスLEDが64x64なので,Pythonを使って正方形の画像を64x64に整形しました.
表示画像はこちら.

上記GIF画像をkana.gifという名前でダウンロードし,画像一枚ずつに分け,64x64にしました.

import cv2

SIZE = 64

# GIF画像読み込み
cap = cv2.VideoCapture("kana.gif")

# 一枚ずつ分けて保存
i = 0
while (cap.isOpened()):
    ret, frame = cap.read()
    if ret:
        # 正方形にする(縦長画像なので高さを横幅に合わせた)
        frame = frame[0:490, :, :]
        # 64x64にする
        frame = cv2.resize(frame, (SIZE, SIZE))
        # 保存
        cv2.imwrite(f"kana_img/{i:04d}.png", frame)
        i += 1

こうして作成した画像をJupyter Notebook経由でSDカードに保存しました.

画像データをPSで読み取ろう

少し後の話になるのですが,PYNQ-Z2のDMAをPythonでやろうとすると,pynqライブラリのバージョンが古くうまくいきませんでした.アップグレードも上手くいかなかったので潔く(?)C++で挑むことにしました.
PYNQ-Z2にC++のOpenCVをインストールしました.「linux opencv c++」とかで調べたらインストール方法が出てくると思うので,ここでは割愛.
余談ですが,PYNQ-Z2についての情報を調べようとしてもほとんどヒットしません.しかしlinuxでの方法を調べると,PYNQ-Z2に応用できそうな情報がたくさんヒットします.
これで画像データが読み込めるようになりました.実際のプログラムは次の章でやります.

読み取った画像データをPLへ送ろう

次にpynq_apiライブラリをインストールしました.基本的にはGithubのreadme.mdに従えばインストールできます.一つ注意点として,readmeにはリンカ指定は-lpynq_apiと書かれていますが,正しくは-lpynqです.

PSから送信

PSからPLへはDMAを使ってデータを送ります.ちなみにDMAはその逆,PLからPSへの送信もできますが今回は使いません.
DMAでは一度に送れるデータ量に制限があります.バースト転送することでたくさんのデータを送ります.
ここでは一度に64ビットの情報を送ります.
また,一画素あたり4ビットRGBで表現することにしたので,一画像あたり64(マトリクスLEDの行数)x64(マトリクスLEDの列数)x4(一画素のビット数)x3(RGB)=49152bit=6144Byteあります.一色当たり2048Byteです.
バースト長は256になります.

send_img_data.cpp
#include <iostream>
#include <opencv2/opencv.hpp>

#define SUCCESS          (0)
#define IMG_READ_FAIL   (-1)
#define IMG_SIZE        (64)
#define BURST_SIZE     (256)

// 一枚の画像情報をPSからPLへ送信する関数
int send_img_data(
    PYNQ_AXI_DMA &dma,
    PYNQ_SHARED_MEMORY &memory,
    unsigned long long *data,
    const char *img_path
) {
    // 画像の読み込み
    cv::Mat img;
    try {
        img = cv::imread(img_path, -1);
        if (img.empty()) throw IMG_READ_FAIL;
    }
    catch (int e) {
        return e;
    }

    // 画像データをバッファに詰めて送信する
    cv::Vec3b pixel;
    unsigned char place = 0;
    int shift_num = 0;
    // RGBの三回ループ
    for (int c = 0; c < 3; c++) {
        // 初期化(たぶんなくてもいい)
        for (int i = 0; i < BURST_SIZE; i++) {
            data[i] = 0L;
        }
        // 二重ループで一画素ずつループ
        for (int i = 0; i < IMG_SIZE; i++) {
            for (int j = 0; j < IMG_SIZE; j++) {
                pixel = img.at<cv::Vec3b>(i, j);

                // 一画素分の情報8bitの上位4bitを取得
                data[place] |= (((unsigned long long)(pixel[c]) >> 4)) << shift_num;
                shift_num += 4;

                // 64ビット埋まったら次の要素へ
                if (shift_num == 64) {
                    place++;
                    shift_num = 0;
                }
            }
        }

        // DMAで送信
        PYNQ_writeDMA(&dma, &memory, 0, sizeof(long long) * BURST_SIZE);
        PYNQ_waitForDMAComplete(&dma, AXI_DMA_WRITE);
    }

    return SUCCESS;
}

メイン関数はこちら.
ちなみにDMAのアドレスはブロックデザインの下記画像から調べられます.

image.png

kana.cpp
#include <iostream>
#include <string>
#include <sstream>
#include <chrono>
#include <thread>
#include <opencv2/opencv.hpp>
extern "C" {
#include <pynq_api.h>
}

#include "send_img_data.h"

using namespace std::chrono;

int main(void) {
    std::cout << "start" << std::endl;
    char bit_path[] = "./bit/ps2pl2hub_nc.bit";
    const int ADDR = 0x40400000;
    int rtn;

    // ビットファイルの準備
    PYNQ_loadBitstream(bit_path);

    // DMAの準備
    PYNQ_SHARED_MEMORY memory;
    PYNQ_allocatedSharedMemory(&memory, sizeof(long long) * BURST_SIZE, 1);

    PYNQ_AXI_DMA dma;
    PYNQ_openDMA(&dma, ADDR);

    unsigned long long *data = (unsigned long long *)memory.pointer;

    // 4回ループ(GIF画像の繰り返し数)
    for (int i = 0; i < 4; i++) {
        // 34回ループ(GIF画像の画像数)
        for (int j = 0; j < 34; j++) {
            // 開始時刻取得
            auto start = system_clock::now();

            // ファイル名作成
            std::stringstream ss;
            ss << std::setw(4) << std::setfill('0') << j;
            std::string file_name = "./img_kana/" + ss.str() + ".png";

            // 画像データを送る
            rtn = send_img_data(
                dma, memory, data,
                file_name.c_str()
            );
            if (rtn != SUCCESS) {
                std::cout << i << " " << j << " error" << std::endl;
                break;
            }

            // 70ms経つまで待つ
            while (
                duration_cast<milliseconds>(system_clock::now() - start).count() % 1000 < 70
            );
        }
    }

    // 真っ黒画像を送る
    rtn = send_img_data(
        dma, memory, data,
        "./img/black.png"
    );
    if (rtn != SUCCESS) {
        std::cout << "black error" << std::endl;
    }
    else {
        std::cout << "black send" << std::endl;
    }

    // DMAを閉じる
    PYNQ_closeDMA(&dma);
    PYNQ_freeSharedMemory(&memory);

    std::cout << "end" << std::endl;

    return 0;
}

ちなみにコンパイルコマンドはこんな感じです.OpenCVとpynq_apiを使うためにかなり長くなっています.

g++ -o kana kana.cpp -I /usr/local/include/opencv4 -l opencv_core -l opencv_objdetect -l opencv_highgui -l opencv_imgproc -l opencv_videoio -l opencv_imgcodecs -lpynq -lcma -lpthread

PLで受信

ここからはHDLになります.
その前に,全体像を張ります.

image.png

axi_dmaの設定

右クリック-> Add IPからAXI Direct Memory Accessをブロックデザインに追加します.
追加直後は以下のような見た目です.

image.png

設定画面を開いて,以下のように設定します.

image.png

ここで,それぞれの言葉の意味は以下のような感じです.

  • Enable Scatter Gather Engine: DMAの複雑な機能が使えるらしいです.よく知りません.「scatter gather」でググると情報が出てきます.
  • Enabe Micro DMA: これをオンにすると下のAllow Unallgned Transfersにチェックを入れられなくなります.
  • Width of Buffer length Register: バッファの位置を指定するレジスタの幅.バッファが32bitのとき,このレジスタは5bit以上必要.
  • Address Width: 送受信するデータのアドレス幅っぽい.
  • Enable Read Channel: そのまま,受信チャンネルを有効化するかどうか.ここでいう「受信」はPL目線なので,PS->PL通信を有効にするか.
    • Memory Map Data Width: このIPがPSと通信するときのデータ幅.
    • Stream Data Width: このIPがPLと通信するときのデータ幅.
    • Max Burst Size: そのまま.最大バースト長.
  • Enable Write Channel: そのまま.PL->PS通信を有効にするか.

あとはZYNQをダブルクリックして設定画面を開いて以下の設定(HP Slave AXI Interfaceのところ)をしてAuto ~~をやれば配線できます.

image.png

受信部はaxi_dma_0で,受け取った画像をimg_memory_wrapper_0で保存しています.その中身はこちら(Qiitaではコードの色が変わらなかったので拡張子を「.v」としていますが,実際は「.sv」です).

img_memory.v
module img_memory_0(
    input clk,
    input resetn,

    input [3:0] btn,    // デバッグ用なので関係ない
    input [1:0] sw,     // デバッグ用なので関係ない
    output [7:0] ar,    // デバッグ用なので関係ない

    // s axis
    input [63:0] tdata,
    input tlast,
    input tvalid,
    output reg tready,

    input [4:0] row,                // 表示する行
    output [64 * 4 - 1:0] red_f,    // 赤の上側
    output [64 * 4 - 1:0] red_s,    // 赤の下側
    output [64 * 4 - 1:0] green_f,
    output [64 * 4 - 1:0] green_s,
    output [64 * 4 - 1:0] blue_f,
    output [64 * 4 - 1:0] blue_s
    );

    // 受信状態
    typedef enum logic [2:0] {
        IDLE,
        BLUE,
        GREEN,
        RED
    } state_t;

    reg [2:0][255:0][63:0] full_data;  // 画像データ(RGBバースト長送られてくる1データの三次元配列)
    reg [7:0] itr = 8'hff;

    state_t state = IDLE;

    always @(posedge clk) begin
        // 初期化(リセット状態)
        if (!resetn) begin
            tready <= 1'b0;

            for (logic [1:0] i = 0; i < 3; i++) begin
                for (logic [8:0] j = 0; j < 256; j++) begin
                    full_data[i][j] <= 64'b0;
                end
            end
            itr <= 8'hff;

            state <= IDLE;
        // 通常動作
        end else begin
            case (state)
                // 待機(データが送信されてくるのを待つ)
                IDLE: begin
                    tready <= 1'b0;
                    if (tvalid) begin
                        state <= BLUE;
                        itr <= 8'hff;
                    end
                end

                // 青色を取得
                BLUE: begin
                    if (tvalid) begin
                        tready <= 1'b1;
                        full_data[0][itr] <= tdata;
                        if (tlast) begin
                            state <= GREEN;
                            itr <= 8'hff;
                        end else begin
                            itr <= itr + 8'b1;
                        end
                    end else begin
                        tready <= 1'b0;
                    end
                end

                // 緑色を取得
                GREEN: begin
                    if (tvalid) begin
                        tready <= 1'b1;
                        full_data[1][itr] <= tdata;
                        if (tlast) begin
                            state <= RED;
                            itr <= 8'hff;
                        end else begin
                            itr <= itr + 8'b1;
                        end
                    end else begin
                        tready <= 1'b0;
                    end
                end

                // 赤色を取得
                RED: begin
                    if (tvalid) begin
                        tready <= 1'b1;
                        full_data[2][itr] <= tdata;
                        if (tlast) begin
                            state <= IDLE;
                            itr <= 8'hff;
                        end else begin
                            itr <= itr + 8'b1;
                        end
                    end else begin
                        tready <= 1'b0;
                    end
                end
            endcase
        end
    end

    // 指定された行のデータを出力(4要素で一行分のデータになる)
    assign blue_f  = {full_data[0][{1'b0, row, 2'b11}], full_data[0][{1'b0, row, 2'b10}], full_data[0][{1'b0, row, 2'b01}], full_data[0][{1'b0, row, 2'b00}]};
    assign blue_s  = {full_data[0][{1'b1, row, 2'b11}], full_data[0][{1'b1, row, 2'b10}], full_data[0][{1'b1, row, 2'b01}], full_data[0][{1'b1, row, 2'b00}]};
    assign green_f = {full_data[1][{1'b0, row, 2'b11}], full_data[1][{1'b0, row, 2'b10}], full_data[1][{1'b0, row, 2'b01}], full_data[1][{1'b0, row, 2'b00}]};
    assign green_s = {full_data[1][{1'b1, row, 2'b11}], full_data[1][{1'b1, row, 2'b10}], full_data[1][{1'b1, row, 2'b01}], full_data[1][{1'b1, row, 2'b00}]};
    assign red_f   = {full_data[2][{1'b0, row, 2'b11}], full_data[2][{1'b0, row, 2'b10}], full_data[2][{1'b0, row, 2'b01}], full_data[2][{1'b0, row, 2'b00}]};
    assign red_s   = {full_data[2][{1'b1, row, 2'b11}], full_data[2][{1'b1, row, 2'b10}], full_data[2][{1'b1, row, 2'b01}], full_data[2][{1'b1, row, 2'b00}]};

    // デバッグ用なので関係ない
    assign ar = sw != 2'b11 ? full_data[sw][btn][7:0] : 8'b0;

endmodule

画像データをマトリクスLEDへ送ろう

これまでのところで,PYNQ-Z2のSDカードに保存されている画像データをPL上のレジスタに展開できたので,今度はそのデータをマトリクスLEDに送信します.保存されている画像データを絶えず送信し続けます.
例によって,実際の拡張子は「.sv」です.

hub_driver.v
module hub_driver(
    input clk,
    input resetn,

    // 一行分のデータ(上側)
    input [64 * 4 - 1:0] blue_in_f,
    input [64 * 4 - 1:0] green_in_f,
    input [64 * 4 - 1:0] red_in_f,

    // 一行分のデータ(下側)
    input [64 * 4 - 1:0] blue_in_s,
    input [64 * 4 - 1:0] green_in_s,
    input [64 * 4 - 1:0] red_in_s,

    // hub75eへ送信するデータ
    output reg [4:0] row,   // hub75eへ送るとともにimg_memoryモジュールに行を指定している
    output reg [1:0] red,
    output reg [1:0] green,
    output reg [1:0] blue,
    output reg lcl, // led clk
    output reg lat,
    output reg oe
    );

    typedef enum logic [1:0] {
        IDLE,
        SEND_ROW,
        LAT_HIGH,
        LAT_LOW
    } state_t;

    // hub75eへ送信するデータのクロック周波数
    localparam integer FREQ = 8_000_000 /* Hz */;
    localparam integer HALF_CYCLE = 100_000_000 / FREQ / 2;

    state_t state = IDLE;

    reg [31:0] cnt = 32'b1;

    reg carry_flag = 1'b0;
    reg [5:0] col = 6'b1;   // 
    reg [3:0] step = 4'b0;  // 明るさを指定するための変数

    // それぞれ一行分のデータ
    reg [64 * 4 - 1:0] red_color_f = 256'b0;
    reg [64 * 4 - 1:0] blue_color_f = 256'b0;
    reg [64 * 4 - 1:0] green_color_f = 256'b0;
    reg [64 * 4 - 1:0] red_color_s = 256'b0;
    reg [64 * 4 - 1:0] blue_color_s = 256'b0;
    reg [64 * 4 - 1:0] green_color_s = 256'b0;

    always_ff @(posedge clk) begin
        if (!resetn) begin
            row <= 5'd0;
            red <= 2'b0;
            green <= 2'b0;
            blue <= 2'b0;
            lcl <= 1'b0;
            lat <= 1'b1;
            oe <= 1'b1;

            state <= IDLE;

            cnt <= 32'b1;

            carry_flag <= 1'b0;
            col <= 6'b1;
            step <= 4'b0;

            red_color_f <= 256'b0;
            blue_color_f <= 256'b0;
            green_color_f <= 256'b0;
            red_color_s <= 256'b0;
            blue_color_s <= 256'b0;
            green_color_s <= 256'b0;
        end else begin
            case (state)
                // 初期化
                IDLE: begin
                    lat <= 1'b0;
                    oe <= 1'b0;
                    state <= SEND_ROW;
                    step <= step + 4'b1;
                    frame_cnt <= 32'b1;
                    red_color_f <= red_in_f;
                    green_color_f <= green_in_f;
                    blue_color_f <= blue_in_f;
                    red_color_s <= red_in_s;
                    green_color_s <= green_in_s;
                    blue_color_s <= blue_in_s;
                end

                SEND_ROW: begin
                    if (cnt == HALF_CYCLE) begin
                        lcl <= !lcl;
                        cnt <= 32'b1;
                        // 立下りのタイミングで送信データを更新する
                        if (lcl) begin
                            // 4bitのデータがstepを超えているかどうかで出力をhighにするかどうか決める
                            // stepはインクリメントされていくので明るい画素ほど点灯される時間が長い
                            red <= {red_color_s[3:0] > step, red_color_f[3:0] > step};
                            green <= {green_color_s[3:0] > step, green_color_f[3:0] > step};
                            blue <= {blue_color_s[3:0] > step, blue_color_f[3:0] > step};
                            // 次の画素データを最下位ビットに移動させる
                            red_color_f   <= {4'b0, red_color_f[64 * 4 - 1:4]};
                            green_color_f <= {4'b0, green_color_f[64 * 4 - 1:4]};
                            blue_color_f  <= {4'b0, blue_color_f[64 * 4 - 1:4]};
                            red_color_s   <= {4'b0, red_color_s[64 * 4 - 1:4]};
                            green_color_s <= {4'b0, green_color_s[64 * 4 - 1:4]};
                            blue_color_s  <= {4'b0, blue_color_s[64 * 4 - 1:4]};
                            {carry_flag, col} <= col + 7'b1;
                            // 64列送り終えたら状態更新
                            if (carry_flag) begin
                                state <= LAT_HIGH;
                                lat <= 1'b1;
                                oe <= 1'b1;
                            end
                        end
                    end else begin
                        cnt <= cnt + 32'b1;
                    end
                end

                // 以下点灯処理と次の行の表示準備
                LAT_HIGH: begin
                    if (cnt == HALF_CYCLE) begin
                        cnt <= 32'b1;
                        lat <= 1'b0;
                        oe <= 1'b0;
                        state <= LAT_LOW;
                        red_color_f <= red_in_f;
                        green_color_f <= green_in_f;
                        blue_color_f <= blue_in_f;
                        red_color_s <= red_in_s;
                        green_color_s <= green_in_s;
                        blue_color_s <= blue_in_s;
                    end else begin
                        cnt <= cnt + 32'b1;
                    end
                end

                LAT_LOW: begin
                    if (cnt == HALF_CYCLE) begin
                        cnt <= 32'b1;
                        row <= row + 5'b1;
                        // 画像一枚分を送り終えたらstepを増やしもう一度送信
                        if (row == 5'd31) begin
                            state <= IDLE;
                        end else begin
                            state <= SEND_ROW;
                        end
                    end else begin
                        cnt <= cnt + 32'b1;
                    end
                end
            endcase
        end
    end

endmodule

その他

System Verilogはブロックデザインに追加できないので,Verilog HDLでラッパーを作っています.

img_memory_wrapper.v
module img_memory_wrapper(
    input clk,
    input resetn,
    
    input [3:0] btn,    // デバッグ用なので関係ない
    input [1:0] sw,     // デバッグ用なので関係ない
    output [7:0] ar,    // デバッグ用なので関係ない

    // s axis
    input [63:0] tdata,
    input tlast,
    input tvalid,
    output tready,

    input [4:0] row,
    output [64 * 4 - 1:0] red_f,
    output [64 * 4 - 1:0] red_s,
    output [64 * 4 - 1:0] green_f,
    output [64 * 4 - 1:0] green_s,
    output [64 * 4 - 1:0] blue_f,
    output [64 * 4 - 1:0] blue_s
    );

    img_memory_0 img_memory (
        .clk(clk), .resetn(resetn),
        .btn(btn), .sw(sw), .ar(ar),
        .tdata(tdata), .tlast(tlast), .tvalid(tvalid), .tready(tready),
        .row(row + 2),  // 次の行のデータが欲しいのでここで調整
        .red_f(red_f), .red_s(red_s),
        .green_f(green_f), .green_s(green_s),
        .blue_f(blue_f), .blue_s(blue_s)
    );

endmodule
hub_driver_wrapper.v
module hub_driver_wrapper(
    input clk,
    input resetn,
    
    input [64 * 4 - 1:0] red_in_f,
    input [64 * 4 - 1:0] red_in_s,
    input [64 * 4 - 1:0] green_in_f,
    input [64 * 4 - 1:0] green_in_s,
    input [64 * 4 - 1:0] blue_in_f,
    input [64 * 4 - 1:0] blue_in_s,

    output [4:0] row,
    output [1:0] red,
    output [1:0] green,
    output [1:0] blue,
    output lcl, // led clk
    output lat,
    output oe,
    output gnd
    );

    assign gnd = 1'b0;

    hub_driver hub_driver_0 (
        .clk(clk), .resetn(resetn),
        .blue_in_f(blue_in_f), .green_in_f(green_in_f), .red_in_f(red_in_f),
        .blue_in_s(blue_in_s), .green_in_s(green_in_s), .red_in_s(red_in_s),
        .row(row),
        .red(red), .green(green), .blue(blue),
        .lcl(lcl), .lat(lat), .oe(oe)
    );

endmodule

あとXDC.

top.xdc
##PmodA

set_property -dict { PACKAGE_PIN Y18   IOSTANDARD LVCMOS33 } [get_ports { red[0] }]; #IO_L17P_T2_34 Sch=ja_p[1]
set_property -dict { PACKAGE_PIN Y19   IOSTANDARD LVCMOS33 } [get_ports { blue[0] }]; #IO_L17N_T2_34 Sch=ja_n[1]
set_property -dict { PACKAGE_PIN Y16   IOSTANDARD LVCMOS33 } [get_ports { red[1] }]; #IO_L7P_T1_34 Sch=ja_p[2]
set_property -dict { PACKAGE_PIN Y17   IOSTANDARD LVCMOS33 } [get_ports { blue[1] }]; #IO_L7N_T1_34 Sch=ja_n[2]
set_property -dict { PACKAGE_PIN U18   IOSTANDARD LVCMOS33 } [get_ports { green[0] }]; #IO_L12P_T1_MRCC_34 Sch=ja_p[3]
set_property -dict { PACKAGE_PIN U19   IOSTANDARD LVCMOS33 } [get_ports { gnd }]; #IO_L12N_T1_MRCC_34 Sch=ja_n[3]
set_property -dict { PACKAGE_PIN W18   IOSTANDARD LVCMOS33 } [get_ports { green[1] }]; #IO_L22P_T3_34 Sch=ja_p[4]
set_property -dict { PACKAGE_PIN W19   IOSTANDARD LVCMOS33 } [get_ports { row[4] }]; #IO_L22N_T3_34 Sch=ja_n[4]

##PmodB

set_property -dict { PACKAGE_PIN W14   IOSTANDARD LVCMOS33 } [get_ports { row[0] }]; #IO_L8P_T1_34 Sch=jb_p[1]
set_property -dict { PACKAGE_PIN Y14   IOSTANDARD LVCMOS33 } [get_ports { row[2] }]; #IO_L8N_T1_34 Sch=jb_n[1]
set_property -dict { PACKAGE_PIN T11   IOSTANDARD LVCMOS33 } [get_ports { lcl }]; #IO_L1P_T0_34 Sch=jb_p[2]
set_property -dict { PACKAGE_PIN T10   IOSTANDARD LVCMOS33 } [get_ports { oe }]; #IO_L1N_T0_34 Sch=jb_n[2]
set_property -dict { PACKAGE_PIN V16   IOSTANDARD LVCMOS33 } [get_ports { row[1] }]; #IO_L18P_T2_34 Sch=jb_p[3]
set_property -dict { PACKAGE_PIN W16   IOSTANDARD LVCMOS33 } [get_ports { row[3] }]; #IO_L18N_T2_34 Sch=jb_n[3]
set_property -dict { PACKAGE_PIN V12   IOSTANDARD LVCMOS33 } [get_ports { lat }]; #IO_L4P_T0_34 Sch=jb_p[4]
set_property -dict { PACKAGE_PIN W13   IOSTANDARD LVCMOS33 } [get_ports { gnd }]; #IO_L4N_T0_34 Sch=jb_n[4]

完成品

実はランダムでPS->PLのデータ送受信に失敗するのでたまに表示が乱れます.
おそらく回路のどこかに非効率な部分があると思うのですが,心折れたのでこの辺で完成としました.

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0