筆試時也很常見。
[例1] 一個簡單的狀態機設計--序列檢測器
序列檢測器是時序數字電路設計中經典的教學范例,下面我們將用Verilog HDL語言來描述、仿真、并實現它。
序列檢測器的邏輯功能描述:
序列檢測指的就是將一個指定的序列從數字碼流中識別出來。本例中,我們將設計一個“10010”序列的檢測器。設X為數字碼流輸入,Z為檢出標記輸出,高電平表示“發現指定序列”,低電平表示“沒有發現指定序列”。考慮碼流為“ 110010010000100101…”
在時鐘2-6,碼流X中出現指定序列“10010”,對應輸出Z在第6個時鐘變為高電平――“1”,表示“發現指定序列”。同樣地,在時鐘13-17碼流,X中再次出現指定序列“10010”,Z輸出“1”。注意,在時鐘5-9還有一次檢出,但它是與第一次檢出的序列重疊的,即前者的前面兩位同時也是后者的最后兩位。
根據以上邏輯功能描述,我們可以分析得出狀態轉換圖如下:
其中狀態A-E表示5比特序列“10010”按順序正確地出現在碼流中。考慮到序列重疊的可能,轉換圖中還有狀態F、G。另外、電路的初始狀態設為IDLE。
進一步,我們得出Verilog HDL代碼。
//文件:sequence.v
module seqdet( x, z, clk, rst);
input x,clk, rst;
output z;
reg [2:0] state;//狀態寄存器
wire z;
parameter IDLE= ‘d0, A=’d1, B=‘d2,
C=’d3, D=‘d4,
E=’d5, F=‘d6,
G=’d7;
assign z=(state==D && x==0) ? 1 :0;
always @(posedge clk or negedge rst)
if(!rst)
begin
state《=IDLE;
end
else
casex( state)
IDLE: if(x==1)
begin
state《=A;
end
A: if (x==0)
begin
state《=B;
end
B: if (x==0)
begin
state《=C;
end
else
begin
state《=F;
end
C: if(x==1)
begin
state《=D;
end
else
begin
state《=G;
end
D: if(x==0)
begin
state《=E;
end
else
begin
state《=A;
end
E: if(x==0)
begin
state《=C;
end
else
begin
state《=A;
end
F: if(x==1)
begin
state《=A;
end
else
begin
state《=B;
end
G: if(x==1)
begin
state《=F;
end
default: state《=IDLE;
endcase
endmodule
為了驗證其正確性,我們接著編寫測試用代碼。
//文件:sequence.tf
`timescale 1ns/1ns
module t;
reg clk, rst;
reg [23:0] data;
wire z,x;
assign x=data[23];
initial
begin
clk《=0;
rst《=1;
#2 rst《=0;
#30 rst《=1; //復位信號
data=‘b1100_1001_0000_1001_0100; //碼流數據
end
always #10 clk=~clk; //時鐘信號
always @ (posedge clk) // 移位輸出碼流
data={data[22:0],data[23]};
seqdet m ( .x(x), .z(z), .clk(clk), .rst(rst)); //調用序列檢測器模塊
// Enter fixture code here
endmodule // t
其中、X碼流的產生,我們采用了移位寄存器的方式,以方便更改測試數據。仿真結果如下圖所示:
從波形中,我們可以看到程序代碼正確地完成了所要設計的邏輯功能。另外,sequence.v的編寫,采用了可綜合的Verilog HDL 風格,它可以通過綜合器的綜合最終實現到FPGA中。
說明:以上編程、仿真、綜合和后仿真在PC WINDOWS NT 4.0操作系統及QuickLogic SPDE環境下通過。
[例2]EEPROM讀寫器件的設計
下面我們將介紹一個經過實際運行驗證并可綜合到各種FPGA和ASIC工藝的串行EEPROM讀寫器件的設計過程。列出了所有有關的Verilog HDL程序。這個器件能把并行數據和地址信號轉變為串行EEPROM能識別的串行碼并把數據寫入相應的地址,或根據并行的地址信號從EEPROM相應的地址讀取數據并把相應的串行碼轉換成并行的數據放到并行地址總線上。當然還需要有相應的讀信號或寫信號和應答信號配合才能完成以上的操作。
1.二線制I2C CMOS 串行EEPROM的簡單介紹二線制I2C CMOS 串行EEPROM AT24C02/4/8/16 是一種采用CMOS 工藝制成的串行可用電擦除可編程只讀存儲器。串行EEPROM 一般具有兩種寫入方式,一種是字節寫入方式,還有另一種頁寫入方式,允許在一個寫周期內同時對一個字節到一頁的若干字節進行編程寫入,一頁的大小取決于芯片內頁寄存器的大小,不同公司的同一種型號存儲器的內頁寄存器可能是不一樣的。
為了程序的簡單起見,在這里只編寫串行 EEPROM 的一個字節的寫入和讀出方式的Verilog HDL 的行為模型代碼,串行EEPROM讀寫器的Verilog HDL模型也只是字節讀寫方式的可綜合模型,對于頁寫入和讀出方式,讀者可以參考有關書籍,改寫串行EEPROM 的行為模型和串行EEPROM讀寫器的可綜合模型。
2.I2C (Inter Integrated Circuit)總線特征介紹I2C 雙向二線制串行總線協議定義如下:
只有在總線處于“非忙”狀態時,數據傳輸才能被初始化。在數據傳輸期間,只要時鐘線為高電平,數據線都必須保持穩定,否則數據線上的任何變化都被當作“啟動”或“停止”信號。
①總線非忙狀態(A 段)
數據線SDA 和 時鐘線 SCL 都保持高電平。
②啟動數據傳輸(B 段)
當時鐘線(SCL)為高電平狀態時,數據線(SDA)由高電平變為低電平的下降沿被認為是“啟動”信號。只有出現“啟動”信號后,其它的命令才有效。
③停止數據傳輸(C 段)
當時鐘線(SCL)為高電平狀態時,數據線(SDA)由低電平變為高電平的上升沿被認為是“停止”信號。隨著“停在”信號出現,所有的外部操作都結束。
④數據有效(D 段)
在出現“啟動”信號以后,在時鐘線(SCL)為高電平狀態時數據線是穩定的,這時數據線的狀態就要傳送的數據。數據線(SDA)上的數據的改變必須在時鐘線為低電平期間完成,每位數據占用一個時鐘脈沖。每個數傳輸都是由“啟動”信號開始,結束于“停止”信號。
⑤應答信號
每個正在接收數據的EEPROM 在接到一個字節的數據后,通常需要發出一個應答信號。而每個正在發送數據的EEPROM 在發出一個字節的數據后,通常需要接收一個應答信號。EEPROM 讀寫控制器必須產生一個與這個應答位相聯系的額外的時鐘脈沖。在EEPROM 的讀操作中,EEPROM 讀寫控制器對EEPROM 完成的最后一個字節不產生應答位,但是應該給EEPROM 一個結束信號。
3.二線制I2C CMOS 串行EEPROM讀寫操作1) EEPROM 的寫操作(字節編程方式)所謂EEPROM的寫操作(字節編程方式)就是通過讀寫控制器把一個字節數據發送到EEPROM 中指定地址的存儲單元。
其過程如下:EEPROM 讀寫控制器發出“啟動”信號后,緊跟著送4位I2C總線器件特征編碼1010 和3 位EEPROM 芯片地址/頁地址XXX 以及寫狀態的R/W 位(=0),到總線上。這一字節表示在接收到被尋址的EEPROM 產生的一個應答位后,讀寫控制器將跟著發送1個字節的EEPROM 存儲單元地址和要寫入的1個字節數據。
EEPROM 在接收到存儲單元地址后又一次產生應答位以后,讀寫控制器才發送數據字節,并把數據寫入被尋址的存儲單元。EEPROM 再一次發出應答信號,讀寫控制器收到此應答信號后,便產生“停止”信號。字節寫入幀格式如圖2所示:
2)二線制I2C CMOS 串行EEPROM 的讀操作所謂EEPROM的讀操作即通過讀寫控制器讀取 EEPROM 中指定地址的存儲單元中的一個字節數據。串行EEPROM 的讀操作分兩步進行:讀寫器首先發送一個“啟動”信號和控制字節(包括頁面地址和寫控制位)到EEPROM,再通過寫操作設置EEPROM 存儲單元地址(注意:雖然這是讀操作,但需要先寫入地址指針的值),在此期間EEPROM 會產生必要的應答位。
接著讀寫器重新發送另一個“啟動”信號和控制字節(包括頁面地址和讀控制位R/W = 1),EEPROM收到后發出應答信號,然后,要尋址存儲單元的數據就從SDA 線上輸出。讀操作有三種:讀當前地址存儲單元的數據、讀指定地址存儲單元的數據、讀連續存儲單元的數據。在這里只介紹讀指定地址存儲單元數據的操作。
4.EEPROM的Verilog HDL 程序要設計一個串行EEPROM讀寫器件,不僅要編寫EEPROM讀寫器件的可綜合Verilog HDl的代碼,而且要編寫相應的測試代碼以及EERPOM的行為模型。
1) EEPROM的行為模型為了設計這樣一個電路我們首先要設計一個EEPROM的Verilog HDL模型,而設計這樣一個模型我們需要仔細地閱讀和分析EEPROM器件的說明書,因為EEPROM不是我們要設計的對象,而是我們驗證設計對象所需要的器件,所以只需設計一個EEPROM的行為模型,而不需要可綜合風格的模型,這就大大簡化了設計過程。
下面的Verilog HDL程序就是這個EEPROM(AT24C02/4/8/16) 能完成一個字節數據讀寫的部分行為模型,請讀者查閱AT24C02/4/8/16說明書,對照下面的Verilog HDL程序理解設計的要點。因為這一程序是我們自己編寫的有不完善之處敬請指正。
這里只對在操作中用到的信號線進行模擬,對于沒有用到的信號線就略去了。對EEPROM用于基本總線操作的引腳SCL和SDA說明如下:SCL,串行時鐘端,這個信號用于對輸入和輸出數據的同步,寫入串行EEPROM的數據用其上升沿同步,輸出數據用其下降沿同步;SDA,串行數據(/地址)輸入/輸出端。
`timescale 1ns/1ns
`define timeslice 100
module EEPROM(scl, sda);
input scl; //串行時鐘線
inout sda; //串行數據線
reg out_flag; //SDA數據輸出的控制信號
reg[7:0] memory[2047:0];
reg[10:0] address;
reg[7:0] memory_buf;
reg[7:0] sda_buf; //SDA 數據輸出寄存器
reg[7:0] shift; //SDA 數據輸入寄存器
reg[7:0] addr_byte; //EEPROM 存儲單元地址寄存器
reg[7:0] ctrl_byte; //控制字寄存器
reg[1:0] State; //狀態寄存器
integer i;
//--------------------------------------------------------------
parameter r7= 8’b10101111,w7= 8‘b10101110, //main7
r6= 8’b10101101,w6= 8‘b10101100, //main6
r5= 8’b10101011,w5= 8‘b10101010, //main5
r4= 8’b10101001,w4= 8‘b10101000, //main4
r3= 8’b10100111,w3= 8‘b10100110, //main3
r2= 8’b10100101,w2= 8‘b10100100, //main2
r1= 8’b10100011,w1= 8‘b10100010, //main1
r0= 8’b10100001,w0= 8‘b10100000; //main0
//--------------------------------------------------------------
assign sda = (out_flag == 1) ? sda_buf[7] : 1’bz;
//―――――――寄存器和存儲器初始化―――――――――――――――
initial
begin
addr_byte = 0;
ctrl_byte = 0;
out_flag = 0;
sda_buf = 0;
State = 2‘b00;
memory_buf = 0;
address = 0;
shift = 0;
for(i=0;i《=2047;i=i+1)
memory[i]=0;
end
//------------ 啟動信號 -----------------------------
always @ (negedge sda)
if(scl == 1 )
begin
State = State + 1;
if(State == 2’b11)
disable write_to_eeprm;
end
//------------ 主狀態機 --------------------------
always @(posedge sda)
if (scl == 1 ) //停止操作
stop_W_R;
else
begin
casex(State)
2‘b01:
begin
read_in;
if(ctrl_byte==w7||ctrl_byte==w6||ctrl_byte==w5
||ctrl_byte==w4||ctrl_byte==w3||ctrl_byte==w2
||ctrl_byte==w1||ctrl_byte==w0)
begin
State = 2’b10;
write_to_eeprm; //寫操作
end
else
State = 2‘b00;
end
2’b11:
read_from_eeprm; //讀操作
default:
State=2‘b00;
endcase
end
//------------- 操作停止------------------------------
task stop_W_R;
begin
State =2’b00; //狀態返回為初始狀態
addr_byte = 0;
ctrl_byte = 0;
out_flag = 0;
sda_buf = 0;
end
endtask
//------------- 讀進控制字和存儲單元地址 ------------------------
task read_in;
begin
shift_in(ctrl_byte);
shift_in(addr_byte);
end
endtask
//------------EEPROM 的寫操作---------------------------------------
task write_to_eeprm;
begin
shift_in(memory_buf);
address = {ctrl_byte[3:1],addr_byte};
memory[address] = memory_buf;
$display(“eeprm----memory[%0h]=%0h”,address,memory[address]);
State =2‘b00; //回到0狀態
end
endtask
//-----------EEPROM 的讀操作----------------------------------------
task read_from_eeprm;
begin
shift_in(ctrl_byte);
if(ctrl_byte==r7||ctrl_byte==r6||ctrl_byte==r5||ctrl_byte==r4
||ctrl_byte==r3||ctrl_byte==r2||ctrl_byte==r1||ctrl_byte==r0)
begin
address = {ctrl_byte[3:1],addr_byte};
sda_buf = memory[address];
shift_out;
State= 2’b00;
end
end
endtask
//-----SDA 數據線上的數據存入寄存器,數據在SCL的高電平有效-------------
task shift_in;
output [7:0] shift;
begin
@ (posedge scl) shift[7] = sda;
@ (posedge scl) shift[6] = sda;
@ (posedge scl) shift[5] = sda;
@ (posedge scl) shift[4] = sda;
@ (posedge scl) shift[3] = sda;
@ (posedge scl) shift[2] = sda;
@ (posedge scl) shift[1] = sda;
@ (posedge scl) shift[0] = sda;
@ (negedge scl)
begin
#`timeslice ;
out_flag = 1; //應答信號輸出
sda_buf = 0;
end
@(negedge scl)
#`timeslice out_flag = 0;
end
endtask
//―――EEPROM 存儲器中的數據通過SDA 數據線輸出,數據在SCL 低電平時變化
task shift_out;
begin
out_flag = 1;
for(i=6;i》=0;i=i-1)
begin
@ (negedge scl);
#`timeslice;
sda_buf = sda_buf《《1;
end
@(negedge scl) #`timeslice sda_buf[7] = 1; //非應答信號輸出
@(negedge scl) #`timeslice out_flag = 0;
end
endtask
endmodule
###2 ) EEPROM讀寫器的可綜合的Verilog HDL模型
下面的程序是一個串行EEPROM讀寫器的可綜合的Verilog HDL模型,它接收來自信號源模型產生的讀信號、寫信號、并行地址信號、并行數據信號,并把它們轉換為相應的串行信號發送到串行EEPROM(AT24C02/4/8/16) 的行為模型中去;它還發送應答信號 (ACK)到信號源模型,以便讓信號源來調節發送或接收數據的速度以配合EEPROM模型的接收(寫)和發送(讀)數據。因為它是我們的設計對象,所以它不但要仿真正確無誤,還需要可綜合。
這個程序基本上由兩部分組成:開關組合電路和控制時序電路,見圖5。開關電路在控制時序電路的控制下按照設計的要求有節奏的打開或閉合,這樣SDA可以按I2C 數據總線的格式輸出或輸入,SDA和SCL一起完成EEPROM的讀寫操作。
電路最終用同步有限狀態機(FSM)的設計方法實現。程序實則上是一個嵌套的狀態機,由主狀態機和從狀態機通過由控制線啟動的總線在不同的輸入信號的情況下構成不同功能的較復雜的有限狀態機,這個有限狀態機只有唯一的驅動時鐘CLK。
根據串行EEPROM的讀寫操作時序可知,用5個狀態時鐘可以完成寫操作,用7個狀態時鐘可以完成讀操作,由于讀寫操作的狀態中有幾個狀態是一致的,用一個嵌套的狀態機即可。狀態轉移如圖6,程序由一個讀寫大任務和若干個較小的任務所組成,其狀態機采用獨熱編碼,若需改變狀態編碼,只需改變程序中的parameter定義即可。讀者可以通過模仿這一程序來編寫較復雜的可綜合Verilog HDL模塊程序。這個設計已通過后仿真,并可在FPGA上實現布局布線。
`timescale 1ns/1ns
module EEPROM_WR(SDA,SCL,ACK,RESET,CLK,WR,RD,ADDR,DATA);
output SCL; //串行時鐘線
output ACK; //讀寫一個周期的應答信號
input RESET; //復位信號
input CLK; //時鐘信號輸入
input WR,RD; //讀寫信號
input[10:0] ADDR; //地址線
inout SDA; //串行數據線
inout[7:0] DATA; //并行數據線
reg ACK;
reg SCL;
reg WF,RF; //讀寫操作標志
reg FF; //標志寄存器
reg [1:0] head_buf; //啟動信號寄存器
reg[1:0] stop_buf; //停止信號寄存器
reg [7:0] sh8out_buf; //EEPROM寫寄存器
reg [8:0] sh8out_state; //EEPROM 寫狀態寄存器
reg [9:0] sh8in_state; //EEPROM 讀狀態寄存器
reg [2:0] head_state; //啟動狀態寄存器
reg [2:0] stop_state; //停止狀態寄存器
reg [10:0] main_state; //主狀態寄存器
reg [7:0] data_from_rm; //EEPROM讀寄存器
reg link_sda; //SDA 數據輸入EEPROM開關
reg link_read; //EEPROM讀操作開關
reg link_head; //啟動信號開關
reg link_write; //EEPROM寫操作開關
reg link_stop; //停止信號開關
wire sda1,sda2,sda3,sda4;
//--------------串行數據在開關的控制下有次序的輸出或輸入-------------------
assign sda1 = (link_head) ? head_buf[1] : 1‘b0;
assign sda2 = (link_write) ? sh8out_buf[7] : 1’b0;
assign sda3 = (link_stop) ? stop_buf[1] : 1‘b0;
assign sda4 = (sda1 | sda2 | sda3);
assign SDA = (link_sda) ? sda4 : 1’bz;
assign DATA = (link_read) ? data_from_rm : 8‘hzz;
//--------------------------------主狀態機狀態------------------------------------------
parameter
Idle = 11’b00000000001,
Ready = 11‘b00000000010,
Write_start = 11’b00000000100,
Ctrl_write = 11‘b00000001000,
Addr_write = 11’b00000010000,
Data_write = 11‘b00000100000,
Read_start = 11’b00001000000,
Ctrl_read = 11‘b00010000000,
Data_read = 11’b00100000000,
Stop = 11‘b01000000000,
Ackn = 11’b10000000000,
//-------------------------并行數據串行輸出狀態-----------------------------
sh8out_bit7 = 9‘b000000001,
sh8out_bit6 = 9’b000000010,
sh8out_bit5 = 9‘b000000100,
sh8out_bit4 = 9’b000001000,
sh8out_bit3 = 9‘b000010000,
sh8out_bit2 = 9’b000100000,
sh8out_bit1 = 9‘b001000000,
sh8out_bit0 = 9’b010000000,
sh8out_end = 9‘b100000000;
//--------------------------串行數據并行輸出狀態----------------------------
parameter sh8in_begin = 10’b0000000001,
sh8in_bit7 = 10‘b0000000010,
sh8in_bit6 = 10’b0000000100,
sh8in_bit5 = 10‘b0000001000,
sh8in_bit4 = 10’b0000010000,
sh8in_bit3 = 10‘b0000100000,
sh8in_bit2 = 10’b0001000000,
sh8in_bit1 = 10‘b0010000000,
sh8in_bit0 = 10’b0100000000,
sh8in_end = 10‘b1000000000,
//---------------------------------啟動狀態----------------------------------
head_begin = 3’b001,
head_bit = 3‘b010,
head_end = 3’b100,
//---------------------------------停止狀態----------------------------------
stop_begin = 3‘b001,
stop_bit = 3’b010,
stop_end = 3‘b100;
parameter YES = 1,
NO = 0;
//-------------產生串行時鐘,為輸入時鐘的二分頻-------------------
always @(negedge CLK)
if(RESET)
SCL 《= 0;
else
SCL 《= ~SCL;
//-----------------------------主狀態程序----------------------------------
always @ (posedge CLK)
if(RESET)
begin
link_read 《= NO;
link_write 《= NO;
link_head 《= NO;
link_stop 《= NO;
link_sda 《= NO;
ACK 《= 0;
RF 《= 0;
WF 《= 0;
FF 《= 0;
main_state 《= Idle;
end
else
begin
casex(main_state)
Idle:
begin
link_read 《= NO;
link_write 《= NO;
link_head 《= NO;
link_stop 《= NO;
link_sda 《= NO;
if(WR)
begin
WF 《= 1;
main_state 《= Ready ;
end
else if(RD)
begin
RF 《= 1;
main_state 《= Ready ;
end
else
begin
WF 《= 0;
RF 《= 0;
main_state 《= Idle;
end
end
Ready:
begin
link_read 《= NO;
link_write 《= NO;
link_stop 《= NO;
link_head 《= YES;
link_sda 《= YES;
head_buf[1:0] 《= 2’b10;
stop_buf[1:0] 《= 2‘b01;
head_state 《= head_begin;
FF 《= 0;
ACK 《= 0;
main_state 《= Write_start;
end
Write_start:
if(FF == 0)
shift_head;
else
begin
sh8out_buf[7:0] 《= {1’b1,1‘b0,1’b1,1‘b0,ADDR[10:8],1’b0};
link_head 《= NO;
link_write 《= YES;
FF 《= 0;
sh8out_state 《= sh8out_bit6;
main_state 《= Ctrl_write;
end
Ctrl_write:
if(FF ==0)
shift8_out;
else
begin
sh8out_state 《= sh8out_bit7;
sh8out_buf[7:0] 《= ADDR[7:0];
FF 《= 0;
main_state 《= Addr_write;
end
Addr_write:
if(FF == 0)
shift8_out;
else
begin
FF 《= 0;
if(WF)
begin
sh8out_state 《= sh8out_bit7;
sh8out_buf[7:0] 《= DATA;
main_state 《= Data_write;
end
if(RF)
begin
head_buf 《= 2‘b10;
head_state 《= head_begin;
main_state 《= Read_start;
end
end
Data_write:
if(FF == 0)
shift8_out;
else
begin
stop_state 《= stop_begin;
main_state 《= Stop;
link_write 《= NO;
FF 《= 0;
end
Read_start:
if(FF == 0)
shift_head;
else
begin
sh8out_buf 《= {1’b1,1‘b0,1’b1,1‘b0,ADDR[10:8],1’b1};
link_head 《= NO;
link_sda 《= YES;
link_write 《= YES;
FF 《= 0;
sh8out_state 《= sh8out_bit6;
main_state 《= Ctrl_read;
end
Ctrl_read:
if(FF == 0)
shift8_out;
else
begin
link_sda 《= NO;
link_write 《= NO;
FF 《= 0;
sh8in_state 《= sh8in_begin;
main_state 《= Data_read;
end
Data_read:
if(FF == 0)
shift8in;
else
begin
link_stop 《= YES;
link_sda 《= YES;
stop_state 《= stop_bit;
FF 《= 0;
main_state 《= Stop;
end
Stop:
if(FF == 0)
shift_stop;
else
begin
ACK 《= 1;
FF 《= 0;
main_state 《= Ackn;
end
Ackn:
begin
ACK 《= 0;
WF 《= 0;
RF 《= 0;
main_state 《= Idle;
end
default: main_state 《= Idle;
endcase
end
//------------------------串行數據轉換為并行數據任務----------------------------------
task shift8in;
begin
casex(sh8in_state)
sh8in_begin:
sh8in_state 《= sh8in_bit7;
sh8in_bit7: if(SCL)
begin
data_from_rm[7] 《= SDA;
sh8in_state 《= sh8in_bit6;
end
else
sh8in_state 《= sh8in_bit7;
sh8in_bit6: if(SCL)
begin
data_from_rm[6] 《= SDA;
sh8in_state 《= sh8in_bit5;
end
else
sh8in_state 《= sh8in_bit6;
sh8in_bit5: if(SCL)
begin
data_from_rm[5] 《= SDA;
sh8in_state 《= sh8in_bit4;
end
else
sh8in_state 《= sh8in_bit5;
sh8in_bit4: if(SCL)
begin
data_from_rm[4] 《= SDA;
sh8in_state 《= sh8in_bit3;
end
else
sh8in_state 《= sh8in_bit4;
sh8in_bit3: if(SCL)
begin
data_from_rm[3] 《= SDA;
sh8in_state 《= sh8in_bit2;
end
else
sh8in_state 《= sh8in_bit3;
sh8in_bit2: if(SCL)
begin
data_from_rm[2] 《= SDA;
sh8in_state 《= sh8in_bit1;
end
else
sh8in_state 《= sh8in_bit2;
sh8in_bit1: if(SCL)
begin
data_from_rm[1] 《= SDA;
sh8in_state 《= sh8in_bit0;
end
else
sh8in_state 《= sh8in_bit1;
sh8in_bit0: if(SCL)
begin
data_from_rm[0] 《= SDA;
sh8in_state 《= sh8in_end;
end
else
sh8in_state 《= sh8in_bit0;
sh8in_end: if(SCL)
begin
link_read 《= YES;
FF 《= 1;
sh8in_state 《= sh8in_bit7;
end
else
sh8in_state 《= sh8in_end;
default: begin
link_read 《= NO;
sh8in_state 《= sh8in_bit7;
end
endcase
end
endtask
//------------------------------ 并行數據轉換為串行數據任務 ---------------------------
task shift8_out;
begin
casex(sh8out_state)
sh8out_bit7:
if(!SCL)
begin
link_sda 《= YES;
link_write 《= YES;
sh8out_state 《= sh8out_bit6;
end
else
sh8out_state 《= sh8out_bit7;
sh8out_bit6:
if(!SCL)
begin
link_sda 《= YES;
link_write 《= YES;
sh8out_state 《= sh8out_bit5;
sh8out_buf 《= sh8out_buf《《1;
end
else
sh8out_state 《= sh8out_bit6;
sh8out_bit5:
if(!SCL)
begin
sh8out_state 《= sh8out_bit4;
sh8out_buf 《= sh8out_buf《《1;
end
else
sh8out_state 《= sh8out_bit5;
sh8out_bit4:
if(!SCL)
begin
sh8out_state 《= sh8out_bit3;
sh8out_buf 《= sh8out_buf《《1;
end
else
sh8out_state 《= sh8out_bit4;
sh8out_bit3:
if(!SCL)
begin
sh8out_state 《= sh8out_bit2;
sh8out_buf 《= sh8out_buf《《1;
end
else
sh8out_state 《= sh8out_bit3;
sh8out_bit2:
if(!SCL)
begin
sh8out_state 《= sh8out_bit1;
sh8out_buf 《= sh8out_buf《《1;
end
else
sh8out_state 《= sh8out_bit2;
sh8out_bit1:
if(!SCL)
begin
sh8out_state 《= sh8out_bit0;
sh8out_buf 《= sh8out_buf《《1;
end
else
sh8out_state 《= sh8out_bit1;
sh8out_bit0:
if(!SCL)
begin
sh8out_state 《= sh8out_end;
sh8out_buf 《= sh8out_buf《《1;
end
else
sh8out_state 《= sh8out_bit0;
sh8out_end:
if(!SCL)
begin
link_sda 《= NO;
link_write 《= NO;
FF 《= 1;
end
else
sh8out_state 《= sh8out_end;
endcase
end
endtask
//--------------------------- 輸出啟動信號任務 ---------------------------------
task shift_head;
begin
casex(head_state)
head_begin:
if(!SCL)
begin
link_write 《= NO;
link_sda 《= YES;
link_head 《= YES;
head_state 《= head_bit;
end
else
head_state 《= head_begin;
head_bit:
if(SCL)
begin
FF 《= 1;
head_buf 《= head_buf《《1;
head_state 《= head_end;
end
else
head_state 《= head_bit;
head_end:
if(!SCL)
begin
link_head 《= NO;
link_write 《= YES;
end
else
head_state 《= head_end;
endcase
end
endtask
//--------------------------- 輸出停止信號任務 --------------------------------------
task shift_stop;
begin
casex(stop_state)
stop_begin: if(!SCL)
begin
link_sda 《= YES;
link_write 《= NO;
link_stop 《= YES;
stop_state 《= stop_bit;
end
else
stop_state 《= stop_begin;
stop_bit: if(SCL)
begin
stop_buf 《= stop_buf《《1;
stop_state 《= stop_end;
end
else
stop_state《= stop_bit;
stop_end: if(!SCL)
begin
link_head 《= NO;
link_stop 《= NO;
link_sda 《= NO;
FF 《= 1;
end
else
stop_state 《= stop_end;
endcase
end
endtask
endmodule
程序最終通過Synplify器的綜合,并在Actel 3200DX 系列的FPGA上實現布局布線,通過布線后仿真 。
3 ) EEPROM的信號源模塊和頂層模塊完成串行EEPROM讀寫器件的設計后,我們還需要做的重要一步是EEPROM讀寫器件的仿真。仿真可以分為前仿真和后仿真,前仿真是Verilog HDL的功能仿真,后仿真是Verilog HDL 代碼經過綜合、布局布線后的時序仿真。
為此,我們還要編寫了用于EEPROM讀寫器件的仿真測試的信號源程序。這個信號源能產生相應的讀信號、寫信號、并行地址信號、并行數據信號,并能接收串行EEPROM讀寫器件的應答信號 (ACK),來調節發送或接收數據的速度。
在這個程序中,我們為了保證串行EEPROM讀寫器件的正確性,可以進行完整的測試,寫操作時輸入的地址信號和數據信號的數據通過系統命令readmemh和$fopen等系統命令讀者可以參考Verilog HDL的語法部分。最后我們把信號源、EEPROM和EEPROM讀寫器用頂層模塊連接在一起。在下面的程序就是這個信號源的Verilog HDL模型和頂層模塊。
信號源模型:
`timescale 1ns/1ns
`define timeslice 200
module Signal(RESET,CLK,RD,WR,ADDR,ACK,DATA);
output RESET; //復位信號
output CLK; //時鐘信號
output RD,WR; //讀寫信號
output[10:0] ADDR; //11位地址信號
input ACK; //讀寫周期的應答信號
inout[7:0] DATA; //數據線
reg RESET;
reg CLK;
reg RD,WR;
reg W_R; //低位:寫操作;高位:讀操作
reg[10:0] ADDR;
reg[7:0] data_to_eeprom;
reg[10:0] addr_mem[0:255];
reg[7:0] data_mem[0:255];
reg[7:0] ROM[1:2048];
integer i,j;
integer OUTFILE;
assign DATA = (W_R) ? 8‘bz : data_to_eeprom ;
//------------------------------------時鐘信號輸入------------------------------
always #(`timeslice/2)
CLK = ~CLK;
//----------------------------------- 讀寫信號輸入------------------------------
initial
begin
RESET = 1;
i = 0;
j =0;
W_R = 0;
CLK = 0;
RD = 0;
WR = 0;
#1000 ;
RESET = 0;
repeat(15) //連續寫15次數據
begin
#(5*`timeslice);
WR = 1;
#(`timeslice);
WR = 0;
@ (posedge ACK);
end
#(10*`timeslice);
W_R = 1; //開始讀操作
repeat(15) //連續讀15次數據
begin
#(5*`timeslice);
RD = 1;
#(`timeslice);
RD = 0;
@ (posedge ACK);
end
end
//-----------------------------------------寫操作-----------------------------
initial
begin
$display(“writing-----writing-----writing-----writing”);
# (2*`timeslice);
for(i=0;i《=15;i=i+1)
begin
ADDR = addr_mem[i];
data_to_eeprom = data_mem[i];
$fdisplay(OUTFILE,“@%0h %0h”,ADDR, data_to_eeprom);
@(posedge ACK) ;
end
end
//----------------------------------------讀操作----------------------------
initial
@(posedge W_R)
begin
ADDR = addr_mem[0];
$fclose(OUTFILE);
$readmemh(“。/eeprom.dat”,ROM);
$display(“Begin READING-----READING-----READING-----READING”);
for(j = 0; j 《= 15; j = j+1)
begin
ADDR = addr_mem[j];
@(posedge ACK);
if(DATA == ROM[ADDR])
$display(“DATA %0h == ROM[%0h]---READ RIGHT”,DATA,ADDR);
else
$display(“DATA %0h != ROM[%0h]---READ WRONG”,DATA,ADDR);
end
end
initial
begin
OUTFILE = $fopen(“。/eeprom.dat”);
$readmemh(“。/addr.dat”,addr_mem); //地址數據存入地址存儲器
$readmemh(“。/data.dat”,data_mem); //寫入EEPROM的數據存入數據存儲器
end
endmodule
頂層模塊:
`include “。/Signal.v”
`include “。/EEPROM.v”
`include “。/EEPROM_WR.v”
`timescale 1ns/1ns
module Top;
wire RESET;
wire CLK;
wire RD,WR;
wire ACK;
wire[10:0] ADDR;
wire[7:0] DATA;
wire SCL;
wire SDA;
Signal signal(.RESET(RESET),.CLK(CLK),.RD(RD),
.WR(WR),.ADDR(ADDR),.ACK(ACK),.DATA(DATA));
EEPROM_WR eeprom_wr(.RESET(RESET),.SDA(SDA),.SCL(SCL),.ACK(ACK),
.CLK(CLK),.WR(WR),.RD(RD),.ADDR(ADDR),.DATA(DATA));
EEPROM eeprom(.sda(SDA),.scl(SCL));
endmodule
通過前后仿真可以驗證程序的正確性。這里給出的是EEPROM讀寫時序的前仿真波形。后仿真波形除SCL和SDA與CLK有些延遲外,信號的邏輯關系與前仿真一致:
說明:
以上編程、仿真、綜合在PC WINDOWS NT 4.0操作系統、Synplify 、Actel Designer 、Altera Maxplus9.3及ModelSim Verilog環境下通過前后仿真,也在Unix Cadence Verilog-XL上通過前、后仿真(可綜合到各種FPGA和ASIC工藝)。
編輯:jq
-
Verilog
+關注
關注
28文章
1351瀏覽量
110265 -
HDL
+關注
關注
8文章
328瀏覽量
47432 -
讀寫器
+關注
關注
3文章
668瀏覽量
38906
原文標題:Verilog復雜時序邏輯電路設計實踐
文章出處:【微信號:gh_339470469b7d,微信公眾號:FPGA與數據通信】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論