レジスタに入っている16ビットデータを5桁の十進数値としてキャラクターLCDに表示する

テキストは、VHDLで組んだ二進 → 十進変換回路でOUTポートのデータを十進数に変換し、それを5桁の7セグLEDで表示している(pp.221ff.)。

ここでは、せっかくCPUを作ったのでソフトウェアで解決してみる。たとえば0d54321は次の手順で各桁を取り出す。詳細不明であるがdouble dabbleアルゴリズムなるものがあるとの由。

54321 / 10 = 5432, 余り1 (余りが一の位)             
5432  / 10 = 543,  余り2 (余りが十の位)                      
543   / 10 = 54,   余り3 (余りが百の位)                                
54    / 10 = 5,    余り4 (余りが千の位, 商が万の位)                                 

今、ロータリーDIPから0xBA98 (0d47768)を入力している。
f:id:ti-nspire:20210219104157j:plain:w700

クロックは1 MHzにした。もっとコンパクトにできそうだがアセンブリは身につきそうにない。このへんでよしておく。

############################
# アセンブラ(のようなもの) #
############################

import math

def constrain(val, _max, _min):
    return int(min(max(val, _min), _max))

def generate_mif(file_name, width, depth, rom):
    f = open(file_name, "w")
    f.write("WIDTH=%d;\n" % width)
    f.write("DEPTH=%d;\n" % depth)
    f.write("ADDRESS_RADIX=UNS;\n")
    f.write("DATA_RADIX=BIN;\n")
    f.write("CONTENT BEGIN\n")
    format_of_code = "0" + str(width) + "b"
    for i in range(depth):
        machine_code = format(rom[i], format_of_code)
        f.write("%10d   :   %s;\n" % (i, machine_code))
    f.write("END;\n")
    f.close()

def mov(ra, rb):   return  0 << 11 | ra << 8 | rb << 5
def add(ra, rb):   return  1 << 11 | ra << 8 | rb << 5
def sub(ra, rb):   return  2 << 11 | ra << 8 | rb << 5
def _and(ra, rb):  return  3 << 11 | ra << 8 | rb << 5
def _or(ra, rb):   return  4 << 11 | ra << 8 | rb << 5
def sl(ra):        return  5 << 11 | ra << 8                 # 左シフト
def sr(ra):        return  6 << 11 | ra << 8                 # 右シフト
#def sra(ra):       return  7 << 11 | ra << 8                 # 算術右シフト
def jmp_reg(ra):   return  7 << 11 | ra << 8                 # レジスタに記憶してあるアドレスへジャンプ
def ldl(ra, ival): return  8 << 11 | ra << 8 | (ival & 0xFF) # 下位バイトに即値をロード
def ldh(ra, ival): return  9 << 11 | ra << 8 | (ival & 0xFF) # 上位バイトに即値をロード
def cmp(ra, rb):   return 10 << 11 | ra << 8 | rb << 5       # 一致したらフラグが立つ。
def je(addr):      return 11 << 11 | (addr & 0xFF)           # 一致フラグが立っていたらジャンプ
def jmp(addr):     return 12 << 11 | (addr & 0xFF)           # 無条件ジャンプ
def ld(ra, addr):  return 13 << 11 | ra << 8 | (addr & 0xFF) # RAMからレジスタへ値を持ってくる。
def st(ra, addr):  return 14 << 11 | ra << 8 | (addr & 0xFF) # レジスタの値をRAMへ格納する。
#def hlt():         return 15 << 11
def jnc(addr):     return 15 << 11 | (addr & 0xFF)           # jump if no carry

def nop()  : return 0
def _in(ra): return ld(ra, 65) # INポートからレジスタへ読み込む。
def out(ra): return st(ra, 64) # レジスタからOUTポートへ書き出す。


# レジスタへ2バイト即値をロード(★2命令★)
def ld_hl(ra, two_bytes):
    return ldh(ra, two_bytes >> 8), \
           ldl(ra, two_bytes & 0xFF)


FILE_NAME = "rom_init.mif"
WIDTH = 15
DEPTH = 256
_ = [0] * DEPTH



################################################
########### アセンブリ(のようなもの) ###########
################################################

##################################
# directive、macro(のようなもの) #
##################################

F_CPU = 1E6

DELAY_40ms  =  40E-3
DELAY_20ms  =  20E-3
DELAY_2ms   =   2E-3
DELAY_100us = 100E-6
DELAY_50us  =  50E-6

