概要
CPUのOUTポートから出力した16ビット値(0x0000~0xFFFF)を10進数表記(0~65535)でキャラクターLCDに表示してみる。
前回のセクション4.8、4.9、4.10のときはCPU側でソフトウェア的に解決した処理を、今度は外づけ回路として作る。CPUで制御するわけではないので、CPUとはまったく関係ない。
2進数→ BCD変換回路については、テキスト(pp.221ff.)はかなり大がかりなVHDLを記述しているが、ここではシンプルにdouble dabbleアルゴリズムを利用する。
2進数 → BCD変換回路
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に表示するための回路
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で扱いがある。
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が表示されれば正解。