(原創) 如何在DE2將CCD影像顯示在彩色LCD上? (Nios II軟體篇 + onchip memory) (IC Design) (DE2) (Nios II) (SOPC Builder) (TRDB-LCM)
Abstract
前一篇(原創) 如何在DE2將CCD影像顯示在彩色LCD上? (純硬體篇) (IC Design) (DE2) 討論了使用Verilog純硬體的方式實作簡易的數位相機,為了實現SOC和軟硬體整合,本文我們將加上Nios II CPU,透過軟體的方式去控制CCD和彩色LCD。
使用環境:Quartus II 7.2 SP1 + Nios II 7.2 SP1 + DE2(Cyclone II EP2C35F627C6) + TRDB_LCM + TRDB_DC2
Introduction
若就功能面而言,使用Verilog純硬體實作的數位相機已經達到功能了,唯一的缺憾是他是『純硬體』,我們無法使用C語言軟體的方式去控制CCD和LCD,所以我們打算加上Nios II CPU,透過記憶體定址的方式去控制硬體。
在(原創) 如何在DE2將CCD影像顯示在彩色LCD上? (純硬體篇) (IC Design) (DE2)中,我們使用了KEY和SW作為輸入的介面,這些KEY和SW直接與硬體的module相接,這是純硬體的方式。若要用軟體,我們勢必得用軟體去攔截KEY和SW輸入的結果,然後再將結果送到控制CCD和LCD的module。由於要使用軟體,一定需要一顆CPU,Nios II softcore CPU剛好可扮演這個角色,而軟體一定得載入記憶體執行,所以還需要一塊記憶體,DE2上有很多種記憶體可用,除了FPGA上的onchip-memory外,還有SRAM、SDRAM、Flash,為了簡化問題,本文先討論最單純的onchip-memory,後續會繼續討論使用SRAM等其他記憶體。
該如何攔截KEY和SW呢?Nios II提供了PIO的方式,可以輕易的攔截KEY和SW,這問題不大,但該如何用軟體去控制CCD和LCD呢?這就是本文的重點了。
Nios II提供了Avalon Bus,所有的周邊要和Nios II CPU溝通,都得透過這個bus,當然也必須遵守Avalon Bus的協定才能順利與Nios II溝通,所以必須將原來純硬體的Verilog程式碼,再多加上一個Adapter(是的,就是Design Pattern那個Adapter Pattern),讓原來的硬體能掛上Avalon Bus和Nios II溝通,只要能掛上Avalon Bus後,Nios II就能對這個Adapter做記憶體定址,只要有記憶體位址,C語言就能靠著記憶體位址對Adapter作存取了,也因此間接控制了CCD和LCD。
建立DE2_LCM_CCD_onchip專案
Step 1:
建立DE2_LCM_CCD_onchip目錄,將原本用Verilog純硬體的DE2_LCM_CCD.7z下載,將解壓縮後DE2_LCM_CCD下所有的檔案放到DE2_LCM_CCD_onchip下。
Step 2:
在DE2_LCM_CCD_onchip下建立ip子目錄。
要使SOPC Builder抓到你自己寫的controller,在Quartus II 7.2和以前版本有些不同,有兩種方法[1]:
1.將自己寫的controller放在專案目錄的ip子目錄下,這樣SOPC Builder在啟動時就會自動載入。這種方式適合controller只有這個專案會用到,而且期望你將專案複製給其他人時,不用設定就能抓到controller。
2.將controller放在獨立目錄,然後在SOPC Builder的Tools->Options->IP Search Path加入controller目錄,讓SOPC Builder可以找的到,這種方式適合多個專案要共用一個controller,不過若將專案複製給其他人時,對方還得自己設定controller路徑。
SOPC Builder更詳細的運作方式,請參閱(原創) 如何加速Altera的EDA工具? (IC Design) (Quartus II) (Nios II) (SOPC Builder) 。
本文採用的是第一種方法。
Step 3:
在ip目錄下建立CCD_Controller子目錄。
將CCD_Controller的Verilog code放在CCD_Controller目錄下。
撰寫CCD_Controller
CCD_Controller.v
2 // Avalon clock interface siganals
3 csi_clockreset_clk,
4 csi_clockreset_reset_n,
5 // Signals for Avalon-MM slave port
6 avs_s1_address,
7 avs_s1_chipselect,
8 avs_s1_write,
9 avs_s1_writedata,
10 // export
11 avs_s1_export_o_key,
12 avs_s1_export_o_sw
13 );
14
15 input csi_clockreset_clk;
16 input csi_clockreset_reset_n;
17
18 input avs_s1_address;
19 input avs_s1_chipselect;
20 input avs_s1_write;
21 input [15:0] avs_s1_writedata;
22
23 output [2:0] avs_s1_export_o_key;
24 output [15:0] avs_s1_export_o_sw;
25
26 reg [2:0] r_key;
27 reg [15:0] r_sw;
28
29 // write to export
30 always@(posedge csi_clockreset_clk or negedge csi_clockreset_reset_n)
31 begin
32 if (!csi_clockreset_reset_n)
33 begin
34 r_key <= 4\'b1111;
35 r_sw <= 16\'b0000_0000_0000_0000;
36 end
37 else
38 begin
39 if (avs_s1_address == 1\'b0 && avs_s1_chipselect && avs_s1_write)
40 begin
41 r_key <= avs_s1_writedata[2-:3];
42 r_sw <= r_sw;
43 end
44 else if (avs_s1_address == 1\'b1 && avs_s1_chipselect && avs_s1_write)
45 begin
46 r_key <= r_key;
47 r_sw <= avs_s1_writedata;
48 end
49 end
50 end
51
52 assign avs_s1_export_o_key = r_key;
53 assign avs_s1_export_o_sw = r_sw;
54
55 endmodule
這個CCD_Controller.v主要的目的在做什麼?由於CCD_Controller.v需要掛在Avalon Bus上,所以必須遵守Avalon-MM slave的規定,如clk、reset_n、address、chip_select、write、writedata這些port,尤其若要將資料送進CCD_Controller,Nios II是將資料送進writedata中,所以我們必須將writedata的資料進行解析,然後export出來,與CCD和LCD相關module相連。在Quartus II早期的版本,在port命名並沒有規定naming convention,所以在Component Editor的Signals還必須手動指定Signal Type,但在Quartus II 5.1以後增加了以下規定:
<interface_type>_<interface name>_<signal type>
| Interface Type | Meaning |
| avs | Avalon-MM Slave |
| avm | Avalon-MM Master |
| ats | Avalon-MM Tristate Slave |
| atm | Avalon-MM Tristate Master |
| aso | Avalon-ST Source |
| asi | Avalon-ST Sink |
| cso | Clock Output |
| csi | Clock Input |
| inr | Interrupt Receiver |
| ins | Interupt Sender |
| cos | Conduit Start |
| coe | Conduit End |
| ncm | Nios II Custom Instruction Master |
| ncs | Nios II Custom Instruction Slave |
| csi_clockreset_clk | Clock Reset |
| csi_clockreset_reset_n | Clock Reset N |
Table.1 Interface type value meaning [2]
20行
為什麼avs_s1_writedata要設定16 bit寬呢?因為這個controller主要的input為KEY和SW,KEY[0]由於作為硬體reset用,所以軟體就不加以攔截了,所以只有KEY[1]、KEY[2]、KEY[3]共3 bit,SW為16 bit,所以由最大的16 bit決定writedata寬度。
23行、24行
output [15:0] avs_s1_export_o_sw;
如前段所解釋,因為KEY只有3 bit,所以export用的avs_s1_export_key也只有3 bit,同理,avs_s1_export_o_sw也只有如SW的16 bit。
39行到43行
begin
r_key <= avs_s1_writedata[2-:3];
r_sw <= r_sw;
end
目的是將writedata資料解析為KEY,address的功用是做什麼呢?由於所有輸入的訊號都由writedata進來,我該怎麼分辨進來的訊號是KEY還是SW?就是靠address!!由於本controller只須分辨KEY和SW兩種訊號,所以address只需1 bit分辨0與1即可,並且規定1\'b0為KEY,1\'b1為SW,除此之外,Avalon Bus還規定chipselect和write為1時訊號才有效,所以必須增加判斷。
為什麼只抓writedata[2-:3]呢?由於writedata為16 bit,但KEY只有3 bit,所以只需writedata低3bit的資料即可,[2-:3]為Verilog專屬語法,表是從3bit開始往下抓3 bit。
44行到48行解析SW同理,由於SW和writedata同寬,只需用assign vector的方式即可。
最後必須將CCD_Controller.v存到DE2_LCM_CCD_onchip\ip\CCD_Controller\hdl\目錄下。
使用Component Editor建立CCD_Controller
Verilog程式寫好還不夠,還必須使用Component Editor將CCD_Controller.v包裝成SOPC Builder能用的controller。
Step 1:
按右上角圖示啟動SOPC Builder。
Fig.1 SOPC Builder圖示
Step 2:
設定System名稱,輸入nios_ii_system。
Fig.2 設定System名稱
Step 3:
啟動Component Editor
Fig.3 SOPC Builder
Step 4:
按下Add HDL File,加入CCD_Controller.v
Fig.4 Component Editor
Fig.5 加入CCD_Controller.v
加入後,Component Editor會對CCD_Controller.v作及時編譯,若Verilog語法有錯會顯示出來,若成功將出現以下畫面。
Fig.6 CCD_Controller.v編譯成功
Step 5:
設定Signals
由於CCD_Controller.v已經依照naming convention寫了,所以就不用再設定Signal Type,Component Editor會自動抓到。
Fig.7 Component Editor : Signals
Step 6:
設定Interfaces
Component Editor預設多抓了export_0這個interface(原因不明),須手動按Remove Interfaces With No Signals將其刪除。
Fig.8 Component Editor:Interfaces
移除export_0後,將s1的Associated Clock選擇clockreset,Slave Addressing選擇NATIVE。
Fig.9 Component Editor:Interfaces 2
Step 7:
填入Controller資訊
這些資訊可依你的需要自行修改,不會影響功能,最後按下Finish。
Fig.10 Component Editor:Component Wizard
Step 8:
完成CCD_Controller
按下Yes, Save後,Component Editor會為你在DE2_LCM_CCD_onchip\ip\CCD_Controller\hdl下產生CCD_Controller_hw.tcl。
Fig.11 確定產生CCD_Controller_hw.tcl
Fig.12 SOPC Builder出現CCD_Controller
建立Nios II System
接著我們要使用SOPC Builder產生以Nios II為基礎的SOC
Step 1:
加入Nios II Processor
選擇左側Altera SOPC Builder->Nios II Processor,滑鼠按兩下加入
Fig.13 加入Nios II Processor
選擇Nios II/e,因為本專案要使用on-chip memory,所以只好選Nios II/e以節省logic element,讓on-chip memory能開到最大。
Fig.14 選用Nios II/e
Step 2:
加入on-chip memory
由於要使用軟體,所以必須使用記憶體載入程式,為了簡化問題,所以本專案選擇最單純的on-chip memory,將記憶體直接放在FPGA內。
選擇左側Altera SOPC Builder->Memories and Memory Controller->On-Chip->On-Chip Memory(RAM or ROM),滑鼠按兩下加入
Fig.15 加入on-chip memory
設定on-chip memory大小為35KB。
為什麼只設定35KB,而不設定其他數值呢?Cyclone II(EP2C35F672C6)的Nios II,若取最單純的設定:Nios II/e + on-chip memory + JTAG UART + System ID Peripheral,且不包含任何自己寫的Verilog,極限可設定on-chip memory 49KB,且可正常編譯,以本例而言,由於CCD和LCD已經寫了不少Verilog,所以已經占了FPGA不少logic element,導致on-chip memory的極限只能設定到36KB,若更大則Quartus II無法正常編譯,這裡取35KB只因為剛好為整數,且對本系統已經夠用。
Fig.16 on-chip memory size
Step 3:
加入JTAG UART
本專案雖然不需在Console顯示文字,不過在Console使用printf()仍為最常用的debug方式,所以建議還是加上JTAG UART。
在左側SOPC Builder->Interface Protocols->Serial下找到JTAG UART,滑鼠點兩下加入,接受預設值即可,按Finish。
Fig.17 加入JTAG UART
Fig.18 JTAG UART window
Step 4:
加入System ID Peripheral
System ID Peripheral可以用來辨識硬體,雖然沒有加入也能執行,不過Altera原廠手冊仍建議加入此controller。
在左側Altera SOPC Builder->Peripherals->Debug and Performance下找到System ID Peripheral,滑鼠按兩下加入,接受預設值即可,按Finish。
Fig.19 加入System ID Peripheral
Fig.20 System ID Peripheral window
Step 5:
加入CCD_Controller
現在要加入剛剛自己寫好的CCD_Controller。
在左側Altera SOPC Builder->My Controller下找到CCD_Controller,滑鼠按兩下加入,接受預設值即可,按Finish。
Fig.21 加入CCD_Controller
Fig.22 CCD_Controller window
Step 6:
加入KEY PIO
由於我們要用軟體攔截KEY的輸入,所以要加上PIO。
在左側Altera SOPC Builder->Peripherals->Microcontroller Peripherals下找到PIO(Parallel I/O),滑鼠按兩下加入,接受預設值即可,按Finish。
Fig.23 加入PIO
由於KEY[0]是reset,因此軟體不攔,只用軟體攔KEY[1]、KEY[2]與KEY[3]而已,所以Width填3 bit。因為只是讀取KEY的輸入值,所以選Input ports only。
Fig.24 PIO window
更名為key_pio
Fig.25 更名為key_pio
Step 7:
加入SW PIO
由於我們要用軟體攔截SW的輸入,所以要加上PIO。
在左側Altera SOPC Builder->Peripherals->Microcontroller Peripherals下找到PIO(Parallel I/O),滑鼠按兩下加入,接受預設值即可,按Finish。
Fig.25 加入PIO
由於SW為16 bit,所以Width填3 bit。因為只是讀取SW的輸入值,所以選Input ports only。
Fig.26 PIO window
更名為sw_pio
Fig.27 更名為sw_pio
Step 8:
設定reset和exception vector
在右側CPU用滑鼠點兩下。
Fig.28 設定reset與exception
將reset和exception vector設在onchip_mem,這也是本專案唯一的記憶體。
Fig.29 將reset和exception設在onchip_mem
Step 9:
重新設定Base Address
由下方的錯誤訊息得知,有些元件所分配的base address衝突了,所以必須重新指定base address。
選擇System->Auto-Assign Base Address
Fig.30 Auto-Assign Base Address
Step 10:
產生Nios II System
截至目前為止,整個Nios II System已經設定完成,按Generate產生nios_ii_system.ptf,這需要一點時間,非常依賴CPU運算速度。
Fig.31 Generate System
按Save對Nios II System存檔。
Fig.32 Save system
經過一段時間後,值到右下方出現Generate,表是建立完成,按Exit回到Quartus II。
Fig.33 Generate complete
修改Top mudule:DE2_LCM_CCD.v
雖然SOPC Builder和Component Editor自動產生了很多Verilog程式碼,為我們省下不少工夫,但Top module還是得手動自己改,主要為了連接Nios II System和原本純硬體的CCD和LCD。
DE2_LCM_CCD.V
2 ////////////////////// Clock Input ////////////////////
3 CLOCK_27, // 27 MHz
4 CLOCK_50, // 50 MHz
5 EXT_CLOCK, // External Clock
6 ////////////////////// Push Button ////////////////////
7 KEY, // Pushbutton[3:0]
8 //////////////////// DPDT Switch ////////////////////
9 SW, // Toggle Switch[17:0]
10 ////////////////////// 7-SEG Dispaly ////////////////////
11 HEX0, // Seven Segment Digit 0
12 HEX1, // Seven Segment Digit 1
13 HEX2, // Seven Segment Digit 2
14 HEX3, // Seven Segment Digit 3
15 HEX4, // Seven Segment Digit 4
16 HEX5, // Seven Segment Digit 5
17 HEX6, // Seven Segment Digit 6
18 HEX7, // Seven Segment Digit 7
19 ////////////////////// LED ////////////////////////
20 LEDG, // LED Green[8:0]
21 LEDR, // LED Red[17:0]
22 ////////////////////// UART ////////////////////////
23 UART_TXD, // UART Transmitter
24 UART_RXD, // UART Receiver
25 ////////////////////// IRDA ////////////////////////
26 IRDA_TXD, // IRDA Transmitter
27 IRDA_RXD, // IRDA Receiver
28 ////////////////////// SDRAM Interface ////////////////
29 DRAM_DQ, // SDRAM Data bus 16 Bits
30 DRAM_ADDR, // SDRAM Address bus 12 Bits
31 DRAM_LDQM, // SDRAM Low-byte Data Mask
32 DRAM_UDQM, // SDRAM High-byte Data Mask
33 DRAM_WE_N, // SDRAM Write Enable
34 DRAM_CAS_N, // SDRAM Column Address Strobe
35 DRAM_RAS_N, // SDRAM Row Address Strobe
36 DRAM_CS_N, // SDRAM Chip Select
37 DRAM_BA_0, // SDRAM Bank Address 0
38 DRAM_BA_1, // SDRAM Bank Address 0
39 DRAM_CLK, // SDRAM Clock
40 DRAM_CKE, // SDRAM Clock Enable
41 ////////////////////// Flash Interface ////////////////
42 FL_DQ, // FLASH Data bus 8 Bits
43 FL_ADDR, // FLASH Address bus 22 Bits
44 FL_WE_N, // FLASH Write Enable
45 FL_RST_N, // FLASH Reset
46 FL_OE_N, // FLASH Output Enable
47 FL_CE_N, // FLASH Chip Enable
48 ////////////////////// SRAM Interface ////////////////
49 SRAM_DQ, // SRAM Data bus 16 Bits
50 SRAM_ADDR, // SRAM Address bus 18 Bits
51 SRAM_UB_N, // SRAM High-byte Data Mask
52 SRAM_LB_N, // SRAM Low-byte Data Mask
53 SRAM_WE_N, // SRAM Write Enable
54 SRAM_CE_N, // SRAM Chip Enable
55 SRAM_OE_N, // SRAM Output Enable
56 ////////////////////// ISP1362 Interface ////////////////
57 OTG_DATA, // ISP1362 Data bus 16 Bits
58 OTG_ADDR, // ISP1362 Address 2 Bits
59 OTG_CS_N, // ISP1362 Chip Select
60 OTG_RD_N, // ISP1362 Write
61 OTG_WR_N, // ISP1362 Read
62 OTG_RST_N, // ISP1362 Reset
63 OTG_FSPEED, // USB Full Speed, 0 = Enable, Z = Disable
64 OTG_LSPEED, // USB Low Speed, 0 = Enable, Z = Disable
65 OTG_INT0, // ISP1362 Interrupt 0
66 OTG_INT1, // ISP1362 Interrupt 1
67 OTG_DREQ0, // ISP1362 DMA Request 0
68 OTG_DREQ1, // ISP1362 DMA Request 1
69 OTG_DACK0_N, // ISP1362 DMA Acknowledge 0
70 OTG_DACK1_N, // ISP1362 DMA Acknowledge 1
71 ////////////////////// LCD Module 16X2 ////////////////
72 LCD_ON, // LCD Power ON/OFF
73 LCD_BLON, // LCD Back Light ON/OFF
74 LCD_RW, // LCD Read/Write Select, 0 = Write, 1 = Read
75 LCD_EN, // LCD Enable
76 LCD_RS, // LCD Command/Data Select, 0 = Command, 1 = Data
77 LCD_DATA, // LCD Data bus 8 bits
78 ////////////////////// SD_Card Interface ////////////////
79 SD_DAT, // SD Card Data
80 SD_DAT3, // SD Card Data 3
81 SD_CMD, // SD Card Command Signal
82 SD_CLK, // SD Card Clock
83 ////////////////////// USB JTAG link ////////////////////
84 TDI, // CPLD -> FPGA (data in)
85 TCK, // CPLD -> FPGA (clk)
86 TCS, // CPLD -> FPGA (CS)
87 TDO, // FPGA -> CPLD (data out)
88 ////////////////////// I2C ////////////////////////////
89 I2C_SDAT, // I2C Data
90 I2C_SCLK, // I2C Clock
91 ////////////////////// PS2 ////////////////////////////
92 PS2_DAT, // PS2 Data
93 PS2_CLK, // PS2 Clock
94 ////////////////////// VGA ////////////////////////////
95 VGA_CLK, // VGA Clock
96 VGA_HS, // VGA H_SYNC
97 VGA_VS, // VGA V_SYNC
98 VGA_BLANK, // VGA BLANK
99 VGA_SYNC, // VGA SYNC
100 VGA_R, // VGA Red[9:0]
101 VGA_G, // VGA Green[9:0]
102 VGA_B, // VGA Blue[9:0]