# (3命令×4クロック + 2命令×4クロック×(LOOPS定数 + 1) + 1命令×4クロック) / F_CPU = delay
# LOOPS定数 = (delay * F_CPU - 16) / 8 - 1
LOOPS_for_40ms = constrain(math.ceil((DELAY_40ms   * F_CPU - 16) / 8 - 1), 2**16-1, 0)
LOOPS_for_2ms  = constrain(math.ceil((DELAY_2ms    * F_CPU - 16) / 8 - 1), 2**16-1, 0)
LOOPS_for_100us = constrain(math.ceil((DELAY_100us * F_CPU - 16) / 8 - 1), 2**16-1, 0)

FUNC_SET = 0b111000 # 1, 8/4 bits, 2/1 lines, 10/8 dots, don't care, don't care
DISP_CNT = 0b1100   # 1, 文字表示, カーソル表示, カーソル点滅
CLS      = 0b1      # 表示を消去
ENT_MOD  = 0b110    # 1, 右/左シフト, 表示範囲全体シフト有効化

LEFT_MOST = [0x80, 0xC0] # y行目x列目のアドレス = (x + y * 0x40) | (0x80)


r0, r1, r2, r3, r4, r5, r6, r7 = range(8) # 汎用レジスタ

OUT     = r0
RET_TO  = r1
TMP0    = r2
TMP1    = r3
VAL = r4
Q   = r5 # 商


# call命令 ★2命令★
# サブルーチンから戻ってくるアドレスを保存しておいてからサブルーチンへ飛ぶ、
def call(go_to, return_to):
    return ldl(RET_TO, return_to), \
           jmp(go_to)

# ret命令
def ret():
    return jmp_reg(RET_TO)

# カーソル位置(0ベース)をセットする命令
def set_RC(r, c):
    return ldl(OUT, LEFT_MOST[r] + c)

# 0xABCD (0d43981)を0xDCBA (0d56506)に変換する函数
def reverse_4nibles(two_bytes):
    return eval("0x" + ((str(hex(two_bytes))[2:])[::-1]))


###############
# subroutines #
###############

# DELAY_40MS:
DELAY_40MS = 248 # ~254
_[(pc := DELAY_40MS)], \
_[(pc := pc+1)] = ld_hl(TMP0, 1)                    # 減分をセットして、
_[(pc := pc+1)], \
_[(pc := pc+1)]       = ld_hl(TMP1, LOOPS_for_40ms) # ループ数-1をセットして、
_[(pc := pc+1)]       = sub(TMP1, TMP0)             # 実際にカウントダウンする。
_[(pc := pc+1)]       = jnc(pc)
_[(pc := pc+1)]       = ret()

# COMMAND_W:
COMMAND_W = 230 # ~244
_[(pc := COMMAND_W)] = out(OUT)
_[(pc := pc+1)]      = ldh(OUT, 0b00)
_[(pc := pc+1)]      = out(OUT)
_[(pc := pc+1)]      = ldh(OUT, 0b10)
_[(pc := pc+1)]      = out(OUT)
_[(pc := pc+1)]      = nop()
_[(pc := pc+1)]      = ldh(OUT, 0b00)
_[(pc := pc+1)]      = out(OUT)
_[(pc := pc+1)], \
_[(pc := pc+1)]      = ld_hl(TMP0, 1)               # 減分をセットして、
_[(pc := pc+1)], \
_[(pc := pc+1)]      = ld_hl(TMP1, LOOPS_for_100us) # ループ数-1をセットして、
_[(pc := pc+1)]      = sub(TMP1, TMP0)              # 実際にカウントダウンする。
_[(pc := pc+1)]      = jnc(pc)
_[(pc := pc+1)]      = ret()

# DELAY_2MS:
DELAY_2MS = 220 # ~226
_[(pc := DELAY_2MS)], \
_[(pc := pc+1)] = ld_hl(TMP0, 1)                    # 減分をセットして、
_[(pc := pc+1)], \
_[(pc := pc+1)]        = ld_hl(TMP1, LOOPS_for_2ms) # ループ数-1をセットして、
_[(pc := pc+1)]        = sub(TMP1, TMP0)            # 実際にカウントダウンする。
_[(pc := pc+1)]        = jnc(pc)
_[(pc := pc+1)]        = ret()

