0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

NSLで回路を作ろう#04: ディスプレイに文字を表示

0
Last updated at Posted at 2024-01-19

【改訂2版】FPGAボードで学ぶ 組込みシステム開発入門[Intel FPGA編]の第7章に載っているVGA文字表示回路をハードウェア記述言語のNSLで作ってみよう。

1. 回路について

DE1-SoCにはVGA端子(Wikipedia)がある

  • 同期信号に合わせて走査線単位でRGBの出力をする。
  • 信号範囲は800 x 525だが、外側に非表示領域を含む
    • 実際に表示される大きさは640 x 480
  • 画面を横のラインで分ける
    • 最初は一番上のラインの左から右方向に画素値の信号を送る
    • 右までたどり着いたら、一つ下の次のラインの一番左から右、次のラインの左から右といった順番。
    • 下記のイメージ図参照

scanning_line.png

グラフィック表示するには、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の時は左上を示す。
      • カウンタの値に合わせて、同期信号を生み出したり、文字の読み込みするアドレスを決めたりする。

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しておく。
vga_param.nsh
#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ファイル

ヘッダファイル

syncgen.nsh
#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

モジュール

syncgen.nsl
#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ファイルを用いる

ダウンロードしたファイルでは以下のような設定がされている。


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;

見やすいように表形式にしたものを以下に示す。

CGROM-memmap01.png

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)

VRAM-memmap01.png

記述ファイル

作成したNSLファイルを以下に示す。(長いので折りたたみ)

vgaif_ip

ヘッダファイル

vgaif_ip.nsh
#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

モジュール

vgaif_ip.nsl
#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

nios2_components.png

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出力波形を上のピクセルから順番に表示したもの

vgaif_sim_01.gif

赤枠の部分を順番に並べてみると・・・
signal_A.png

たしかにAと表示されていそうだ。

  • DE1-SoCを使ってプログラムを実行してディスプレイに接続してみた。(下図)

vga_test01.png

4. まとめ

FPGAでからVGAのディスプレイに文字を表示した。

  • VRAMで文字情報を記憶
  • CGROMに表示パターンを格納
  • VRAMから読み取ったASCIIコードに対応する文字をCGROMから表示パターンを取得し、VGAに出力

次回はテキストの第8章のSDRAMを使用したグラフィック表示回路を作ってみる。

参考文献

  1. テキストによると、内蔵メモリの使用効率を考え、word数を2の累乗の値(4096)を超えない範囲にしたらしい。内部ではデータ容量が8Kbitのメモリを組み合わせて実装するようで、32bit 4096wordの場合ではメモリ使用数は16だが、そこから1word増えるだけでもメモリ使用数は32になってしまう。 ↩

0
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?