『動かしてわかるCPUの作り方10講』(技術評論社)第9講までをFPGA MAX10 + 回路図エディターで作る(その2) / 「2進数→ BCD変換回路」「キャラクターLCD表示回路」を追加する

#概要
CPUのOUTポートから出力した16ビット値(0x0000～0xFFFF)を10進数表記(0～65535)でキャラクターLCDに表示してみる。                       

[前回](https://qiita.com/cyq04000/items/db60e7e08929115f1d2e)のセクション[4.8](https://qiita.com/cyq04000/items/db60e7e08929115f1d2e#48%E3%82%AD%E3%83%A3%E3%83%A9%E3%82%AF%E3%82%BF%E3%83%BClcd%E3%81%A7hello-world)、[4.9](
https://qiita.com/cyq04000/items/db60e7e08929115f1d2e#49%E3%83%AC%E3%82%B8%E3%82%B9%E3%82%BF%E3%81%AE16%E3%83%93%E3%83%83%E3%83%88%E5%80%A4%E3%82%92hex%E5%80%A4%E3%81%A8%E3%81%97%E3%81%A6%E3%82%AD%E3%83%A3%E3%83%A9%E3%82%AF%E3%82%BF%E3%83%BClcd%E3%81%AB%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B)、[4.10](https://qiita.com/cyq04000/items/db60e7e08929115f1d2e#410%E3%83%AC%E3%82%B8%E3%82%B9%E3%82%BF%E3%81%AE16%E3%83%93%E3%83%83%E3%83%88%E5%80%A4%E3%82%92%E5%8D%81%E9%80%B2%E6%95%B0%E5%80%A4%E3%81%A8%E3%81%97%E3%81%A6%E3%82%AD%E3%83%A3%E3%83%A9%E3%82%AF%E3%82%BF%E3%83%BClcd%E3%81%AB%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B)のときはCPU側でソフトウェア的に解決した処理を、今度は外づけ回路として作る。CPUで制御するわけではないので、CPUとはまったく関係ない。   

2進数→ BCD変換回路については、テキスト\(pp.221ff.\)はかなり大がかりなVHDLを記述しているが、ここではシンプルにdouble dabbleアルゴリズムを利用する。  


#2進数 → BCD変換回路
あとでそのままパッケージに移せるよう函数として作った。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/f989b8aa-a452-f04b-6f98-f51166678cb3.png)

```bin_to_bcd.vhd
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity bin_to_bcd is
	generic(
		NUM_IN_BITS : natural := 16;
		NUM_OUT_BITS: natural := 20 -- must be a multiple of 4.
	);
	port(
		bin_in : in  std_logic_vector(NUM_IN_BITS-1 downto 0);
		bcd_out: out std_logic_vector(NUM_OUT_BITS-1 downto 0)
	);
end entity;

architecture rtl of bin_to_bcd is

	function slv_to_bcd(
		slv         : std_logic_vector;
		num_in_bits : natural;
		num_out_bits: natural) -- must be a multiple of 4.
		return std_logic_vector
	is
		constant NUM_BCD: natural := num_out_bits/4;
		variable bcd: unsigned(num_out_bits-1 downto 0) := (others => '0');
	begin
		for i in num_in_bits-1 downto 1 loop
			bcd := bcd(num_out_bits-2 downto 0) & slv(i);
			for j in NUM_BCD downto 1 loop
				if bcd(j*4-1 downto j*4-4) > 4 then
					bcd(j*4-1 downto j*4-4) := bcd(j*4-1 downto j*4-4) + 3;
				end if;
			end loop;
		end loop;
		bcd := bcd(num_out_bits-2 downto 0) & slv(0);
		return std_logic_vector(bcd);
	end function;

begin
	bcd_out <= slv_to_bcd(bin_in, NUM_IN_BITS, NUM_OUT_BITS);
end architecture;
```

#20ビット値を16進数5桁でキャラクターLCDに表示するための回路
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/a7c65496-22b7-7572-35f7-36afbbc77a2d.png)

