Z80でリングバッファの実装

## はじめに

こちらは鈴鹿高専 Advent Calendar 2021の4日目の記事です．

https://qiita.com/advent-calendar/2021/snct

注意事項としては，ここで記載されているコードを他のプロダクトでそのまま使うのは**禁止**とします．

何かバグがあっても責任を取れないためです．

また，実際に何かを制作する際には必ず一次情報(データシートなど)を参考にしてください．



## 作ったもの

アセンブラでリングバッファを作りました．

シリアル通信の受信バッファとして適しています．

いきなり内容が薄くなりましたが，元々自分のホームページで公開していたものに加筆修正を施したものです．



## 概要

リングバッファは以下の図のようなリング状のバッファです．
![circularbuffer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/545164/2f314f9d-d9c8-1374-dfd0-a5292ce82e71.png)

先端と終端が論理的に繋がっていて古いデータは上書きされていきます．

C言語などの高級言語での実装では先端と終端の結合に%演算子を使っていることが多いのですが，アセンブラ+Z80ではいろいろしんどい(除算器がない)ので大人しく条件分岐でインデックスを先端に戻す処理をかきます．



## コード

```asm
BUFFER_SIZE     EQU     80H                     ;バッファーのサイズはここで指定
                ORG     5000H
;バッファーの初期化
;引数:  なし
;破壊:  なし
INIT_BUFFER:    PUSH    AF                      ;レジスタの退避
                PUSH    BC
                PUSH    DE
                PUSH    HL
                ;
                XOR     A                       ;ポインタの初期化
                LD      (BUFFER_READ),A
                LD      (BUFFER_WRITE),A
                LD      (BUFFER_LENGTH),A
                ;
                LD      HL,BUFFER               ;バッファー領域の初期化
                LD      DE,BUFFER+1
                LD      BC,BUFFER_SIZE-1
                LD      A,00H
                LD      (HL),A
                LDIR
                ;
                POP     HL                      ;レジスタの復帰
                POP     DE
                POP     BC
                POP     AF
                RET
;
;バッファーへのデータ追加
;引数:          D       追加するデータ
;破壊:          A,F
;返り値:        Z       	T:失敗(空きがない)
;                       F:成功
SET_BUFFER:     PUSH    BC                      ;レジスタの退避
                PUSH    HL
                ;
                LD      A,(BUFFER_LENGTH)       ;バッファーに空きがあるか調べる
                LD      B,BUFFER_SIZE
                XOR     B
                JP      NZ,SET_BUFFER1
                POP     HL
                POP     BC
                RET
                ;
SET_BUFFER1:    LD      HL,BUFFER               ;アドレス算出
                LD      B,0
                LD      A,(BUFFER_WRITE)
                LD      C,A
                ADD     HL,BC
                ;
                LD      (HL),D                  ;データの追加
                ;
                LD      A,(BUFFER_LENGTH)       ;BUFFER_LENGTH++
                INC     A
                LD      (BUFFER_LENGTH),A
                ;
                INC     C                       ;BUFFER_WRITE++
                LD      A,C
                LD      (BUFFER_WRITE),A
                ;
                LD      B,BUFFER_SIZE           ;BUFFER_SIZE==BUFFER_WRITE?
                XOR     B
                POP     HL
                POP     BC
                RET     NZ                      ;BUFFER_SIZE!=BUFFER_WRITE
                XOR     A                       ;BUFFER_WRITE=0
                LD      (BUFFER_WRITE),A
                INC     A                       ;Zフラグを消す
                OR      A
                RET
;
;バッファーからのデータの取り出し
;引数:          なし
;破壊:          A,F
;返り値:        D       取り出したデータ
;              Z       T       失敗(空)
;                      F       成功
GET_BUFFER:     PUSH    BC                      ;レジスタの退避
                PUSH    HL
                ;
                LD      A,(BUFFER_LENGTH)       ;データが入っているか調べる
                LD      B,0
                XOR     B
                JP      NZ,GET_BUFFER1
                POP     HL
                POP     BC
                RET
                ;
GET_BUFFER1:    LD      HL,BUFFER               ;アドレスの算出
                LD      B,0
                LD      A,(BUFFER_READ)
                LD      C,A
                ADD     HL,BC
                ;
                LD      D,(HL)                  ;データの取り出し
                ;
                LD      A,(BUFFER_LENGTH)       ;BUFFER_LENGTH--
                DEC     A
                LD      (BUFFER_LENGTH),A
                ;
                INC     C                       ;BUFFER_READ++
                LD      A,C
                LD      (BUFFER_READ),A
                ;
                LD      B,BUFFER_SIZE           ;BUFFER_SIZE==BUFFER_READ?
                XOR     B
                POP     HL
                POP     BC
                RET     NZ                      ;BUFFER_SIZE!=BUFFER_READ
                XOR     A                       ;BUFFER_READ=0
                LD      (BUFFER_READ),A
                INC     A
                OR      A
                RET
;RAM AREA
                ORG     9000H
BUFFER_READ:    DEFS    01H
BUFFER_WRITE:   DEFS    01H
BUFFER_LENGTH:  DEFS    01H
BUFFER:         DEFS    80H
```

Z80のアセンブラは容易に読めるので，コメントと合わせれば理解は可能だと思います．

それなりに速度に気をつかってみたつもりですが最適化はまだまだできると思います．



## 実際の使用例

```asm
		ORG	0000H
MAIN:	CALL	INIT_BUFFER
		LD	B,4H
LOOP1:	LD	D,B
		CALL	SET_BUFFER
		DJNZ	LOOP1
		LD	B,5H
LOOP2:	LD	D,00H
		CALL	GET_BUFFER
		DJNZ	LOOP2
		HALT
```

こんな感じで所定のレジスタに引数を入れサブルーチンを呼び出すだけでデータの格納，取り出しが可能です．

MIDIの受信に使用するつもりで書きましたが，せっかくなので汎用性を持たせた設計にしました．



## 最後に

Z80は非常に優れたCPUだと思います．

みなさんも是非使ってみてください．