FC2カウンター FPGAの部屋 2009年10月11日
FC2ブログ

FPGAやCPLDの話題やFPGA用のツールの話題などです。 マニアックです。 日記も書きます。

FPGAの部屋

FPGAの部屋の有用と思われるコンテンツのまとめサイトを作りました。Xilinx ISEの初心者の方には、FPGAリテラシーおよびチュートリアルのページをお勧めいたします。

CMOSカメラから画像を入力してディスプレイへ出力9(モデルの説明)

CMOSカメラ・ディスプレイ表示回路もだいぶシミュレーションが進んできた。今日は、モデルがどのように書いてあるかを書こうと思う。
最初にもう一度、全体のブロック図を下に示す。
Camera_Disp_4_090921.png

全体のブロック図のなかのCMOSカメラとSRAMのモデルを作成した。

まずはCMOSカメラのモデルから。CMOSカメラのモデルはモードが固定となっている。本当のCMOSカメラはI2Cでモードを設定することができるが、デフォルトの設定のまま使用するので、モデルもデフォルトの640x480ピクセル、30フレーム、UYVYデータ出力に固定されている。下のVHDLコードがCMOSカメラモデルだ。

-- OV7640_Model.vhd-- 現在のOV7640の仕様機能のみをモデル化する
-- Yデータは0から+1していく。UデータはFFから2つずつ減算(つまりずっと奇数)、VデータはFEから2つずつ減算(つまりずっと偶数)
-- clkに対してpclkは5ns程度遅延させる

library IEEE;
use IEEE.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;

entity OV7640_Model is
    port(
        clk : in std_logic; -- クロック入力
        n_reset : in std_logic; -- リセット入力
        vsync : out std_logic;
        href : out std_logic;
        pclk : out std_logic;
        ydata : out std_logic_vector(7 downto 0)
        -- sio_c : in std_logic;
        -- sio_d : inout std_logic
    );
end OV7640_Model;

architecture BEHAVIOR of OV7640_Model is
constant H_ACTIVE_VIDEO : integer := 640;
constant H_BLANK_SPACE : integer := 124;
constant H_SUM : integer := H_ACTIVE_VIDEO + H_BLANK_SPACE;

constant V_ACTIVE_VIDEO : integer := 480;
constant V_FRONT_PORCH : integer := 31;
constant V_SYNC_PULSE : integer := 3;
constant V_BACK_PORCH : integer := 11;
constant V_SUM : integer := V_ACTIVE_VIDEO + V_FRONT_PORCH + V_SYNC_PULSE + V_BACK_PORCH;

