ブロック RAM の使い方

トップレベルの書き方がわかったところで、いよいよ XC3S50A FPGA のブロック RAM を使ってみる(tutorial4_1.chc)。


process writeRAM(clkin uint#1 clk, rstin uint#1 rst, ram uint#9 r0[2048])
{
	uint#11 i=0;
	do {
		r0[i] = i(7..0);
	} while (++i);

	while (1);
}

process SPI(
	clkin uint#1 clk,
	rstin uint#1 rst,
	in uint#1 SS,
	in uint#1 SCLK,
	in uint#1 MOSI,
	out uint#1 tristate MISO,
	ram uint#9 r0[2048])
{
	uint#1 prev_sclk = 0, prev_prev_sclk = 0;
	uint#8 sreg = 0;
	uint#11 addr=0;
	uint#4 bit=0;

	while (1) {
		uint#1 sclk;
		sclk = SCLK;
		if (!SS) {
			if (!prev_prev_sclk && prev_sclk) {
				sreg = sreg @ MOSI;
				bit++;
			}
			MISO = sreg(7);
			if (bit==8) {
				sreg = r0[addr++];
				bit = 0;
			}
		}
		prev_prev_sclk = prev_sclk;
		prev_sclk = sclk;
	}
}

toplevel logicboy(
	in uint#1 pin<88> clk,
	in uint#1 pin<90> rst,
	in uint#1 pin<98> SS, 
	in uint#1 pin<82> SCLK,
	in uint#1 pin<94> MOSI,
	out uint#1 pin<93> MISO
)
{
	ram uint#9 port_num<2> clock r0[2048];

	SPI(clk, rst, SS, SCLK, MOSI, MISO, r0 port<0>);
	writeRAM(clk, rst, r0 port<1>);
}

RAMは、process の中には書くことが出来ない。トップレベルで宣言して、process のパラメータに渡す。

toplevel の "ram uint#9..." が、RAM の宣言部分。uint#9 は unsigned の 9 ビット幅という意味。port_num<2> は、デュアルポートであることを示す。clock は、ポート 0 のクロックもポート 1 のクロックも clk を使うという意味。r0[2048] は、名前が r0、サイズが 2048 という意味。XC3S***A では、4 ビット x 4096、9 ビット x 2048、18 ビット x 1024、36 ビット x 512 などのサイズの RAM が使える。シングルポートなら 72 ビット x 256 も使えるらしい。なお、この例では、RAMの下位8ビットのみを使っている。

XC3S50Aでは、ブロック RAM は 3 個使える。ちなみに XC3S200A なら、ブロック RAM は 16 個使えるよ。

writeRAM、SPI という二つの process がある。writeRAM は、RAM のポート 0 を使って、0,1,2...とデータを書いて、書き終わったら無限ループする。SPI は、モード 0、スレーブの SPI プロトコルを使って外部にデータを送る。1 バイト目は必ずゼロになり、2 バイト目から RAM のデータをアドレス 0 から送る。SPI の周波数は、FPGA のクロック周波数の 1/4 以下でなければならない。

process SPI のパラメータで、MISO に tristate というのが書いてあるけど、データが出力されないときはトライステート出力になるという意味。process SPI は、1 サイクルのループが無限に回るだけ。

sreg = sreg @ MOSI; の@はビット連結で、下位 1 ビットに MOSI を連結する。連結の結果 9 ビットになるけど、8 ビットの変数に代入するので上位 1 ビットは切り捨てられる。

toplevel での SPI と writeRAM のインスタンスのパラメータで、r0 port<0>、r0 port<1> というのがあるけど、それぞれ ram r0 のポート 0、ポート 1 を使うという意味。

ざっと説明したけど、そんなに難しくないはず。シングルポートの書き方は書かないけど予想できるよね(笑)?

参考までに XMEGA マイコンで SPI 通信をするプログラムを載せておくね。


#define F_CPU 32000000

#include <avr/io.h>
#include <util/delay.h>

#define SPID_SS_bm	0b00001000
#define SPID_MISO_bm	0b01000000
#define SPID_MOSI_bm	0b00100000
#define SPID_SCK_bm	0b10000000

int main(void)
{
	// clock = internal RC 32MHz
	OSC.CTRL = OSC_RC32MEN_bm;
	while ((OSC.STATUS & OSC_RC32MRDY_bm)==0);
	CCP = CCP_IOREG_gc;
	CLK.CTRL = CLK_SCLKSEL_RC32M_gc;

	// clock output on PORTD PIN4 for FPGA
	PORTD.DIRSET =  PIN4_bm;
	PORTCFG.CLKEVOUT = PORTCFG_CLKEVPIN_bm | PORTCFG_CLKOUT_PD7_gc;
	
	// FPGA reset output on PORTB PIN3
	PORTB.OUTSET = PIN3_bm;
	PORTB.DIRSET = PIN3_bm;
	
	// SPI on PORTD	
	SPID_CTRL = (SPI_ENABLE_bm | SPI_MASTER_bm | SPI_MODE_0_gc | SPI_PRESCALER_DIV4_gc);
	PORTD_OUTSET = SPID_SS_bm;
	PORTD_DIRSET = (SPID_MOSI_bm | SPID_SCK_bm | SPID_SS_bm);
	PORTD_DIRCLR = (SPID_MISO_bm);
	PORTD_PIN6CTRL = PORT_OPC_PULLUP_gc;

	// reset FPGA!
	PORTB.OUTCLR = PIN3_bm;
	_delay_ms(1);
	PORTB.OUTSET = PIN3_bm;
	
	// wait 1 sec
	_delay_ms(1000);
	
	// enable SS
	PORTD_OUTCLR = SPID_SS_bm;
	_delay_us(10);
	
	uint8_t buff[1024];
	
	for (uint16_t i=0; i<1024; i++) {
		SPID.DATA = 0xff;
		// wait for transmission complete
		while (!(SPID_STATUS & SPI_IF_bm)) ;
		buff[i] = SPID.DATA;
	}

	_delay_us(10);
	// disable SS
	PORTD_OUTSET = SPID_SS_bm;
		
	....

    while(1);
}

LOGIC BOY では、PD7 (PORT D のピン 7) に 32 MHz のシステムクロックを出力してるけど、PD7 は SPI の SCK とバッティングするので、PD4 にシステムクロックを出力するように変更してる(それしか選択肢がない)。PD4 は、SPID の SS に割り当てられてるけど、実は SS は(出力で使う場合は)他のどのピンでも大丈夫なので、PD3 を使っている。

PD4 の 32MHz クロック、PB3 のリセット、PD3 の SS、PD5 の MOSI、PD6 の MISO、PD7 の SCK を FPGA に接続すれば、SPI 通信で FPGA の RAM の内容を読める(はず)。

さて、これで RAM が使えるようになった。かなり面白いことが出来るんじゃないかな?次回は、いよいよ Digital Clock Manager を使ってみるよ。

追記:ブロック RAM の初期化

cherry syn version 0.4 以降で、RAM の初期値を指定出来るようになったよ。書き方は簡単で、普通に配列の初期化のように書けば良い。


ram uint#9 port_num<2> clock r0[2048] = {1,2,3,4... };