LoginSignup
1
0

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-08-26

概要

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

前回のセクション4.84.94.10のときはCPU側でソフトウェア的に解決した処理を、今度は外づけ回路として作る。CPUで制御するわけではないので、CPUとはまったく関係ない。

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

2進数 → BCD変換回路

あとでそのままパッケージに移せるよう函数として作った。
image.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

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を使った。フォントサイズが小型7セグメントLEDほどもある。マルツ、Digi-Keyで扱いがある。

image.png

ROMの初期化ファイル.mifを生成するためのPythonスクリプト:

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が表示されれば正解。

ファイル一式

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0