signal h_count : unsigned(10 downto 0); -- 水平カウンタ
signal v_count : unsigned(9 downto 0); -- 垂直カウンタ
signal u_data : unsigned(7 downto 0);
signal v_data : unsigned(7 downto 0);
signal y_data : unsigned(7 downto 0);
signal reset : std_logic;
signal vsync_node : std_logic;
signal ydata_node : std_logic_vector(7 downto 0);
signal pclock : std_logic;
signal href_node : std_logic;
begin
    reset <= not n_reset;
    pclock <= transport clk after 5 ns;
    pclk <= pclock;
    
    -- 水平タイミング生成
    process(reset, pclock) begin
        if reset='1' then
            h_count <= (others => '0');
        elsif pclock'event and pclock='1' then
            if h_count(10 downto 1) < H_SUM-1 then
                h_count <= h_count + 1;
            else -- CMOSカメラはUYなどと2クロックで1画素のため
                h_count <= (others => '0');
            end if;
        end if;
    end process;
    href_node <= '1' when h_count<H_ACTIVE_VIDEO and v_count<V_ACTIVE_VIDEO else '0';
    href <= href_node after 1 ns;
    
    -- 垂直タイミング
    process(reset, pclock) begin
        if reset='1' then
            v_count <= unsigned(CONV_STD_LOGIC_VECTOR((V_ACTIVE_VIDEO + V_FRONT_PORCH)-1,10));
        elsif pclock'event and pclock='1' then
            if h_count(10 downto 0)=H_SUM-1 then
                if v_count < V_SUM-1 then
                    v_count <= v_count + 1;
                else
                    v_count <= (others => '0');
                end if;
            end if;
        end if;
    end process;
    
    -- vsync の出力タイミング
    process(v_count) begin
        if v_count>=(V_ACTIVE_VIDEO + V_FRONT_PORCH)-1 then
            if v_count<V_ACTIVE_VIDEO + V_FRONT_PORCH + V_SYNC_PULSE then
                vsync_node <= '1';
            else
                vsync_node <= '0';
            end if;
        else
            vsync_node <= '0';
        end if;
    end process;
    vsync <= vsync_node after 1 ns;
    
    -- ydata の出力
    process(reset, pclock) begin -- Yデータ
        if reset='1' then
            y_data <= (others => '0');
        elsif pclock'event and pclock='1' then
            if h_count(10 downto 1)<H_ACTIVE_VIDEO and h_count(0)='1' then -- 水平の表示区間で2クロックに1回カウントアップ
                y_data <= y_data + 1;
            elsif h_count=0 and v_count=0 then -- 1フレーム描画の最初にリセット
                y_data <= (others => '0');
            end if;
        end if;
    end process;
    
    process(reset, pclock) begin -- Uデータ
        if reset='1' then
            u_data <= (others => '1');
        elsif pclock'event and pclock='1' then
            if h_count(10 downto 1)<H_ACTIVE_VIDEO and h_count(1 downto 0)=0 then -- 水平の表示区間で4クロックに1回-2
                u_data <= u_data - 2;
            elsif h_count=0 and v_count=0 then -- 1フレーム描画の最初にリセット
                u_data <= (others => '1');
            end if;
        end if;
    end process;
    
    process(reset, pclock) begin -- Vデータ
        if reset='1' then
            v_data <= X"FE";
        elsif pclock'event and pclock='1' then
            if h_count(10 downto 1)<H_ACTIVE_VIDEO and h_count(1 downto 0)=2 then -- 水平の表示区間で4クロックに1回-2
                v_data <= v_data - 2;
            elsif h_count=0 and v_count=0 then -- 1フレーム描画の最初にリセット
                v_data <= X"FE";
            end if;
        end if;
    end process;

    process(h_count, y_data, u_data, v_data) begin
        if h_count(10 downto 1)<H_ACTIVE_VIDEO then -- 表示区間
            case h_count(1 downto 0) is
                when "00" =>
                    ydata_node <= std_logic_vector(u_data);
                when "01" =>
                    ydata_node <= std_logic_vector(y_data);
                when "10" =>
                    ydata_node <= std_logic_vector(v_data);
                when others => -- "11"
                    ydata_node <= std_logic_vector(y_data);
            end case;
        else
            ydata_node <= (others => '0');
        end if;
    end process;
    ydata <= ydata_node after 1 ns;
end BEHAVIOR;


当然ながら、h_countとv_countを持って、水平と垂直をカウントしている。それでVSYNCとHREFを出力する。シミュレーション用に最初の方にVSYNCが出て、その後にHREFが出るようにしている。そうでないとSYNCHRONIZERで同期するのに時間がかかりすぎてしまう。
Yデータは0からカウントアップ、UデータはUデータはFFから2つずつ減算(つまりずっと奇数)、VデータはFEから2つずつ減算(つまりずっと偶数)となる。これはシミュレーションの時にわかりやすくするため。つまり、Yではなく間違ってU, Vを受け取っていても、すぐにわかるようにしている。
clkに対してpclkは5ns程度遅延させている。

次に昨日も少し書いたが、SRAMのモデル。最初は256KX8を2つインスタンシエーションしていたが、メモリを食いすぎるので、64KX2を2つに変更した。わざわざbit_vector にしたのだが、やはりメモリを食うのか?下にVHDLソースを示す。

-- IS61LV25616_model.vhd
--
-- IS61LV25616のモデル。タイミング関係は全く考慮せずに動作だけをモデル化する
-- 256KB入れると厳しいので、1/4にした

library IEEE;
use IEEE.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;

library modelsim_lib;
use modelsim_lib.util.all;

entity IS61LV25616 is
    port(
        mem_data : inout std_logic_vector(15 downto 0);    -- SRAMのmemory data
        n_mem_we : in std_logic; -- SRAMのmemory write enable
        n_mem_rd : in std_logic; -- SRAMのmemory read enable
        n_mem_cs : in std_logic; -- SRAMのchip select 0
        n_mem_lowerB : in std_logic; -- SRAMのmemory lower byte enable
        n_mem_upperB : in std_logic; -- SRAMのmemory upper byte enable
        mem_addr : in std_logic_vector(17 downto 0) -- SRAMのmemory address
    );
end IS61LV25616;

architecture RTL of IS61LV25616 is
type SRAM256K is array (0 to 65535) of bit_vector(7 downto 0);
signal sram_upper : SRAM256K;
signal sram_lower : SRAM256K;
signal mem_addr_int : integer range 0 to 65535;
signal mem_data_node : std_logic_vector(15 downto 0);
signal mem_data_node2 : std_logic_vector(15 downto 0);
signal master_sync : std_logic;

