目次

配列処理

 Forthの言語仕様には、配列が存在しません。

 配列は、メモリ上に用意するブロックで代用します。

 メモリ上のブロックを扱うワードは、以下。

 tarボールを解凍して得られるテキストファイルの中に
 blocks.txtがあります。
 その中をのぞいてみると、以下。

\ Blocks for Mecrisp-Stellaris

1024 constant blocksize

\ This will be in Flash or external memory later. In RAM just for testing.
\ Block numbers can be 0 to $FFFFFE. $FFFFFF is reserved for "not assigned".

8 constant #blocks
blocksize #blocks * buffer: blockspace
: block#toaddress ( n -- addr ) blocksize * blockspace + ;
:  readblock ( block# addr -- )  ." Read block " over u. ." into " dup hex. cr swap block#toaddress swap blocksize move ;
: writeblock ( block# addr -- ) ." Write " dup hex. ." into block " over u. cr swap block#toaddress      blocksize move ;

\ Blocks implementation

2 constant #buffers \ Choose number of buffers between 1 and 128
#buffers cells buffer: bufferlist
blocksize #buffers * buffer: blockbuffers

: >block#   ( n -- n ) cells bufferlist + @ $00FFFFFF and ;
: >buffer#  ( n -- n ) cells bufferlist + @ $7F000000 and 24 rshift ;
: >changed? ( n -- ? ) cells bufferlist + @ $80000000 and 0<> ;
: >clean!   ( n -- )   cells bufferlist + dup @ $80000000 bic swap ! ;

: >bufferaddr ( n -- addr ) blocksize * blockbuffers + ;

: topbuffer ( -- addr ) 0 >buffer# >bufferaddr ;

: empty-buffers ( -- ) #buffers 0 do i 24 lshift $00FFFFFF or bufferlist i cells + ! loop ;
: update ( -- ) bufferlist @ $80000000 or bufferlist ! ;

: buffertotop ( n -- ) \ Takes one element of the buffer list and moves it to the top.
  dup cells bufferlist + @ \ Get the element
  swap
  ( Element n )
  bufferlist
  dup cell+
  rot cells
  move \ Let all other elements fall down, for example 3: 2->3 1->2 0->1
  bufferlist ! \ Store on the top
;

: save-buffer ( n -- ) dup >changed? if dup dup >block# swap >buffer# >bufferaddr writeblock >clean! else drop then ;
: save-buffers ( -- ) #buffers 0 do i save-buffer loop ;
: flush ( -- ) save-buffers empty-buffers ;

: load-buffer ( block# -- ) dup topbuffer readblock bufferlist @ $FF000000 and or bufferlist ! ; \ Always loads into the top buffer

: block ( n -- addr )
  \ Check if the desired block is already in one of the buffers.
  #buffers 0 do dup i >block# = if ." Already " drop i buffertotop topbuffer unloop exit then loop

  \ Check if there is an empty buffer
  #buffers 0 do i >block# $00FFFFFF = if ." Empty " i buffertotop load-buffer topbuffer unloop exit then loop

  \ Check if there is an assigned, but still clean buffer
  #buffers 0 do i >changed? not if ." Clean " i buffertotop load-buffer topbuffer unloop exit then loop

  \ No buffer is empty. Write back the least recent one
  ." Writeback "
  #buffers 1- save-buffer
  #buffers 1- buffertotop
  load-buffer
  topbuffer
;

: listbuffers ( -- ) \ Show current buffer allocations and state
  cr
  #buffers 0 do
    ."  Buffer: " i >buffer# u. 
    ." Block: " i >block# dup $00FFFFFF = if ." Not assigned " drop else u. then
    i >changed? if ." Changed" else ." Clean" then
    cr
  loop
;

empty-buffers \ Needed before start as buffer: space is uninitialised !

\ Some code for testing only

: lb ( -- ) listbuffers ;
: db ( -- ) cr #blocks 0 do i u. blockspace i blocksize * + ctype cr loop ;
: s ( buffer-addr -- ) >r token ( string-addr length ) dup r@ c! ( string-addr length ) r> 1+ ( string-addr length buffer-addr ) swap move update ;

\ Erase the block memory in RAM
blockspace   blocksize #blocks *   0   fill

0 block s Have
1 block s a
2 block s good
3 block s time
4 block s with
5 block s Mecrisp
6 block s Stellaris

lb
db

0 block ctype
1 block ctype
2 block ctype
3 block ctype
4 block ctype
5 block ctype
6 block ctype

0 block s Enjoy
2 block s nice

lb
db
flush
db

 定義されたワードと組込みワードを見て、どんなことを
 やっているのか探ってみます。

1024 constant blocksize

\ This will be in Flash or external memory later. In RAM just for testing.
\ Block numbers can be 0 to $FFFFFE. $FFFFFF is reserved for "not assigned".

8 constant #blocks
blocksize #blocks * buffer: blockspace
: block#toaddress ( n -- addr ) blocksize * blockspace + ;
:  readblock ( block# addr -- )  ." Read block " over u. ." into " dup hex. cr swap block#toaddress swap blocksize move ;
: writeblock ( block# addr -- ) ." Write " dup hex. ." into block " over u. cr swap block#toaddress      blocksize move ;

 コメントでは、ブロック番号は、0から$FFFFFEとなっています。
 $FFFFFFは、2の補数で表現しているとすれば、−1になるので
 正の最大値としては、$FFFFFEにしてあります。

 バッファを確保するときのワードは、「buffer: 」なので
 blockspaceがブロック名称で、任意の名称を命名できます。

 blocksize、#blocksが1024、8となっているので8192バイトを
 blockspaceという名称で扱うことに。



 計算でスタックにどんな値を入れるのかを見ていきます。

 blockspaceは、ブロックの先頭アドレスを示すので、目的の
 データを含んでいるアドレスを計算するワードが、以下。

: block#toaddress ( n -- addr ) blocksize * blockspace + ;

 このワードには、パラメータが必要で、ブロック番号を指定。

 パラメータとして2を入れると、ワード「block#toaddress」が
 求めるのは、2ブロック目の先頭アドレスがスタックに入ります。



 ブロックを指定し、その内部をリードするワードを見てみます。

:  readblock ( block# addr -- )  ." Read block " over u. ." into " dup hex. cr swap block#toaddress swap blocksize move ;

 このワードには、パラメータが必要でブロックの先頭アドレスを指定。

 ワード「over」を利用して、ソースとなるブロックの先頭アドレスを
 スタックトップにした後ディストネーションの先頭アドレスと共に
 表示しています。

 ブロックサイズ分のデータを、ワード「move」を利用してコピー。
 図で見ると、以下。



 ブロックを指定し、その内部にデータを格納するワードを見ていきます。


: writeblock ( block# addr -- ) ." Write " dup hex. ." into block " over u. cr swap block#toaddress      blocksize move ;

 ワード「dup」を利用して、ソースとなるブロックの
 先頭アドレスを表示後、ディストネーションの先頭
 アドレスを利用して、ワード「move」を利用してコピー。

 ワード「readblock」との違いは、ソースとなるブロックの
 アドレスが、独自に確保したブロックを利用していると
 いうこと。

 図で見ると、以下。




 32ビットのプロセッサでは、1ワードのデータは32ビットで
 扱うほうが、わかりやすいですが、メモリアドレスはバイト
 で割り付けてあるので、ブロック内のデータの出し入れは
 ワード(32ビット)単位になります。

 ブロックにワード値を格納するたびに、バイトでふってある
 アドレスに4を加算するのは面倒なので、ワード「cell+」が
 用意されています。

 動作を確認してみると、以下。



 ワードは、細胞を意味するセル(cell)で扱われています。

 1セル分のアドレスを増やすことが、4を加算することに
 相当しています。

 セルという概念で、ブロックに入っているデータを表示する
 ことができます。そのためのワードを使って確認しましょう。

 ブロックの先頭と次のセルの内容を表示。



 上から2行目、3行目で表示された内容が、ブロックの中の
 4バイトデータを表示していくワードで公開された値と一致
 しています。

 ワード「@」は、スタックに値を格納する機能をもっていますが
 値のサイズは、32ビット=1ワード=4バイト=1セルである
 と判断してよいでしょう。

 ワード「@」は、次のようにメモリ上のデータを出し入れする
 ことになります。



 これまでの内容は、わかりにくかったので、ワード
 「buffer: 」を利用して簡単なテストをしてみます。

 32バイト=8ワードのデータ領域を確保し、その
 先頭アドレスを表示。



 ブロックの先頭アドレスは、$100006EC。
 この領域は、RAMへと割当てられてます。

 LPC1114は、ARMのCortex-M0ベースなので、バイトごとに
 アドレスを与えていても、32ビット=4バイトを一度に
 扱います。

 32ビット=4バイト=1ワードで扱っていることを
 確認するために、ワードを定義しておきます。



 与えられた数だけ、ワード数だけアドレスが増える
 ようにしてみました。動作を確認。



 確保したブロックの内容を表示できるようにすれば
 ブロックの指定位置にデータを出し入れしたときに
 値が変化しているのを確認しやすくなります。

 確保したワード数だけループを回し、1ワードごとに
 取出し、16進数書式で表示させればよいと考えました。

 ワードの中でdo ... loopを利用します。

: show cr 8 0 do i dup . calc_ptr @ hex. cr loop ;

 このワードを入力。



 定義したワードで、ブロックの内容を表示すると、以下。



 ブロックの1ワード目に値格納後、再度内容を表示してみます。



 内容が変化したことがわかります。

 指定バイト分が確保されているのかは、新たに定義した
 ワードが、どのアドレスから格納されているのかを見て
 みれば、判断できます。

 ワード「words」で、辞書内にあるアドレスを確認。



 ブロック定義でmytableを確保したときには、アドレスが$100006EC。
 新たに定義したワード「calc_ptr」は、$1000070C。

 32=$20となるので、mytableのアドレスに$20を加えてみます。

 $100006EC+$20=$1000070Cより、間違いなく32バイト分の差。

 ブロックが確保できているので、ワード「fill」を使って指定する
 データでブロック内を埋めてみます。



 問題なく、指定データでブロック内を満たすことができました。

 偶然でないことを、別のデータを指定して確認。



 間違いなく、ワード「fill」で指定データで埋められています。

 確保したブロックを、配列として利用するには、ワードだけで
 なく、1バイトか2バイトで扱えないと、使い勝手がよくない
 でしょう。

 バイトで扱う場合は、該当する位置が、どのワード位置に含まれて
 いるのかを計算してから、データを取得するか、設定すれば充分。

 商と剰余の計算をすれば、バイト位置とワード位置の
 相互変換が可能になります。

 1バイトごとのデータ格納は、fillを使えば簡単。

 バッファに1バイトのデータをfillすると考えます。

 Forthのコードでは、以下のようにすれば充分。

: mysta mytable + swap 1 swap fill ;

 ワードを入力して動作を確認してみます。



 間違いなく、指定したバイト位置にデータが格納されました。

 指定した位置のバイトデータを取り出すためには、ワード「@」を
 使って、ワード(4バイト)をスタックに入れて、シフトして取得
 すればよいはず。

: get_byte dup 4 / 4 * mytable + @ swap 4 mod 8 * rshift $FF and hex. ;

 バイト位置を2回利用しています。

 4バイトごとにワードの境界があるので、4で割った商と
 余りを使って、商からワード位置を計算し、そのワードの
 どこに相当するのかを余りで算出。

 4バイト目の値を変更後、取り出してみます。



 もうひとつデータを変更して試してみます。



 バイトでの入出力ができたので、16ビットでの表示を
 担当するワードを定義します。

: get_half dup 4 / 4 * mytable + @ swap 4 mod 8 * rshift $FFFF and hex. ;

 試してみましょう。



 どうやら、これでよいようです。

 16ビットでの表示ができたので、16ビットデータの格納を考えます。

 16ビットの設定はバイトの設定を2回実行で対応すればよいでしょう。

 スタックをジャグリングする方法で、ワード「mysta」を
 2回利用できるようにしてみます。

: put_half dup 1+ swap rot swap mysta mysta ;

 ワード「mysta」は、1バイトのデータを格納するので
 次のようにパラメータを渡すようにすればいけそうと
 考えました。

    $12 5 mysta $34 6 mysta

 16ビットデータを上位、下位の順に指定して、下位の
 1バイトを渡す仕様としました。

    $12 $34 5 put_half

 ワードを入力して、テスト。



 問題はないようです。

 メモリエリアの一部に、バイトサイズでブロックを
 確保して、バイト、ハーフワード、ワード単位での
 データの出し入れができるようにしました。
 これで配列の操作が可能になったと断言してよいでしょう。

 ワードcreateを利用して、バイト単位の配列を確保する
 ことが可能です。

 allotを利用して、次のように8バイト分を確保し
 値を格納してみます。さらに、表示ワードを定義
 します。

create ntbl 8 allot
3 ntbl 0 + c! 2 ntbl 1 + c!
3 ntbl 2 + c! 3 ntbl 3 + c!
1 ntbl 4 + c! 3 ntbl 5 + c!
3 ntbl 6 + c! 3 ntbl 7 + c!
: shown cr 8 0 do i dup . ntbl + c@ .  cr loop ;

 フェッチとストアには、1バイト単位で扱える
 ワードのc@、c!を使っています。

 どうなるのかを、確認すると以下。




 Forthでは、Enterキーを叩くまでに入力したワードは
 バッファに入っていくので、1行に複数の内容を書き
 見やすくしてあります。


目次

inserted by FC2 system