【改訂2版】FPGAボードで学ぶ 組込みシステム開発入門[Intel FPGA編]の第7章に載っているVGA文字表示回路をハードウェア記述言語のNSLで作ってみよう。
1. 回路について
DE1-SoCにはVGA端子(Wikipedia)がある
- 同期信号に合わせて走査線単位でRGBの出力をする。
- 信号範囲は
800 x 525だが、外側に非表示領域を含む- 実際に表示される大きさは
640 x 480
- 実際に表示される大きさは
- 画面を横のラインで分ける
- 最初は一番上のラインの左から右方向に画素値の信号を送る
- 右までたどり着いたら、一つ下の次のラインの一番左から右、次のラインの左から右といった順番。
- 下記のイメージ図参照
グラフィック表示するには、FPGA内蔵のメモリが足りないので文字を表示する。
- 外付けでSDRAMがFPGAボードがあるみたいだが、それはまた次回
方針
- 1文字の大きさは
8 x 8 pxでこれを 横80 x 縦50 にならべて表示する。- つまり
640 x 400の領域に4000字並べる。 - 縦
480なので上下40pxは何も表示しない。1
- つまり
- 表示パターンを書き込んだROM(以下CGROM)から読込んで、文字を表示する。
- 表示する文字や色は 32bit x 4096word のメモリ(以下VRAM) 格納する。
- VRAMには表示する文字の色と文字コードを記憶する。
- CGROMではASCIIコードに対応する文字を表示するためのパターンを格納しておく。
- 表示する際はこのうちの1ラインを読込み、シフトレジスタで各bitを取出す
- 値が1のbitと対応するピクセルにVRAMから取出した色を出力する。
例によってハードウェアとソフトウェア、そしてハードウェアではモジュール作成とNiosIIのシステム構築にわかれる。
ハードウェア
モジュール作成
syncgen
- 水平と垂直の同期信号2つとPCKを生成する。
- PCKはシステムクロック(m_clock:50MHz)を2分周(25MHz)して生成する。
- テキストではこれをクロック入力用い、動かしていた。
- しかし、PLLを使ってない論理信号をクロック入力にするのはまずいので、その方式をやめ、イネーブル信号を追加し、その信号による制御にする。
- 同期信号生成に使うのは、水平用の800進カウンタと垂直用の525進カウンタで、共に0の時は左上を示す。
- カウンタの値に合わせて、同期信号を生み出したり、文字の読み込みするアドレスを決めたりする。
- PCKはシステムクロック(m_clock:50MHz)を2分周(25MHz)して生成する。
VRAM
- Avalonバスの入出力とVGA出力を同時に行えるようにデュアルポートのRAMにする。
- 容量は
32bit(4Byte) x 4096word= 16KByte - こちらはNSLのコーディングではなくIP Catalog で作成する
CGROM
- 文字に対応する表示パターンを格納するROM
- VRAMと同じくIP Catalog で作成する
- 作成時にパターンのデータをファイルで与える。
vgaif_ip
- 上記3モジュールを内蔵する。
- AvalonバスからCPUから送られてきた文字情報をVRAMに格納する。
- syncgenが出力する水平・垂直同期信号用のカウンタの値に合わせ、VGA出力の制御を行う。
- 表示する文字の情報を読み込む
- ラインに合わせた表示パターンをCGROMから読み込む
- シフトレジスタつかってbitと対応するピクセルを出力する。
NiosII システム
- 以下のコンポーネントを使用する
- NiosII CPU
- On Chip Memory(8KB)
- JTAG UART
- System ID
- vgaif_ip
最上位階層
- NiosIIシステムとFPGAボードのVGA端子を接続する。
ソフトウェア
- 文字列と色の情報をVRAMに書き込むプログラムを作る
- bitフィールド構造体、共用体の定義
- 表示する文字、色の設定
- VRAMに書込む
2.作ってみよう
1.でまとめたものをそれぞれ作成していく。
- syncgen
- VRAM
- CGROM
- vgaif_ip
- NiosII システム
- 最上位階層
- ソフトウェア
2.1. syncgenの作成
syncgenでは以下のことを行う
- PCKの生成
- 垂直・水平同期カウンタ及び同期信号の生成。
2.1.1. PCKの生成。
PCKは論理信号でシステムクロック(50MHz)半分の周期(25MHz)に分周する。
また、別の所でこの周期で動く部分があるので、立ち上がりの瞬間に動くように制御信号を出しておく。
// システムクロックを2分周してPCKを生成
PCK_r := ~PCK_r;
if (~PCK_r) {
PCK_posedge();
}
2.1.2. 水平・垂直同期カウンタ及び同期信号の生成。
準備として、タイミング作成用の値をまとめたファイルを用意しておく。
- このファイルを別途
#includeしておく。
#define HPERIOD 10'd800
#define HFRONT 10'd16
#define HWIDTH 10'd96
#define HBACK 10'd48
#define VPERIOD 10'd525
#define VFRONT 10'd10
#define VWIDTH 10'd2
#define VBACK 10'd33
syncgenの立ち上がるタイミングで水平同期カウンタをカウントアップする。
水平同期カウンタが1周するタイミングで、垂直同期カウンタがカウントアップする。
hcntend = (HCNT_r==HPERIOD-10'h001);
if (~PCK_r) {
PCK_posedge();
if ( hcntend ){
HCNT_r := 10'h000;
if ( VCNT_r == VPERIOD - 10'h001 ){
VCNT_r := 10'h000;
}else{
VCNT_r := VCNT_r + 10'h001;
}
}else{
HCNT_r := HCNT_r + 10'h001;
}
}
同期信号のタイミングをまとめた定数を記述。
hsstart = HFRONT - 10'h001;
hsend = HFRONT + HWIDTH - 10'h001;
vsstart = VFRONT;
vsend = VFRONT + VWIDTH;
定数の値と水平・垂直同期カウンタの値から同期信号の出力タイミングを記述する。
any {
( HCNT_r == hsstart ):{
VGA_HS_r := 1'b0;
if ( VCNT_r==vsstart )
VGA_VS_r := 1'b0;
else if ( VCNT_r==vsend )
VGA_VS_r := 1'b1;
}
( HCNT_r==hsend ):{
VGA_HS_r := 1'b1;
}
}
syncgen NSLファイル
ヘッダファイル
#ifndef _SYNCGEN_H
#define _SYNCGEN_H
#include "vga_param.nsh"
declare syncgen {
output PCK;
output VGA_HS;
output VGA_VS;
output HCNT[10];
output VCNT[10];
func_out PCK_posedge();
}
#endif // _SYNCGEN_H
モジュール
#include "syncgen.nsh"
module syncgen {
reg PCK_r = 0;
reg VGA_HS_r = 1;
reg VGA_VS_r = 1;
reg HCNT_r[10] = 0;
reg VCNT_r[10] = 0;
/* 水平カウンタ */
wire hcntend;
/* 同期信号 */
wire hsstart[10];
wire hsend[10];
wire vsstart[10];
wire vsend[10];
/* システムクロックを2分周してPCKを生成 */
PCK_r := ~PCK_r;
/* 水平カウンタ */
hcntend = (HCNT_r==HPERIOD-10'h001);
/* 同期信号 */
hsstart = HFRONT - 10'h001;
hsend = HFRONT + HWIDTH - 10'h001;
vsstart = VFRONT;
vsend = VFRONT + VWIDTH;
if (~PCK_r) {
PCK_posedge();
if ( hcntend ){
HCNT_r := 10'h000;
if ( VCNT_r == VPERIOD - 10'h001 ){
VCNT_r := 10'h000;
}else{
VCNT_r := VCNT_r + 10'h001;
}
}else{
HCNT_r := HCNT_r + 10'h001;
}
any {
( HCNT_r == hsstart ):{
VGA_HS_r := 1'b0;
if ( VCNT_r==vsstart )
VGA_VS_r := 1'b0;
else if ( VCNT_r==vsend )
VGA_VS_r := 1'b1;
}
( HCNT_r==hsend ):{
VGA_HS_r := 1'b1;
}
}
}
PCK = PCK_r;
VGA_HS = VGA_HS_r;
VGA_VS = VGA_VS_r;
HCNT = HCNT_r;
VCNT = VCNT_r;
}
2.2. VRAMの作成
準備としてプロジェクトフォルダ内にROMRAMフォルダを作成し、その中に作成する
- CGROMも同フォルダ内に作成する。
IP Catalog で内蔵メモリを作成する場合、FPGAボードによって作成手順が異なる。
- 今回は Cyclone V 準拠のものを作成する。
- その他、IP Catalogの詳しい説明はテキスト参照
Quartus Prime の IP Catalog > Library > Basic Functions > On Chip Memory > RAM: 2-PORT を選ぶ。
出てくるウィンドウに従って以下の設定を行う
- 名前を
VRAMにし、場所はROMRAMフォルダ内直下にする。 - file typeは
Verilogにする。 - 独立して2系統の読み書きができるタイプ
With two read/write portsを選ぶ。 - bit幅
32,ワード数4096と設定する - クロックはA,B別系統(
Dual clock: use sseparate clocks for A and B ports)で、byteenableのポート(Byte Enable Ports)は A (Create byte enable port A)のみ選ぶ。 - 出力のレジスタ
Read output port(s)のチェックは外す - その他の設定はデフォルト。
finishを選択すると、qipファイルを追加するかどうか確認されるのでYesを選択
2.3. CGROMの作成
ROMは初期化時に値を設定する必要があり、今回はmifファイルを用いる
-
テキストのサポートページからダウンロードできるファイルを使用した。
-
CGDATA.mifをCGROMフォルダにコピーしておく。
-
ダウンロードしたファイルでは以下のような設定がされている。
DEPTH = 1024;
WIDTH = 8;
ADDRESS_RADIX = HEX;
DATA_RADIX = BIN;
CONTENT
BEGIN
-- 00H
000: 00111110;
...
3ff: 11111111;
END;
BEGIN-END間で、ROMのデータをaddr: data;の用に定義するらしい。
- 例えば
Aの表示パターンは以下のようになっている。
208: 00011100;
209: 00100010;
20a: 00100010;
20b: 00100010;
20c: 00111110;
20d: 00100010;
20e: 00100010;
20f: 00000000;
見やすいように表形式にしたものを以下に示す。
1になっている箇所をよくみると、Aと表示されているように見えるだろうか。
定義する時の記数法(radix)はこのサイトによると
you can specify data radixes as binary (BIN), hexadecimal (HEX), octal (OCT), signed decimal (DEC), or unsigned decimal (UNS)
とあり、2,8,16進と、10進の符号有り無しを選べるようだ。
- 今回の場合、アドレスは16進でデータは2進にして、表示パターンを見たまんまに定義できるようにしている様子
以降はVRAMの時と同じような手順でROMを作成する。
Quartus Prime の IP Catalog > Library > Basic Functions > On Chip Memory > ROM: 1-PORT を選ぶ。
出てくるウィンドウに従って以下の設定を行う
- 名前を
CGROMにし、場所はROMRAMフォルダ内直下にする。 - file typeは
Verilogにする。 - bit幅
8,ワード数1024と設定する - 出力のレジスタ
'q' output portのチェックは外す - メモリの初期化ファイルに
CGDATA.mifを設定しておく - その他の設定はデフォルト。
finishを選択すると、qipファイルを追加するかどうか確認されるのでYesを選択
2.4. vgaif_ipの作成
前項までで作成したモジュールをまとめ上げて、VGA出力を生成する。
- 各モジュールの接続
- AvalonバスとVRAMの接続
- 文字情報の読込み
メモリマップ
以下の表のように文字情報を扱う。
- 表示する文字の色 (R,G,B)
- 表示する文字のASCIIコード(CharCode)
記述ファイル
作成したNSLファイルを以下に示す。(長いので折りたたみ)
vgaif_ip
ヘッダファイル
#ifndef _VGAIF_IP_NSH
#define _VGAIF_IP_NSH
#include "vga_param.nsh"
#include "syncgen.nsh"
declare vgaif_ip interface {
input clk;
input reset;
input address[12];
input write;
input read;
input byteenable[4];
input writedata[32];
output readdata[32];
/* VGA出力 */
output VGA_CLK;
output VGA_R[4];
output VGA_G[4];
output VGA_B[4];
output VGA_HS, VGA_VS;
output VGA_BLANK_N;
}
declare CGROM interface {
input address[10];
input clock;
output q[8];
}
declare VRAM interface {
input address_a[12];
input address_b[12];
input byteena_a[4];
input clock_a;
input clock_b;
input data_a[32];
input data_b[32];
input wren_a;
input wren_b;
output q_a[32];
output q_b[32];
}
#endif // _VGAIF_IP_NSH
モジュール
#include "vgaif_ip.nsh"
module vgaif_ip {
syncgen snc;
VRAM vram;
CGROM cgrom;
// syncgen接続
wire HCNT[10];
wire VCNT[10];
wire PCK;
// VRAM接続信号
wire vramout[32];
wire vramaddr[12];
// CGROM接続
wire vdotcnt[3];
wire cgout[8];
// HCNTとVCNTを文字とドットのカウンタとして分けて考える
wire hchacnt[7]; // 水平文字カウンタ
wire hdotcnt[3]; // 水平ドットカウンタ
wire vchacnt[6]; // 垂直文字カウンタ
// 内部での参照用にカウント値を変換
wire iHCNT[10];
wire iVCNT[10];
// シフトレジスタ
reg sreg[8] = 0;
wire sregld;
// 色情報をsregのLDと同時に取り込む
reg color[12] = 0;
// 水平、垂直表示イネーブル信号
wire hdispen;
wire vdispen;
// 表示イネーブルを1クロック遅らせてVGA_BLANK_Nを作成
reg VGA_BLANK_N_r = 0;
// RGB出力信号作成
reg vga_rgb[12] = 0;
wire m_clock, p_reset;
m_clock = clk;
p_reset = reset;
// 色情報をsregのLDと同時に取り込む
reg color[12] = 0;
// 水平、垂直表示イネーブル信号
wire hdispen;
wire vdispen;
// 表示イネーブルを1クロック遅らせてVGA_BLANK_Nを作成
reg VGA_BLANK_N_r = 0;
// RGB出力信号作成
reg vga_rgb[12] = 0;
wire m_clock, p_reset;
m_clock = clk;
p_reset = reset;
// syncgen接続
snc.m_clock = (clk);
snc.p_reset = (reset);
PCK = snc.PCK;
VGA_HS = snc.VGA_HS;
VGA_VS = snc.VGA_VS;
HCNT = snc.HCNT;
VCNT = snc.VCNT;
// VRAM接続
vram.data_a = ( writedata );
vram.address_a = ( address );
vram.wren_a = ( write );
vram.byteena_a = ( byteenable );
vram.clock_a = ( clk );
readdata = ( vram.q_a );
vram.data_b = ( 32'h0 );
vram.address_b = ( vramaddr );
vram.wren_b = ( 1'b0 );
vram.clock_b = ( PCK );
vramout = ( vram.q_b );
// CGROM接続
cgrom.address = ({vramout[6:0], vdotcnt});
cgrom.clock = (PCK);
cgout = cgrom.q;
// HCNTとVCNTを文字とドットのカウンタとして分けて考える
hchacnt = iHCNT[9:3]; // 水平文字カウンタ
hdotcnt = iHCNT[2:0]; // 水平ドットカウンタ
vchacnt = iVCNT[8:3]; // 垂直文字カウンタ
vdotcnt = iVCNT[2:0]; // 垂直ドットカウンタ
// 内部での参照用にカウント値を変換
iHCNT = HCNT - HFRONT - HWIDTH - HBACK + 10'd8;
iVCNT = VCNT - VFRONT - VWIDTH - VBACK - 10'd40;
// シフトレジスタ
sregld = (hdotcnt==3'h6 && iHCNT<10'd640);
// 色情報をsregのLDと同時に取り込む
if (sregld) color := vramout[27:16];
// ここ符号拡張ちゃんとしないとシフト時に上位bitが失われてしまう
vramaddr = (12'(vchacnt)<<6) + (12'(vchacnt)<<4) + 12'(hchacnt);
// 水平、垂直表示イネーブル信号
hdispen = (10'd7<=iHCNT && iHCNT<10'd647);
vdispen = (iVCNT<10'd400);
// 表示イネーブルを1クロック遅らせてVGA_BLANK_Nを作成
VGA_BLANK_N = VGA_BLANK_N_r;
// RGB出力信号作成
VGA_CLK = PCK;
VGA_R = vga_rgb[11:8];
VGA_G = vga_rgb[ 7:4];
VGA_B = vga_rgb[ 3:0];
func snc.PCK_posedge {
// シフトレジスタ
if ( sregld ){
sreg := cgout;
}else{
sreg := {sreg[6:0], 1'b0};
}
// 表示イネーブルを1クロック遅らせてVGA_BLANK_Nを作成
VGA_BLANK_N_r := hdispen & vdispen;
// RGB出力信号作成
vga_rgb := color & (12{(hdispen & vdispen & sreg[7])});
}
}
2.5. NiosII システムの作成
以下のファイルを用意しvgaif_ipフォルダを作成。その中にNSLをVerilogにコンパイルしたファイルと一緒に入れておく。
vgaif_ip_hw.tcl
package require -exact qsys 16.1
#
# module vgaif_ip
#
set_module_property DESCRIPTION ""
set_module_property NAME vgaif_ip
set_module_property VERSION 1.0
set_module_property INTERNAL false
set_module_property OPAQUE_ADDRESS_MAP true
set_module_property AUTHOR ""
set_module_property DISPLAY_NAME vgaif_ip
set_module_property INSTANTIATE_IN_SYSTEM_MODULE true
set_module_property EDITABLE true
set_module_property REPORT_TO_TALKBACK false
set_module_property ALLOW_GREYBOX_GENERATION false
set_module_property REPORT_HIERARCHY false
#
# file sets
#
add_fileset QUARTUS_SYNTH QUARTUS_SYNTH "" ""
set_fileset_property QUARTUS_SYNTH TOP_LEVEL vgaif_ip
set_fileset_property QUARTUS_SYNTH ENABLE_RELATIVE_INCLUDE_PATHS false
set_fileset_property QUARTUS_SYNTH ENABLE_FILE_OVERWRITE_MODE false
add_fileset_file vgaif_ip.v VERILOG PATH vgaif_ip.v TOP_LEVEL_FILE
add_fileset_file syncgen.v VERILOG PATH syncgen.v
#
# parameters
#
#
# display items
#
#
# connection point clock
#
add_interface clock clock end
set_interface_property clock clockRate 0
set_interface_property clock ENABLED true
set_interface_property clock EXPORT_OF ""
set_interface_property clock PORT_NAME_MAP ""
set_interface_property clock CMSIS_SVD_VARIABLES ""
set_interface_property clock SVD_ADDRESS_GROUP ""
add_interface_port clock clk clk Input 1
#
# connection point reset
#
add_interface reset reset end
set_interface_property reset associatedClock clock
set_interface_property reset synchronousEdges DEASSERT
set_interface_property reset ENABLED true
set_interface_property reset EXPORT_OF ""
set_interface_property reset PORT_NAME_MAP ""
set_interface_property reset CMSIS_SVD_VARIABLES ""
set_interface_property reset SVD_ADDRESS_GROUP ""
add_interface_port reset reset reset Input 1
#
# connection point avalon_slave_0
#
add_interface avalon_slave_0 avalon end
set_interface_property avalon_slave_0 addressUnits WORDS
set_interface_property avalon_slave_0 associatedClock clock
set_interface_property avalon_slave_0 associatedReset reset
set_interface_property avalon_slave_0 bitsPerSymbol 8
set_interface_property avalon_slave_0 burstOnBurstBoundariesOnly false
set_interface_property avalon_slave_0 burstcountUnits WORDS
set_interface_property avalon_slave_0 explicitAddressSpan 0
set_interface_property avalon_slave_0 holdTime 0
set_interface_property avalon_slave_0 linewrapBursts false
set_interface_property avalon_slave_0 maximumPendingReadTransactions 0
set_interface_property avalon_slave_0 maximumPendingWriteTransactions 0
set_interface_property avalon_slave_0 readLatency 0
set_interface_property avalon_slave_0 readWaitTime 1
set_interface_property avalon_slave_0 setupTime 0
set_interface_property avalon_slave_0 timingUnits Cycles
set_interface_property avalon_slave_0 writeWaitTime 0
set_interface_property avalon_slave_0 ENABLED true
set_interface_property avalon_slave_0 EXPORT_OF ""
set_interface_property avalon_slave_0 PORT_NAME_MAP ""
set_interface_property avalon_slave_0 CMSIS_SVD_VARIABLES ""
set_interface_property avalon_slave_0 SVD_ADDRESS_GROUP ""
add_interface_port avalon_slave_0 address address Input 12
add_interface_port avalon_slave_0 byteenable byteenable Input 4
add_interface_port avalon_slave_0 write write Input 1
add_interface_port avalon_slave_0 read read Input 1
add_interface_port avalon_slave_0 writedata writedata Input 32
add_interface_port avalon_slave_0 readdata readdata Output 32
set_interface_assignment avalon_slave_0 embeddedsw.configuration.isFlash 0
set_interface_assignment avalon_slave_0 embeddedsw.configuration.isMemoryDevice 0
set_interface_assignment avalon_slave_0 embeddedsw.configuration.isNonVolatileStorage 0
set_interface_assignment avalon_slave_0 embeddedsw.configuration.isPrintableDevice 0
#
# connection point conduit_end
#
add_interface conduit_end conduit end
set_interface_property conduit_end associatedClock clock
set_interface_property conduit_end associatedReset reset
set_interface_property conduit_end ENABLED true
set_interface_property conduit_end EXPORT_OF ""
set_interface_property conduit_end PORT_NAME_MAP ""
set_interface_property conduit_end CMSIS_SVD_VARIABLES ""
set_interface_property conduit_end SVD_ADDRESS_GROUP ""
add_interface_port conduit_end VGA_B vga_b Output 4
add_interface_port conduit_end VGA_G vga_g Output 4
add_interface_port conduit_end VGA_HS vga_hs Output 1
add_interface_port conduit_end VGA_R vga_r Output 4
add_interface_port conduit_end VGA_VS vga_vs Output 1
add_interface_port conduit_end VGA_BLANK_N vga_blank_n Output 1
add_interface_port conduit_end VGA_CLK vga_clk Output 1
Platform Designerで以下のコンポーネントを追加してNiosIIシステム構築をする。詳しい手順は前記事やテキスト参照
- NiosII CPU
- On Chip Memory
- JTAG UART
- System ID
- vgaif_ip
2.6. 最上位層の作成
最上位階層ではPlatform Designerで作られたNiosIIシステムと
FPGAボードのクロック・リセット入力とVGA出力の配線を行っている。
最上位層
declare vgaif {
/* スライドスイッチ、プッシュスイッチ、7セグメントLED */
input SW[9];
input KEY[4];
output HEX0[7], HEX1[7], HEX2[7], HEX3[7], HEX4[7], HEX5[7];
/* 単体LED、PS/2端子 */
output LEDR[10];
inout PS2_CLK, PS2_DAT;
/* VGA */
output VGA_CLK;
output VGA_R[8], VGA_G[8], VGA_B[8];
output VGA_HS, VGA_VS;
output VGA_BLANK_N, VGA_SYNC_N;
/* SDRAM */
output DRAM_CLK, DRAM_CKE;
output DRAM_ADDR[13];
output DRAM_BA[2];
output DRAM_CAS_N, DRAM_RAS_N;
output DRAM_CS_N, DRAM_WE_N;
output DRAM_UDQM, DRAM_LDQM;
inout DRAM_DQ[16];
/* GPIOコネクタ #0 */
inout GPIO_0[36];
/* GPIOコネクタ #1 */
inout GPIO_1[36];
}
declare vgaif_qsys interface {
input clk_clk;
input reset_reset_n;
output vgaif_ip_0_conduit_end_vga_clk;
output vgaif_ip_0_conduit_end_vga_b[4];
output vgaif_ip_0_conduit_end_vga_g[4];
output vgaif_ip_0_conduit_end_vga_r[4];
output vgaif_ip_0_conduit_end_vga_hs;
output vgaif_ip_0_conduit_end_vga_vs;
output vgaif_ip_0_conduit_end_vga_blank_n;
}
module vgaif {
vgaif_qsys u0;
HEX0 = 7'h7f;
HEX1 = 7'h7f;
HEX2 = 7'h7f;
HEX3 = 7'h7f;
HEX4 = 7'h7f;
HEX5 = 7'h7f;
u0.clk_clk = m_clock;
u0.reset_reset_n = ~p_reset;
VGA_CLK = u0.vgaif_ip_0_conduit_end_vga_clk;
VGA_B = u0.vgaif_ip_0_conduit_end_vga_b << 4;
VGA_G = u0.vgaif_ip_0_conduit_end_vga_g << 4;
VGA_R = u0.vgaif_ip_0_conduit_end_vga_r << 4;
VGA_HS = u0.vgaif_ip_0_conduit_end_vga_hs;
VGA_VS = u0.vgaif_ip_0_conduit_end_vga_vs;
VGA_BLANK_N = u0.vgaif_ip_0_conduit_end_vga_blank_n;
}
2.7. プログラムの作成
プログラムでは、文字や色を適当にに設定して表示する。
- 設定には上記のメモリマップに従う
- 以下のbitフィールド構造体と共用体を使用する。
- bitフィールド構造体と共用体は前回説明したので詳しくはそちら参照。
// 共用体
typedef union tag_vram_word{
// ビットフィールドの構造体
// メモリマップに従って各要素を定義
struct tag_vram_bit_field{
unsigned int c : 8;
unsigned int : 8;
unsigned int b : 4;
unsigned int g : 4;
unsigned int r : 4;
unsigned int : 4;
} field;
// RGB をまとめて定義
struct tag_vram_bit_rgb_field{
unsigned int : 16;
unsigned int rgb : 12;
unsigned int : 4;
} rgb;
// bitフィールド全体をまとめて書き込む用
unsigned int out;
} VRAM_WORD;
表示する文字と色の情報を記述。
char s[16]= "Hello,World!123 ";
const int const colors[50] = {
0xf00, 0xf20, 0xf40, 0xf60, 0xf80, 0xfa0, 0xfc0, 0xfe0, 0xff0, 0xef0,
0xcf0, 0xaf0, 0x8f0, 0x6f0, 0x4f0, 0x2f0, 0x0f0, 0x0f2, 0x0f4, 0x0f6,
0x0f8, 0x0fa, 0x0fc, 0x0fe, 0x0ff, 0x0ef, 0x0cf, 0x0af, 0x08f, 0x06f,
0x04f, 0x02f, 0x00f, 0x20f, 0x40f, 0x60f, 0x80f, 0xa0f, 0xc0f, 0xe0f,
0xf1f, 0xf3f, 0xf4f, 0xf6f, 0xf8f, 0xf9f, 0xfbf, 0xfcf, 0xfef, 0xfff,
};
VRAMに書込む処理は以下の通り
- 変数の定義
- 文字や色を選ぶためのカウンタ。
- 実際に書込む値を記憶する変数(先ほど定義した共用体を使用)
- 文字列と色の情報をVRAMに書き込む
- 次に書込む値を決定するためのカウントアップ処理
// カウント用
int i, x, y, col, cnt;
// VRAM書き込む値
VRAM_WORD w;
w.out = 0x00000000;
cnt = 0;
col = 0;
x = 0;
y = 0;
for ( i=0; i<XSIZE*YSIZE; i++ ) {
w.out = 0x00000000;
w.field.c = s[cnt]; // 文字の選択
w.rgb.rgb = colors[y]; // 色の選択
IOWR_32DIRECT(VGAIF_IP_0_BASE, i*4, w.out); // VRAMに書込む
if(x == (XSIZE - 1)) x = 0; else x++;
if (cnt == 15) {
cnt = 0;
if (col == 4){
col = 0;
if(y == 49){
y = 0;
}else y++;
}else col++;
}else cnt++;
}
プログラム全体
#include "system.h"
#include "io.h"
// X, Yサイズ
#define XSIZE 80
#define YSIZE 50
typedef union tag_vram_word{
struct tag_vram_bit_field{
unsigned int c : 8;
unsigned int : 8;
unsigned int b : 4;
unsigned int g : 4;
unsigned int r : 4;
unsigned int : 4;
} field;
struct tag_vram_bit_rgb_field{
unsigned int : 16;
unsigned int rgb : 12;
unsigned int : 4;
} rgb;
unsigned int out;
} VRAM_WORD;
int main()
{
char s[16]= "Hello,World!123 ";
const int const colors[50] = {
0xf00, 0xf20, 0xf40, 0xf60, 0xf80, 0xfa0, 0xfc0, 0xfe0, 0xff0, 0xef0,
0xcf0, 0xaf0, 0x8f0, 0x6f0, 0x4f0, 0x2f0, 0x0f0, 0x0f2, 0x0f4, 0x0f6,
0x0f8, 0x0fa, 0x0fc, 0x0fe, 0x0ff, 0x0ef, 0x0cf, 0x0af, 0x08f, 0x06f,
0x04f, 0x02f, 0x00f, 0x20f, 0x40f, 0x60f, 0x80f, 0xa0f, 0xc0f, 0xe0f,
0xf1f, 0xf3f, 0xf4f, 0xf6f, 0xf8f, 0xf9f, 0xfbf, 0xfcf, 0xfef, 0xfff,
};
int i, x, y, col, cnt;
VRAM_WORD w;
w.out = 0x00000000;
cnt = 0;
col = 0;
x = 0;
y = 0;
for ( i=0; i<XSIZE*YSIZE; i++ ) {
w.out = 0x00000000;
w.field.c = s[cnt];
w.rgb.rgb = colors[y];
IOWR_32DIRECT(VGAIF_IP_0_BASE, i*4, w.out);
if(x == (XSIZE - 1)) x = 0; else x++;
if (cnt == 15) {
cnt = 0;
if (col == 4){
col = 0;
if(y == 49){
y = 0;
}else y++;
}else col++;
}else cnt++;
}
return 0;
}
3. 動作チェック
ModelSim使って波形を見てみる。
- 左上に白
#FFFでAを表示させてみる
VRAMに書込む情報を用意
0fff0041// (00,00): A #fff
00000020// (00,01): #000
00000020// (00,02): #000
00000020// (00,03): #000
...
00000020// (49,78): #000
00000020// (49,79): #000
一番最初以外は真っ黒スペース
- 上から8 x 8pxのVGA出力波形を上のピクセルから順番に表示したもの
たしかにAと表示されていそうだ。
- DE1-SoCを使ってプログラムを実行してディスプレイに接続してみた。(下図)
4. まとめ
FPGAでからVGAのディスプレイに文字を表示した。
- VRAMで文字情報を記憶
- CGROMに表示パターンを格納
- VRAMから読み取ったASCIIコードに対応する文字をCGROMから表示パターンを取得し、VGAに出力
次回はテキストの第8章のSDRAMを使用したグラフィック表示回路を作ってみる。
参考文献
-
テキストによると、内蔵メモリの使用効率を考え、word数を2の累乗の値(4096)を超えない範囲にしたらしい。内部ではデータ容量が8Kbitのメモリを組み合わせて実装するようで、32bit 4096wordの場合ではメモリ使用数は16だが、そこから1word増えるだけでもメモリ使用数は32になってしまう。 ↩






