2016年10月26日水曜日

FPGAでHDMIの液晶モニタに画を映す (6) 最終回

安価なFPGAでHDMIの液晶モニタに画像を映すシリーズは今回で最終回です.

こんな感じに表示されてます.720x480、8bit RGBを縦横斜めにインクリメントしてるのでこんな画像が表示されます.

全面的に参考にさせていただいたのが、こちらのページです.verilogソースも引用させていただきました.ありがとうございます.多謝、多謝!


画像を表示させてみて判ったことは、

1) HDMIケーブルを流れるクロック周波数
前回、誤解してたと書いたとおり、HDMIケーブルを流れるクロックは、チャネルクロック(ex.250MHz)ではなくキャラクタクロック(ex.25MHz)でした.クロック周波数が低いのはうれしいけど、液晶モニタ側でx10倍の周波数UPが必要になります.

2) HSYNC/VSYNCの幅は、1キャラクタなのか? それとも幅を持っているのか?
simulationではこうなってます.つまり、1キャラクタではなくて、幅を持っています.どれだけの幅が必要なのかは不明です.引用したverilog sourceのパラメータを踏襲しただけです.基本的には試行錯誤で決めりゃいいのかと思ってはいますが.
clkp   キャラクタクロック
hd     HSYNC
vd     VSYNC


以下はverilog ソースコードです.

top module
HDMIケーブルに配線するのは、全て差動信号で、clkp/clkm,rp/rm,gp/gm,bp/gm です.
200ΩをシリーズにしてHDMIケーブルに接続します.
おおまかには、4つのブロックから成ります.
・clk270R27      XTAL 50MHz → 270.27MHz をつくります
・testpattern    720x480の画像を作ります.HSYNC/VSYNCも作ります
・dvi_enc          HDMI規格の8/10変換
・10:1シリアル変換     LSB firstでHDMIケーブルに出力

clk270R27は、Xilinxの設計ツールのcoregenで生成していますので、black boxです.

各moduleのclockにはチャネルクロック=270MHzを与えています.ところが多くのmoduleはキャラクタクロックで動作するので、1/10 dutyのen10でenable制御しています.(clockを一本化したかったため)

