本篇文章主要介紹一下EEPROM讀寫器件的設(shè)計思路诲侮,以及未來測試此器件搭建的測試平臺镀虐。
1、串行EEPROM讀寫器件
我們要設(shè)計一個串行EEPROM讀寫器件浆西,這要求我們設(shè)計出能夠綜合的Verilog HDL代碼粉私。所謂串行EEPROM讀寫器件就是指將我們平常輸入信號的輸入習(xí)慣產(chǎn)生符合I2C串行總線的數(shù)據(jù)。我們給此模塊命名為EEPROM_WR近零。
要搭建測試平臺才能夠?qū)υO(shè)計進(jìn)行驗證诺核,這里搭建的測試平臺,只用做行為級描述久信,不一定符合可綜合的設(shè)計要求窖杀。搭建測試平臺需要信號輸入模塊signal和EEPROM模塊。
下面就這三個模塊做一一的詳細(xì)介紹
EEPROM
我們理想的EEPROM數(shù)據(jù)的讀寫規(guī)則就是按照I2C總線給出的規(guī)則進(jìn)行裙士。在這里入客,我們先貼出相應(yīng)的代碼,然后進(jìn)行分析腿椎。
`timescale 1ns/1ns
`define timeslice 100
module EEPROM(scl,sda);
input scl;
inout sda;
reg out_flag;
reg [7:0] memory [2047:0];
reg [10:0] address;
reg [7:0] memory_buf;
reg [7:0] sda_buf;
reg [7:0] shift;
reg [7:0] addr_byte;
reg [7:0] ctrl_byte;
reg [1:0] state;
integer i;
parameter r7=8'b10101111, w7=8'b10101110,
r6=8'b10101101, w6=8'b10101100,
r5=8'b10101011, w5=8'b10101010,
r4=8'b10101001, w4=8'b10101000,
r3=8'b10100111, w3=8'b10100110,
r2=8'b10100101, w2=8'b10100100,
r1=8'b10100011, w1=8'b10100010,
r0=8'b10100001, w0=8'b10100000;
assign sda=(out_flag==1)? sda_buf[7]:1'bz; //sda線上作為輸出
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) //scl為高電平桌硫,sda的下降沿,表示啟動信號
begin
if(scl==1)
begin
state=state+1;
if(state==2'b11) //讀操作的另一次啟動信號啃炸,此時已經(jīng)完成了從sda總線上讀控制字和讀地址的操作铆隘。
disable write_to_EEPROM;
end
end
always@(posedge sda) //僅僅跟隨在啟動信號之后的觸發(fā)條件,因為控制字是1010XXXX南用,所以sda的上升沿就跳入此模塊膀钠。
begin
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; //從sda上讀取寫控制字和地址
write_to_EEPROM; //將sda上的數(shù)據(jù)寫如EEPROM.
end
else
state=2'b00;
end
2'b11: read_from_EEPROM; // 寫控制字,讀數(shù)據(jù)
default: state=2'b00;
endcase
end
end
task shift_in; //總共消耗9個scl周期裹虫,前8個用于讀數(shù)據(jù)肿嘲,將sda讀出,最后一個用于應(yīng)答位的產(chǎn)生筑公,sda被驅(qū)動賦值雳窟。
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) //最后這兩個scl的下降沿是輸出sda,用于應(yīng)答信號匣屡,詳細(xì)可參考波形1.
begin
#`timeslice
out_flag=1;
sda_buf=0; // 應(yīng)答位0.
end
@(negedge scl)
begin
#`timeslice
out_flag=0;
end
end
endtask
task read_in;
begin
shift_in(ctrl_byte);
shift_in(addr_byte);
end
endtask
task shift_out;//將sda_buf上的數(shù)據(jù)寫到總線上去涩拙,共消耗9個scl時鐘周期,其中前8個時鐘周期是將sdf_buf的內(nèi)容賦值到sda中去耸采。
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
task stop_W_R;
begin
state=2'b00;
addr_byte=0;
ctrl_byte=0;
out_flag=0;
sda_buf=0;
end
endtask
task write_to_EEPROM; //將sda上的數(shù)據(jù)寫入到EEPROM指定的地址中。
begin
shift_in(memory_buf);
address={ctrl_byte[3:1],addr_byte};
memory[address]=memory_buf;
$display("EEPROM-----memory[%0h]=%0h",address,memory[address]);
state=2'b00;
end
endtask
task read_from_EEPROM; //將EEPROM中指定地址的值放入sda總線上工育。
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
endmodule
EEPROM_WR
EEPROM讀寫器件我們要求設(shè)計成可綜合的設(shè)計風(fēng)格代碼虾宇,它接受來自信號源模型產(chǎn)生的讀信號、寫信號如绸、并行地址信號和并行數(shù)據(jù)信號嘱朽,并把它們轉(zhuǎn)換成相應(yīng)的串行信號發(fā)送到EEPROM的行為模型中去旭贬。
這部分主要由兩部分組成:一部分是開關(guān)組合電路,另一部分是控制時序電路搪泳。
電路上的同步采用有限狀態(tài)機(jī)的設(shè)計方法實現(xiàn)稀轨,程序上則采用的是一個有限狀態(tài)機(jī)的嵌套結(jié)構(gòu),由主狀態(tài)機(jī)和從狀態(tài)機(jī)通過由控制總線啟動的總線在不同的輸入信號下構(gòu)成不同功能的較為復(fù)雜的有限狀態(tài)機(jī)岸军,這個有限狀態(tài)機(jī)只有唯一的驅(qū)動時鐘clk奋刽。
寫狀態(tài)由5個狀態(tài)完成,讀狀態(tài)由7個狀態(tài)完成艰赞。在本代碼中佣谐,我們選擇了利用獨熱碼對狀態(tài)機(jī)進(jìn)行編碼,若改變狀態(tài)編碼方妖,只需要改變程序中的parameter定義即可狭魂。
下面就代碼結(jié)合波形,我給大家做一下簡要的分析党觅。
本模塊以狀態(tài)轉(zhuǎn)移為框架雌澄,讀寫過程分為三步開始、讀/寫杯瞻、結(jié)束镐牺。在讀寫過程中,涉及到串聯(lián)轉(zhuǎn)并聯(lián)又兵、并聯(lián)轉(zhuǎn)串聯(lián)的任務(wù)任柜。能夠清楚地掌握何時進(jìn)行開關(guān)的打開和關(guān)閉是設(shè)計成功的關(guān)鍵步驟。
module EEPROM_WR(
sda,
scl,
ack,
reset,
clk,
wr,
rd,
addr,
data
);
input clk,reset,wr,rd;
input[10:0]addr;
output scl,ack;
inout sda;
inout[7:0]data;
reg scl;
reg ack;
reg wf,rf;
reg ack_f;
reg[1:0] head_buf;
reg[1:0] stop_buf;
reg[7:0] storage_buf;
reg[8:0] w_state;
reg[9:0] r_state;
reg[2:0] head_state;
reg[2:0] stop_state;
reg[10:0] main_state;
reg[7:0] data_from_sda;
reg link_sda,link_read,link_write,link_head,link_stop;
wire sda1,sda2,sda3,sda4;
assign sda1=(link_head==1)? head_buf[1] : 1'b0; //開始
assign sda2=(link_stop==1)? stop_buf[1] : 1'b0; //結(jié)束
assign sda3=(link_write==1)? storage_buf[7] : 1'b0;//數(shù)據(jù)的輸出
assign sda4=sda1|sda2|sda3; //sda線上的輸出數(shù)據(jù)
assign sda=(link_sda==1)? sda4 : 1'bz;
assign data=(link_read==1'b1)? data_from_sda : 8'hzz;
parameter idle = 11'b00000000001, //主狀態(tài)機(jī)
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;
parameter data_to_sda_7 = 9'b000000001, //并聯(lián)轉(zhuǎn)串聯(lián)
data_to_sda_6 = 9'b000000010,
data_to_sda_5 = 9'b000000100,
data_to_sda_4 = 9'b000001000,
data_to_sda_3 = 9'b000010000,
data_to_sda_2 = 9'b000100000,
data_to_sda_1 = 9'b001000000,
data_to_sda_0 = 9'b010000000,
data_to_sda_end = 9'b100000000;
parameter sda_to_data_begin = 10'b0000000001, //串聯(lián)轉(zhuǎn)并聯(lián)
sda_to_data_7 = 10'b0000000010,
sda_to_data_6 = 10'b0000000100,
sda_to_data_5 = 10'b0000001000,
sda_to_data_4 = 10'b0000010000,
sda_to_data_3 = 10'b0000100000,
sda_to_data_2 = 10'b0001000000,
sda_to_data_1 = 10'b0010000000,
sda_to_data_0 = 10'b0100000000,
sda_to_data_end = 10'b1000000000;
parameter head_begin = 3'b001, //開始狀態(tài)
head_bit = 3'b010,
head_end = 3'b100;
parameter stop_begin = 3'b001, // 結(jié)束狀態(tài)
stop_bit = 3'b010,
stop_end = 3'b100;
task serial_to_perallel;
begin
casex(r_state)
sda_to_data_begin: begin
r_state <= sda_to_data_7;
link_sda<=1'b0;
end
sda_to_data_7 : if(scl)
begin
data_from_sda[7]<=sda;
r_state<=sda_to_data_6;
end
else
r_state<=sda_to_data_7;
sda_to_data_6 : if(scl)
begin
data_from_sda[6]<=sda;
r_state<=sda_to_data_5;
end
else
r_state<=sda_to_data_6;
sda_to_data_5 : if(scl)
begin
data_from_sda[5]<=sda;
r_state<=sda_to_data_4;
end
else
r_state<=sda_to_data_5;
sda_to_data_4 : if(scl)
begin
data_from_sda[4]<=sda;
r_state<=sda_to_data_3;
end
else
r_state<=sda_to_data_4;
sda_to_data_3 : if(scl)
begin
data_from_sda[3]<=sda;
r_state<=sda_to_data_2;
end
else
r_state<=sda_to_data_3;
sda_to_data_2 : if(scl)
begin
data_from_sda[2]<=sda;
r_state<=sda_to_data_1;
end
else
r_state<=sda_to_data_2;
sda_to_data_1 : if(scl)
begin
data_from_sda[1]<=sda;
r_state<=sda_to_data_0;
end
else
r_state<=sda_to_data_1;
sda_to_data_0 : if(scl)
begin
data_from_sda[0]<=sda;
r_state<=sda_to_data_end;
end
else
r_state<=sda_to_data_0;
sda_to_data_end : if(scl)
begin
link_read<=1'b1;
// link_sda<=1'b0;
ack_f<=1'b1;
r_state<=sda_to_data_7;
end
else
r_state<=sda_to_data_end;
/* default: begin
link_read<=1'b0;
r_state<=sda_to_data_7;
end*/
endcase
end
endtask
task perallel_to_serial;
begin
casex(w_state)
data_to_sda_7: if(!scl)
begin
link_sda<=1'b1;
link_write<=1'b1;
w_state<=data_to_sda_6;
end
else
w_state<=data_to_sda_7;
data_to_sda_6: if(!scl)
begin
link_sda<=1'b1;
link_write<=1'b1;
storage_buf<=storage_buf<<1;
w_state<=data_to_sda_5;
end
else
w_state<=data_to_sda_6;
data_to_sda_5: if(!scl)
begin
storage_buf<=storage_buf<<1;
w_state<=data_to_sda_4;
end
else
w_state<=data_to_sda_5;
data_to_sda_4: if(!scl)
begin
storage_buf<=storage_buf<<1;
w_state<=data_to_sda_3;
end
else
w_state<=data_to_sda_4;
data_to_sda_3: if(!scl)
begin
storage_buf<=storage_buf<<1;
w_state<=data_to_sda_2;
end
else
w_state<=data_to_sda_3;
data_to_sda_2: if(!scl)
begin
storage_buf<=storage_buf<<1;
w_state<=data_to_sda_1;
end
else
w_state<=data_to_sda_2;
data_to_sda_1: if(!scl)
begin
storage_buf<=storage_buf<<1;
w_state<=data_to_sda_0;
end
else
w_state<=data_to_sda_1;
data_to_sda_0: if(!scl)
begin
storage_buf<=storage_buf<<1;
w_state<=data_to_sda_end;
end
else
w_state<=data_to_sda_0;
data_to_sda_end: if(!scl)
begin
ack_f<=1'b1;
link_sda<=1'b0;
link_write<=1'b0;
end
//else
// w_state<=data_to_sda_end;
endcase
end
endtask
task head;
begin
casex(head_state)
head_begin:if(!scl)
begin
link_write<=1'b0;
link_sda<=1'b1;
link_head<=1'b1;
head_state<=head_bit;
end
else
head_state<=head_begin;
head_bit:if(scl)
begin
ack_f<=1'b1;
head_buf<=head_buf<<1;
head_state<=head_end;
end
else
head_state<=head_bit;
head_end:if(!scl)
begin
link_write<=1'b0;
link_head<=1'b0;
end
else
head_state<=head_end;
endcase
end
endtask
task stop_1;
begin
casex(stop_state)
stop_begin: if(!scl)
begin
link_sda<=1'b1;
link_stop<=1'b1;
//link_write<=1'b0;
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_sda<=1'b0;
link_stop<=1'b0;
//link_write<=1'b0;
ack_f<=1'b1;
end
else
stop_state<=stop_end;
endcase
end
endtask
always@(negedge clk) //產(chǎn)生scl
begin
if(reset)
scl<=0;
else
scl<=~scl;
end
always@(posedge clk)
if(reset)
begin
link_read<=1'b0;
link_write<=1'b0;
link_head<=1'b0;
link_stop<=1'b0;
link_sda<=1'b0;
ack=1'b0;
ack_f=1'b0;
head_buf<=2'b00;
stop_buf<=2'b00;
wf<=1'b0;
rf<=1'b0;
main_state<=idle;
end
else
begin
casex(main_state)
idle: begin
link_read<=1'b0;
link_write<=1'b0;
link_head<=1'b0;
link_stop<=1'b0;
link_sda<=1'b0;
if(wr)
begin
wf<=1'b1;
main_state<=ready;
end
else if(rd)
begin
rf<=1'b1;
main_state<=ready;
end
else
begin
rf<=1'b0;
wf<=1'b0;
main_state<=idle;
end
end
ready: begin
link_head<=1'b1;
link_sda<=1'b1;
head_buf<=2'b10;
stop_buf<=2'b01;
head_state<=head_begin;
main_state<=write_start;
end
write_start: if(ack_f==1'b0)
head;
else
begin
storage_buf<={1'b1,1'b0,1'b1,1'b0,addr[10:8],1'b0};
link_head<=1'b0;
link_write<=1'b1;
ack_f<=1'b0;
w_state<=data_to_sda_6;
main_state<=ctrl_write;
end
ctrl_write: if(ack_f==1'b0)
perallel_to_serial;
else
begin
w_state<=data_to_sda_7;
ack_f<=1'b0;
storage_buf<=addr[7:0];
main_state<=addr_write;
end
addr_write: if(ack_f==1'b0)
perallel_to_serial;
else if(wf==1'b1)
begin
ack_f<=1'b0;
main_state<=data_write;
storage_buf<=data;
w_state<=data_to_sda_7;
end
else if(rf==1'b1)
begin
ack_f<=1'b0;
main_state<=read_start;
head_state<=head_begin;
head_buf<=2'b10;
end
data_write: if(ack_f==1'b0)
perallel_to_serial;
else
begin
ack_f<=1'b0;
main_state<=stop;
stop_state<=stop_begin;
// link_write<=1'b0;
end
read_start:if(ack_f==1'b0)
head;
else
begin
ack_f<=1'b0;
main_state<=ctrl_read;
link_head<=1'b0;
storage_buf<={1'b1,1'b0,1'b1,1'b0,addr[10:8],1'b1};
link_write<=1'b1;
link_sda<=1'b1;
w_state<=data_to_sda_6;
end
ctrl_read: if(ack_f==1'b0)
perallel_to_serial;
else
begin
ack_f<=1'b0;
link_write<=1'b0;
link_sda<=1'b0;
r_state<=sda_to_data_begin;
main_state<=data_read;
end
data_read: if(ack_f==1'b0)
serial_to_perallel;
else
begin
ack_f<=1'b0;
main_state<=stop;
stop_state<=stop_bit;
link_stop<=1'b1;
link_sda<=1'b1;
end
stop: if(ack_f==1'b0)
stop_1;
else
begin
ack<=1'b1;
ack_f<=1'b0;
main_state<=ackn;
end
ackn: begin
ack<=1'b0;
wf<=1'b0;
rf<=1'b0;
main_state<=idle;
end
default:main_state<=idle;
endcase
end
endmodule
這里我想特別強調(diào)一下雙向端口sda沛厨,究竟何時是輸入端口何時是輸出端口呢宙地?在進(jìn)行寫入數(shù)據(jù)和控制字的寫入時是輸出端口,在進(jìn)行應(yīng)答位和數(shù)據(jù)讀出時是接受外部數(shù)據(jù)的輸入端口逆皮。
- link_sda是控制sda寫入的開關(guān)
-
out_flag來自EEPROM的內(nèi)部信號用于控制sda的輸入宅粥。
寫過程雙向sda信號的驅(qū)動.png讀過程雙向sda信號的驅(qū)動.png
通過讀寫過程的波形圖,我們可以看出电谣,在讀寫過程中秽梅,link_sda和out_flag總是有一個信號保持高電平,這樣的話才能保持sda信號有波形剿牺。
signal
為了測試EEPROM_WR企垦,signal模塊能夠?qū)Ρ粶y試模塊產(chǎn)生的ack信號產(chǎn)生相應(yīng),發(fā)出模仿MCU的數(shù)據(jù)晒来、地址信號和讀寫信號钞诡;被測試的模塊在接收到信號后會發(fā)出讀寫EEPROM虛擬模塊的信號。
本模塊為行為模塊,不能綜合成門級網(wǎng)表荧降。
`timescale 1ns/1ns
`define timeslice 200
module signal(data,
reset,
clk,
rd,
wr,
addr,
ack
);
input ack;
output clk,reset,rd,wr;
output[10:0] addr;
output[7:0] data;
wire[7:0] data;
reg clk,reset,rd,wr;
reg[10:0] addr;
reg W_R;
reg[7:0] data_to_eeprom;
reg[10:0] addr_mem[0:255];
reg[7:0] data_mem[0:255];
reg[7:0] ROM[0:2047];
integer i,j;
integer OUTFILE;
parameter test_number=50;
assign data=W_R ? 8'hzz : 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(test_number)
begin
#(5 * `timeslice)
wr=1;
#(`timeslice)
wr=0;
@(posedge ack);
end
#(10 * `timeslice)
W_R=1;
repeat(test_number)
begin
#(5 * `timeslice)
rd=1;
#(`timeslice)
rd=0;
@(posedge ack);
end
end
initial
begin
OUTFILE=$fopen("C:/Users/XQ/Desktop/eeprom_dat.txt");
$readmemh("C:/Users/XQ/Desktop/addr_dat.txt",addr_mem);
$readmemh("C:/Users/XQ/Desktop/data_dat.txt",data_mem);
end
initial
begin
$display("writing-------------------------------writing");
#(2*`timeslice)
for(i=0;i<=test_number;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("C:/Users/XQ/Desktop/eeprom_dat.txt",ROM);
$display("begin----------------------reading");
for(j=0;j<=test_number;j=j+1)
begin
addr=addr_mem[j];
@(posedge ack)
if(data==ROM[addr])
$display("data %0h == ROM[%0h]",data,addr);
else
$display("data %0h != ROM[%0h]",data,addr);
end
end
EEPROM_ALL_TOP
將各個部分集成在一起
`timescale 1ns/1ns
`define timeslice 200
module EEPROM_ALL_top;
wire[7:0] data;
wire[10:0] addr;
wire reset;
wire clk,rd,wr,ack;
wire sda,scl;
parameter test_numbers=5;
initial
begin
#(`timeslice*180*test_numbers)
$stop;
end
signal #(test_numbers) signal_1(.data(data),
.reset(reset),
.clk(clk),
.rd(rd),
.wr(wr),
.addr(addr),
.ack(ack));
EEPROM EEPROM_1 (.scl(scl),.sda(sda));
EEPROM_WR EEPROM_WR_1 (.sda(sda),
.scl(scl),
.ack(ack),
.reset(reset),
.clk(clk),
.wr(wr),
.rd(rd),
.addr(addr),
.data(data));
endmodule
總結(jié):其實要想徹底搞清楚這個復(fù)雜時序邏輯的工作機(jī)制接箫,還是需要大家真正上手寫一遍代碼