# DATA_W:
DATA_W = 205 # ~217
_[(pc := DATA_W)] = out(OUT)
_[(pc := pc+1)]   = ldh(OUT, 0b11)
_[(pc := pc+1)]   = out(OUT)
_[(pc := pc+1)]   = nop()
_[(pc := pc+1)]   = ldh(OUT, 0b01)
_[(pc := pc+1)]   = out(OUT)
_[(pc := pc+1)], \
_[(pc := pc+1)]   = ld_hl(TMP0, 1)               # 減分をセットして、
_[(pc := pc+1)], \
_[(pc := pc+1)]   = ld_hl(TMP1, LOOPS_for_100us) # ループ数-1をセットして、
_[(pc := pc+1)]   = sub(TMP1, TMP0)              # 実際にカウントダウンする。
_[(pc := pc+1)]   = jnc(pc)
_[(pc := pc+1)]   = ret()

# VAL_TO_ HEX:
VAL_TO_HEX = 190 # ~202
_[(pc := VAL_TO_HEX)] = mov(OUT, VAL)
_[(pc := pc+1)]       = ldl(TMP0, 0xF)
_[(pc := pc+1)]       = _and(OUT, TMP0)
_[(pc := pc+1)]       = ldl(TMP0, 10)             # 値が9より大きいかどうかを判断して、
_[(pc := pc+1)]       = mov(TMP1, OUT)
_[(pc := pc+1)]       = sub(TMP1, TMP0)
_[(pc := pc+1)]       = jnc(pc+5)
_[(pc := pc+1)]       = ldl(TMP0, ord('0') )      # 値が9以下であったらそれを文字コードに変換して、
_[(pc := pc+1)]       = add(OUT, TMP0)
_[(pc := pc+1)]       = jmp(pc+4)
_[(pc := pc+1)]       = ldl(TMP0, ord('A') - 0xA) # 値が10以上であったらそれを文字コードに変換して。
_[(pc := pc+1)]       = add(OUT, TMP0)
_[(pc := pc+1)]       = ret()

# SR_4:
VAL_SR_4 = 180 # ~184
_[(pc := VAL_SR_4)] = sr(VAL)
_[(pc := pc+1)]     = sr(VAL)
_[(pc := pc+1)]     = sr(VAL)
_[(pc := pc+1)]     = sr(VAL)
_[(pc := pc+1)]     = ret()

# DELAY_100US:
DELAY_100US = 170 # ~176
_[(pc := DELAY_100US)], \
_[(pc := pc+1)] = ld_hl(TMP0, 1)                      # 減分をセットして、
_[(pc := pc+1)], \
_[(pc := pc+1)]        = ld_hl(TMP1, LOOPS_for_100us) # ループ数-1をセットして、
_[(pc := pc+1)]        = sub(TMP1, TMP0)              # 実際にカウントダウンする。
_[(pc := pc+1)]        = jnc(pc)
_[(pc := pc+1)]        = ret()


################
# main routine #
################

ad = -1

# 最初にLCDをdisableにする。
_[(ad := ad+1)] = ldh(OUT, 0b00)
_[(ad := ad+1)] = out(OUT)

# CALL DELAY_40ms
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(DELAY_40MS, ad+3)

# Function Set
_[(ad := ad+1)] = ldl(OUT, FUNC_SET)
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(COMMAND_W, ad+3)

# Display ON/OFF Control
_[(ad := ad+1)] = ldl(OUT, DISP_CNT)
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(COMMAND_W, ad+3)

# Clear Display
_[(ad := ad+1)] = ldl(OUT, CLS)
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(COMMAND_W, ad+3)

# CALL DELAY_2ms
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(DELAY_2MS, ad+3)

# Entry Mode Set
_[(ad := ad+1)] = ldl(OUT, ENT_MOD)
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(COMMAND_W, ad+3)




RETURN_WHERE = ad+1
_[(ad := ad+1)] = _in(VAL)          # 割られる数(表示する数)をINポートから入力して、