begin
    mem_addr_int <= CONV_INTEGER(mem_addr(15 downto 0));
    
    -- Write Upper
    process(n_mem_we, n_mem_cs, n_mem_upperB, mem_data, mem_addr_int) begin
        if n_mem_we='0' and n_mem_cs='0' and n_mem_upperB='0' then
            sram_upper(mem_addr_int) <= To_bitvector(mem_data(15 downto 8));
        end if;
    end process;
    
    -- Write Lower
    process(n_mem_we, n_mem_cs, n_mem_lowerB, mem_data, mem_addr_int) begin
        if n_mem_we='0' and n_mem_cs='0' and n_mem_lowerB='0' then
            sram_lower(mem_addr_int) <= To_bitvector(mem_data(7 downto 0));
        end if;
    end process;
    
    -- -- Read Upper
    -- process(n_mem_cs, n_mem_rd, n_mem_upperB, mem_addr_int) begin
        -- if n_mem_cs='0' and n_mem_rd='0' and n_mem_upperB='0' then
            -- mem_data_node(15 downto 8) <= To_stdlogicvector(sram_upper(mem_addr_int));
        -- else
            -- mem_data_node(15 downto 8) <= (others => 'Z');
        -- end if;
    -- end process;
    
    -- -- Read Lower
    -- process(n_mem_cs, n_mem_rd, n_mem_lowerB, mem_addr_int) begin
        -- if n_mem_cs='0' and n_mem_rd='0' and n_mem_lowerB='0' then
            -- mem_data_node(7 downto 0) <= To_stdlogicvector(sram_lower(mem_addr_int));
        -- else
            -- mem_data_node(7 downto 0) <= (others => 'Z');
        -- end if;
    -- end process;
    
    -- init_signal_spy でIS61LV25616_instの下にmaster_syncを持ってくる
    process begin
        init_signal_spy("../camdispcntrl_sram_inst/master_sync", "master_sync", 1, -1);
        wait;
    end process;
        
    -- データの流れを見やすいように上位のデータは55から+1, 下位のデータはAAから+1して、出力
    process(master_sync, n_mem_we) begin -- Read Upper
        if master_sync='1' then
            mem_data_node2(15 downto 8) <= x"55";
        elsif n_mem_we'event and n_mem_we='1' then
            if n_mem_cs='0' and n_mem_upperB='0' then
                mem_data_node2(15 downto 8) <= mem_data_node2(15 downto 8) + 1;
            end if;
        end if;
    end process;
    mem_data_node(15 downto 8) <= mem_data_node2(15 downto 8) when n_mem_cs='0' and n_mem_rd='0' and n_mem_upperB='0' else (others => 'Z');
        
    process(master_sync, n_mem_we) begin -- Read Lower
        if master_sync='1' then
            mem_data_node2(7 downto 0) <= x"AA";
        elsif n_mem_we'event and n_mem_we='1' then
            if n_mem_cs='0' and n_mem_lowerB='0' then
                mem_data_node2(7 downto 0) <= mem_data_node2(7 downto 0) + 1;
            end if;
        end if;
    end process;
    mem_data_node(7 downto 0) <= mem_data_node2(7 downto 0) when n_mem_cs='0' and n_mem_rd='0' and n_mem_lowerB='0' else (others => 'Z');
        
    mem_data <= mem_data_node after 1 ns;
end RTL;


CMOSカメラの転送レートはディスプレイ表示回路の半分なので、RGB出力は最初の垂直フレームはずっと0のままになってしまう。そうすると40msくらいシミュレーションしないとだめということになる。それかクロックを驚異的に速めるという方法もあるな~。
そこでSRAMモデルの方でReadの時に、SRAMに書き込まれたデータではなく、自分で生成したデータを出して回路をデバックしようと思った。(昨日の引用)つまり、データの流れを見やすいように上位のデータは55から+1, 下位のデータはAAから+1して、出力することにした。

このようにモデルを書いた。次はシミュレーション波形の説明をしたい。

#今日は、子供会の旅行でディズニーシーに行きます。子供会の幹事なので行くのですが、家の子供は誰も行かないで夫婦だけです。(変ですよね)ウォーキングしてきます。。。

(2009/10/13:追記)
IS61LV25616_model.vhd が間違っていたので修正しました。
(2009/10/14:修正)
 OV7640_Model.vhd のv_countが2倍になっていたので修正。
  1. 2009年10月11日 05:21 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:5