```char_LCD_5_digits.vhd
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity char_LCD_5_digits is
    generic(
        F_CLK : natural := 1_000_000
    );
    port(
        clk   : in std_logic;
        rst_n : in std_logic;
        bin_in: in std_logic_vector(19 downto 0);

        E : buffer std_logic;
        RS: out    std_logic;
        DB: out    std_logic_vector(7 downto 4)
    );
end entity;

architecture rtl of char_LCD_5_digits is

    type state_type is (
        -- Arrange these states in this order, starting from the top of the list.
        ReturnHome_H,
        W1H, W2H, W3H, W4H, W5H,
        W1L, W2L, W3L, W4L, W5L, 
        ---------------------------------------

        power_on_reset1, power_on_reset8,

        FunctionSet1, FunctionSet2, FunctionSet3,
        FunctionSet4, FunctionSet5, FunctionSet6,

        ClearDisplay_H, DisplayControl_H, EntryMode_H,
        ClearDisplay_L, DisplayControl_L, EntryMode_L,

        ReturnHome_L
    );

    signal pr_state, nx_state: state_type;
    --attribute syn_encoding: string;
    --attribute syn_encoding of state_type: type is "compact";

    --attribute keep: boolean;
    --attribute keep of pr_state, nx_state: signal is true;

    function upper_4(num: std_logic_vector(3 downto 0))
        return std_logic_vector is
    begin
        if   unsigned(num)<10 then return "0011"; -- when 0-9
        else                       return "0100"; -- when A-F
        end if;
    end function;

    function lower_4(num: std_logic_vector(3 downto 0))
        return std_logic_vector is
    begin
        if   unsigned(num)<10 then return num;         -- when 0-9
        else return std_logic_vector(unsigned(num)-9); -- when A-F
        end if;
    end function;

begin

   -- generate a clock signal whose period is 2 ms or more.
    process(clk)
        constant count_max: natural := F_CLK / 900;
        variable count: natural range 0 to count_max;
    begin
        if rising_edge(clk) then
            count := count + 1;
            if count = count_max then
                count := 0;
                E <= not E;
            end if;
        end if;
    end process;

   -- a register for keeping a state.
    process(E, rst_n)
    begin
        if    rst_n='0'      then pr_state <= power_on_reset1;
        elsif rising_edge(E) then pr_state <= nx_state;
        end if;
    end process;

   -- a combinational logic circuit for deciding a next state.
    process(all)
    begin
        RS <= '0';
        DB <= "0011";

        case pr_state is
            when power_on_reset1 =>
                for i in 1 to 7 loop
                    if    i < 7 then nx_state <= power_on_reset1;
                    elsif i = 7 then nx_state <= power_on_reset8;
                    end if;
                end loop;

            when power_on_reset8 => nx_state <= FunctionSet1;

            when FunctionSet1 => nx_state <= FunctionSet2;
            when FunctionSet2 => nx_state <= FunctionSet3;
            when FunctionSet3 => nx_state <= FunctionSet4;

            when FunctionSet4 => DB <= "0010"; nx_state <= FunctionSet5;
            when FunctionSet5 => DB <= "0010"; nx_state <= FunctionSet6;
            when FunctionSet6 => DB <= "1000"; nx_state <= ClearDisplay_H;

            when ClearDisplay_H => DB <= "0000"; nx_state <= ClearDisplay_L;
            when ClearDisplay_L => DB <= "0001"; nx_state <= DisplayCOntrol_H;

            when DisplayControl_H => DB <= "0000"; nx_state <= DisplayControl_L;
            when DisplayControl_L => DB <= "1100"; nx_state <= EntryMode_H;

            when EntryMode_H => DB <= "0000"; nx_state <= EntryMode_L;
            when EntryMode_L => DB <= "0110"; nx_state <= W5H; -- first, display a highest-order digit.

            when W1H to W5H => -- states 1-5
                RS <= '1';
                DB <= upper_4(bin_in(state_type'pos(pr_state)*4-1 downto state_type'pos(pr_state)*4-4));
                nx_state <= state_type'val(state_type'pos(pr_state)+5);
            when W1L to W5L => -- states 6-10
                RS <= '1';
                DB <= lower_4(bin_in((state_type'pos(pr_state)-5)*4-1 downto (state_type'pos(pr_state)-5)*4-4));
                nx_state <= state_type'val(state_type'pos(pr_state)-6);

            when ReturnHome_H => DB <= "1000"; nx_state <= ReturnHome_L; -- state 0
            when ReturnHome_L => DB <= "0000"; nx_state <= W5H;

            when others => nx_state <= power_on_reset1;
        end case;
    end process;

end architecture;
```


#1～150の総和を10進数(=0d11325)でキャラクターLCDに表示してみる
##全体図

`counter_16bits`は、CPU (下図では`modules`)の動作を極端に遅くして計算経過が見えるようにするため分周器。キャラクターLCDは[NHD-0108FZ-FL-YBW-33V3](https://www.newhavendisplay.com/nhd0108fzflybw33v3-p-5155.html)を使った。フォントサイズが小型7セグメントLEDほどもある。マルツ、Digi-Keyで扱いがある。   

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193936/dacdfb57-eaf5-9fd8-c00e-eefa7b003d1f.png)

##ROMの初期化ファイル.mifを生成するためのPythonスクリプト:
```python3
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 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 jnc(addr):     return 15 << 11 | (addr & 0xFF)           # jump if no carry

def nop()  : return 0          # mov(0, 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)


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


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


total        = r0
inc          = r1
operand      = r2
num_of_loops = r3

ad = -1

# 増分をセットして、
_[(ad := ad+1)], \
_[(ad := ad+1)] = ld_hl(inc, 1)

# ループ回数をセットして、
_[(ad := ad+1)], \
_[(ad := ad+1)] = ld_hl(num_of_loops, 150)

jump_where = ad+1
_[(ad := ad+1)] = add(operand, r1)           # 足す数をインクリメントして、
_[(ad := ad+1)] = add(total, operand)        # それをここまでの総和に加算して、
_[(ad := ad+1)] = out(total)                 # 確認のためここまでの総和をOUTポートから出力して、
_[(ad := ad+1)] = cmp(operand, num_of_loops) # 足す数 = ループ回数であるなら、
_[(ad := ad+1)] = je(255)                    # ループを抜けて、
_[(ad := ad+1)] = jmp(jump_where)            # そうでなければ加算を繰り返して、

_[255] = jmp(255)  # ここにとどまる。

generate_mif(FILE_NAME, WIDTH, DEPTH, _)
```

##実行結果
最終的に11325が表示されれば正解。          
<iframe width="560" height="315" src="https://www.youtube.com/embed/FK3SRV64XOU" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>


#ファイル一式
https://github.com/ti-nspire/MAX_V_MAX10/tree/main/cpu15_mega_ram_symbol_ver2