トップレベルの書き方がわかったところで、いよいよ Spartan 3A シリーズ のブロック RAM を使ってみる(tutorial4_1.chc)。Spartan 6 シリーズでも同じようにブロック RAM を使えるよ。
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< clk,clk> は、ポート 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 を使うという意味。
ざっと説明したけど、そんなに難しくないはず。シングルポートの書き方は書かないけど予想できるよね(笑)?
さて、これで RAM が使えるようになった。かなり面白いことが出来るんじゃないかな?次回は、いよいよ Digital Clock Manager を使ってみるよ。
cherry syn version 0.4 以降で、RAM の初期値を指定出来るようになったよ。書き方は簡単で、普通に配列の初期化のように書けば良い。
ram uint#9 clock<clk> r0[2048] = {1,2,3,4... };
参考までに 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 の内容を読める(はず)。