# 一の位を取り出して、
_[(ad := ad+1)], \
_[(ad := ad+1)], = ld_hl(TMP1, 10) # 割る数をセットして、
_[(ad := ad+1)], \
_[(ad := ad+1)] = ld_hl(Q, 0)      # 商をリセットして、
_[(ad := ad+1)], \
_[(ad := ad+1)] = ld_hl(TMP0, 1)   # 増分をセットして、
_[(ad := ad+1)] = add(Q, TMP0)     # 引いた回数を先にインクリメントしてから、
_[(ad := ad+1)] = sub(VAL, TMP1)   # 実際に引いて、
_[(ad := ad+1)] = jnc(ad-1)        # キャリー(というかボロー)が発生していなければ、また引き算を繰り返すが、
_[(ad := ad+1)] = sub(Q, TMP0)     # キャリー(というかボロー)が発生していたらもう引けないので、1回多く引いたぶんだけ商を戻して、
_[(ad := ad+1)] = add(VAL, TMP1)   # 余りも1回分だけ戻して、

# 一の位を表示する。
_[(ad := ad+1)] = set_RC(0, 4)           # カーソル位置をセットして、
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(COMMAND_W, ad+3)
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(VAL_TO_HEX, ad+3) # 1の位を文字コードに変換して、
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(DATA_W, ad+3)     # LCDへ出力する。
_[(ad := ad+1)] = mov(VAL, Q)            # 次に割られる数をセットして、



# 十の位を取り出して、
_[(ad := ad+1)], \
_[(ad := ad+1)], = ld_hl(TMP1, 10)
_[(ad := ad+1)], \
_[(ad := ad+1)] = ld_hl(Q, 0)
_[(ad := ad+1)], \
_[(ad := ad+1)] = ld_hl(TMP0, 1)
_[(ad := ad+1)] = add(Q, TMP0)
_[(ad := ad+1)] = sub(VAL, TMP1)
_[(ad := ad+1)] = jnc(ad-1)
_[(ad := ad+1)] = sub(Q, TMP0)
_[(ad := ad+1)] = add(VAL, TMP1)

# 十の位を表示する。
_[(ad := ad+1)] = set_RC(0, 3)
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(COMMAND_W, ad+3)
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(VAL_TO_HEX, ad+3)
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(DATA_W, ad+3)
_[(ad := ad+1)] = mov(VAL, Q)



# 百の位を取り出して、
_[(ad := ad+1)], \
_[(ad := ad+1)], = ld_hl(TMP1, 10)
_[(ad := ad+1)], \
_[(ad := ad+1)] = ld_hl(Q, 0)
_[(ad := ad+1)], \
_[(ad := ad+1)] = ld_hl(TMP0, 1)
_[(ad := ad+1)] = add(Q, TMP0)
_[(ad := ad+1)] = sub(VAL, TMP1)
_[(ad := ad+1)] = jnc(ad-1)
_[(ad := ad+1)] = sub(Q, TMP0)
_[(ad := ad+1)] = add(VAL, TMP1)

# 百の位を表示する。
_[(ad := ad+1)] = set_RC(0, 2)
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(COMMAND_W, ad+3)
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(VAL_TO_HEX, ad+3)
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(DATA_W, ad+3)
_[(ad := ad+1)] = mov(VAL, Q)



# 千の位を取り出して、
_[(ad := ad+1)], \
_[(ad := ad+1)], = ld_hl(TMP1, 10)
_[(ad := ad+1)], \
_[(ad := ad+1)] = ld_hl(Q, 0)
_[(ad := ad+1)], \
_[(ad := ad+1)] = ld_hl(TMP0, 1)
_[(ad := ad+1)] = add(Q, TMP0)
_[(ad := ad+1)] = sub(VAL, TMP1)
_[(ad := ad+1)] = jnc(ad-1)
_[(ad := ad+1)] = sub(Q, TMP0)
_[(ad := ad+1)] = add(VAL, TMP1)

# 千の位を表示する。
_[(ad := ad+1)] = set_RC(0, 1)
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(COMMAND_W, ad+3)
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(VAL_TO_HEX, ad+3)
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(DATA_W, ad+3)
_[(ad := ad+1)] = mov(VAL, Q)



# 万の位を表示する。
_[(ad := ad+1)] = set_RC(0, 0)
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(COMMAND_W, ad+3)
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(VAL_TO_HEX, ad+3)
_[(ad := ad+1)], \
_[(ad := ad+1)] = call(DATA_W, RETURN_WHERE)


_[(ad := ad+1)] = jmp(255) # unreachable




_[255] = jmp(255) # unreachable

generate_mif(FILE_NAME, WIDTH, DEPTH, _)