============= top module ここから ===============
`timescale 1ns / 1ps

module top(
    input clki,
    input xrst,
    output reg clkp, // character clock
    output reg clkm,
    output rp,
    output rm,
    output gp,
    output gm,
    output bp,
    output bm,
output [3:0] LED
    );

wire chclk;
clk270R27 HDMIclk (
    .CLKIN_IN(clki), //50MHz
    .RST_IN(~xrst),
    .CLKFX_OUT(chclk) // 270.27MHz
    );

// channel counter
reg [3:0] r;
always @(posedge chclk or negedge xrst)
if(!xrst) r <= 0;
else if(r==9) r<=0;
else r<=r+1;

// character enabler
reg en10;
always @(posedge chclk or negedge xrst)
if(!xrst) en10<=0;
else en10<=(r==0);

// character clock output
always @(posedge chclk or negedge xrst)
if(!xrst)     begin clkp<=0; clkm<=1; end
else if(r==0) begin clkp<=1; clkm<=0; end
else if(r==5) begin clkp<=0; clkm<=1; end

// test pattern generation
wire [7:0] red, grn, blu;
wire den, hd, vd;
testpattern testpattern (
    .clk(chclk),
.en(en10),
.xrst(xrst),
    .red(red),
    .grn(grn),
    .blu(blu),
    .den(den),
    .hd(hd),
    .vd(vd)
    );

// encoder
wire [29:0] tmds; // ={r[9:0],g[9:0],b[9:0]}
dvi_enc dvi_enc (
    .clk(chclk),
.en(en10),
.xrst(xrst),
    .red_in(red),
    .grn_in(grn),
    .blu_in(blu),
    .den_in(den),
    .hd_in(hd),
    .vd_in(vd),
    .tmds_out(tmds)
    );

// shift register
reg [29:0] tmdsp, tmdsm;
always @(posedge chclk or negedge xrst)
if(!xrst) begin tmdsp<=0; tmdsm<=~0; end
else if(en10) begin tmdsp<=tmds; tmdsm<=~tmds; end
else begin tmdsp<={1'b0,tmdsp[29:1]}; tmdsm<={1'b1,tmdsm[29:1]}; end

// LSB first serial output
assign rp =tmdsp[20];
assign rm =tmdsm[20];
assign gp =tmdsp[10];
assign gm =tmdsm[10];
assign bp =tmdsp[0];
assign bm =tmdsm[0];

// LED indicator
reg [27:0] LEDr;
always @(posedge chclk or negedge xrst)
if(!xrst) LEDr<=0;
else LEDr<=LEDr+1;
assign LED[0] = LEDr[25];
assign LED[1] = LEDr[20];
assign LED[2] = LEDr[15];
assign LED[3] = LEDr[10];

endmodule
============= top module ここまで ===============


testpattern
720x480のパラメータと、640x480のパラメータが書かれていて、640x480はコメントアウトしてあります.640x480で動かしたければ、720x480をコメントアウトすると同時に、topのclockを250MHzにする必要があります.

always文の中では、ごちょごちょと計算して、走査線データをRGB+SYNCの形で生成しています.
preambleとguard bandを発生する機能が在りません.それでもHDMIは動くという証明かと.

shiftという変数を0固定してあります.shiftをインクリメントさせると、画面がゾゾーッと動きます.目が回るので0固定にしました.

============= testpattern  ここから ===============
module testpattern(
  input  wire clk, en, xrst,
  output reg  [7:0] red, grn, blu,
  output reg  den, hd, vd
);

//Vact TtlLines Vblank VFreq HFreq PixeFreq Httl Hact Hblank
// 480     525    45   60.0  31.50  27.027   858  720  138
//720x480   858x525x60=27.027MHz
  wire [15:0] hsync = 16'd40;
  wire [15:0] hbp   = 16'd40;
  wire [15:0] hdata = 16'd720;
  wire [15:0] hfp   = 16'd58;
  wire hsp   = 1;
  wire [15:0] vsync = 16'd2;
  wire [15:0] vbp   = 16'd33;
  wire [15:0] vdata = 16'd480;
  wire [15:0] vfp   = 16'd10;
  wire vsp   = 1;

//Vact TtlLines Vblank VFreq HFreq PixeFreq Httl Hact Hblank
// 480     525    45   60.0  31.50  25.200   800  640  160
//VGA 640x480   800x525x60=25.2MHz
/*
  wire [15:0] hsync = 16'd96;
  wire [15:0] hbp   = 16'd48;
  wire [15:0] hdata = 16'd640;
  wire [15:0] hfp   = 16'd16;
  wire hsp   = 1;
  wire [15:0] vsync = 16'd2;
  wire [15:0] vbp   = 16'd33;
  wire [15:0] vdata = 16'd480;
  wire [15:0] vfp   = 16'd10;
  wire vsp   = 1;
*/
  reg  [15:0] hcnt, vcnt;
  reg  [ 7:0] shift;

  always@(posedge clk or negedge xrst)
  if(!xrst) begin hcnt<=0; vcnt<=0; shift<=0; red<=0; grn<=0; blu<=0; den<=0; hd<=0; vd<=0; end
  else if(en) begin
    if(hcnt<hsync+hbp+hdata+hfp-16'd1) begin
      hcnt <= hcnt + 1;
    end
else begin
      hcnt <= 0;
      if(vcnt<vsync+vbp+vdata+vfp-16'd1) begin
        vcnt <= vcnt + 1;
      end
else begin
        vcnt <= 0;
        shift <= shift + 0;  // stop to shifting
      end
    end
    if(hcnt<hsync) hd <= hsp; else hd <= !hsp;
    if(vcnt<vsync) vd <= vsp; else vd <= !vsp;
    if(hsync+hbp<=hcnt && hcnt<hsync+hbp+hdata && vsync+vbp<=vcnt && vcnt<vsync+vbp+vdata) begin
      red <= (hcnt-hsync-hbp+shift)&8'hFF;
      grn <= (vcnt-vsync-vbp+shift)&8'hFF;
      blu <= (hcnt-hsync-hbp+vcnt-vsync-vbp-shift)&8'hFF;
      den <= 1;
    end
else begin
      den <= 0;
    end
  end

endmodule
============= testpattern ここまで ===============


dvi_enc
testpatternを入力され、RGB各々の8/10変換をするところです.

HSYNC/VSYNCは、BLUE channelにのみ入力されます.

AUDIOは存在しません.

============= dvi_enc ここから ===============
module dvi_enc(
  input  wire clk, en, xrst,
  input  wire [7:0] red_in,
  input  wire [7:0] grn_in,
  input  wire [7:0] blu_in,
  input  wire den_in, hd_in, vd_in,
  output wire [29:0] tmds_out
);

  wire [9:0] r,g,b;
  assign tmds_out = {r,g,b};

  tmds_enc ENC0(blu_in, hd_in, vd_in, den_in, clk, en, xrst, b);
  tmds_enc ENC1(grn_in, 1'b0 , 1'b0 , den_in, clk, en, xrst, g);
  tmds_enc ENC2(red_in, 1'b0 , 1'b0 , den_in, clk, en, xrst, r);
endmodule
============= dvi_enc ここまで ===============


tmds_enc
冒頭で引用したサイトからダウンロードしました.このmoduleについては一文字も変更してません.
dvi_enc.v
RGB用の8/10変換が実装されています.
SYNC用の2/10変換が実装されています.
AUDIOは伝送しないので、4/10変換は実装されていません.

それと、preambleとguard bandを発生する機能が在りません.HDMIはそれでも動くっていうことの証明かと思います.


制約ファイル
・ピン設定
・外付けXTAL=50MHzのタイミング設定
・マルチサイクルパス設定
をやっています.

マルチサイクルパス設定の意図は、clockに270MHzを与えているけど、27MHzで動けばOKなので制約を緩くする為です.クロックを一本化にこだわったのでこのような部分にしわ寄せが....

============= ここから ===============
NET "clki"   LOC = "P129" |IOSTANDARD = LVCMOS33 | PERIOD = 20 ns HIGH 50%;
NET "xrst"   LOC = "P69"  |IOSTANDARD = LVCMOS33 ;
NET "clkp"   LOC = "P87"  |IOSTANDARD = LVCMOS33 |DRIVE = 12 |SLEW = FAST ;
NET "clkm"   LOC = "P88"  |IOSTANDARD = LVCMOS33 |DRIVE = 12 |SLEW = FAST ;
NET "rp"     LOC = "P91"  |IOSTANDARD = LVCMOS33 |DRIVE = 12 |SLEW = FAST ;
NET "rm"     LOC = "P92"  |IOSTANDARD = LVCMOS33 |DRIVE = 12 |SLEW = FAST ;
NET "gp"     LOC = "P93"  |IOSTANDARD = LVCMOS33 |DRIVE = 12 |SLEW = FAST ;
NET "gm"     LOC = "P94"  |IOSTANDARD = LVCMOS33 |DRIVE = 12 |SLEW = FAST ;
NET "bp"     LOC = "P96"  |IOSTANDARD = LVCMOS33 |DRIVE = 12 |SLEW = FAST ;
NET "bm"     LOC = "P97"  |IOSTANDARD = LVCMOS33 |DRIVE = 12 |SLEW = FAST ;
NET "LED<0>" LOC = "P52"  |IOSTANDARD = LVCMOS33 |DRIVE = 8  |SLEW = SLOW ;
NET "LED<1>" LOC = "P53"  |IOSTANDARD = LVCMOS33 |DRIVE = 8  |SLEW = SLOW ;
NET "LED<2>" LOC = "P54"  |IOSTANDARD = LVCMOS33 |DRIVE = 8  |SLEW = SLOW ;
NET "LED<3>" LOC = "P58"  |IOSTANDARD = LVCMOS33 |DRIVE = 8  |SLEW = SLOW ;

TIMESPEC TS_270MHz = PERIOD 3.7 ns HIGH 50%;

INST testpattern/* TNM = SOURCE1;
INST testpattern/* TNM = DESTINATION1;
TIMESPEC TS_TESTPATTERN = FROM SOURCE1 TO DESTINATION1 TS_270MHz*10;

INST dvi_enc/* TNM = SOURCE2;
INST dvi_enc/* TNM = DESTINATION2;
TIMESPEC TS_ENC = FROM SOURCE2 TO DESTINATION2 TS_270MHz*10;
============= ここまで ===============


以上の情報があれば、大抵の方には再現できると思います.
文字を表示させたいならば、各位で実装をお願いします.

その5へ戻る


続くシリーズで、中華LCDパネルにHDMI-IFをつける試作を行いましたのでそちらも参考になるかもしれませんのでよろしく.

かしこ

0 件のコメント:

コメントを投稿