–作者:小黑同学
本文为明德扬原创及录用文章,转载请注明出处!
1.1 总体设计
1.1.1 概述
在微机的发展初期,BIOS都存放在ROM(只读存储器)中。ROM内部的资料是在ROM的制造工序中,在工厂里用特殊的方法烧录进去的,其中的内容只能读不能改,一旦烧录进去,用户只能验证写入的资料是否正确,不能再做任何修改。如果发现资料有任何错误,则只有舍弃不用,重新订做一份。ROM是在生产线上生产的,由于成本高,一般只用在大批量应用的场合。
由于ROM制造和升级的不便,后来人们发明了PROM(Programmable ROM,可编程ROM)。最初从工厂中制作完成的PROM内部并没有资料,用户可以用专用的编程器将自己的资料写入,但是这种机会只有一次,一旦写入后也无法修改,若是出了错误,已写入的芯片只能报废。PROM的特性和ROM相同,但是其成本比ROM高,而且写入资料的速度比ROM的量产速度要慢,一般只适用于少量需求的场合或是ROM量产前的验证。
EPROM(Erasable Programmable ROM,可擦除可编程ROM)芯片可重复擦除和写入,解决了PROM芯片只能写入一次的弊端。EPROM芯片有一个很明显的特征,在其正面的陶瓷封装上,开有一个玻璃窗口,透过该窗口,可以看到其内部的集成电路,紫外线透过该孔照射内部芯片就可以擦除其内的数据,完成芯片擦除的操作要用到EPROM擦除器。EPROM内资料的写入要用专用的编程器,并且往芯片中写内容时必须要加一定的编程电压(VPP=12~24V,随不同的芯片型号而定)。EPROM的型号是以27开头的,如27C020(8*256K)是一片2M Bits容量的EPROM芯片。EPROM芯片在写入资料后,还要以不透光的贴纸或胶布把窗口封住,以免受到周围的紫外线照射而使资料受损。
由于EPROM操作的不便,后来出的主板上BIOS ROM芯片大部分都采用EEPROM(Electrically Erasable Programmable ROM,电可擦除可编程ROM)。EEPROM的擦除不需要借助于其它设备,它是以电子信号来修改其内容的,而且是以Byte为最小修改单位,不必将资料全部洗掉才能写入,彻底摆脱了EPROM Eraser和编程器的束缚。EEPROM在写入数据时,仍要利用一定的编程电压,此时,只需用厂商提供的专用刷新程序就可以轻而易举地改写内容,所以,它属于双电压芯片。借助于EEPROM芯片的双电压特性,可以使BIOS具有良好的防毒功能,在升级时,把跳线开关打至“on”的位置,即给芯片加上相应的编程电压,就可以方便地升级;平时使用时,则把跳线开关打至“off”的位置,防止CIH类的病毒对BIOS芯片的非法修改。所以,仍有不少主板采用EEPROM作为BIOS芯片并作为自己主板的一大特色。
1.1.2 设计目标
整个工程由FPGA、矩阵键盘/按键、数码管和AT93C46组成,实现一个上电后能重新加载,接着上次计数的数字时钟,详细功能如下。
1、数码管显示时钟值,共使用了6个数码管,分别表示时十位、时个位、分十位、分个位、秒十位和秒个位。
2、矩阵键盘或者按键可以对数字时钟进行时分秒的设置。
A、上电后,时钟默认处于计时状态,当按键1按下,跳到时间设置状态,当按键1再次按下,回到计时状态。
B、当处于时间设置状态时,默认此刻设置的是秒个位,当按键2按下,此刻设置秒十位,以此类推,一次设置为分个位、分十位、时个位和时十位。再按下按键2,则重新设置秒个位。
C、当处于时间设置状态时,按下按键3,则设置位的值加1,如果溢出,则变成0。例如当目前小时显示05时,设置时十位,按下按键3,变成15,再按下按键3,则变成05.当目前小时显示为03时,设置时十位,按一下按键3,变成13,再按一下按键3,则变成23,再按则为03。
3、AT93C46则用于保存时钟值,其具有断电保护功能,断电数据不丢失。
A、AT93C46一共可以保存128字节的数据。工程将AT93C46分成空间1和空间2。空间1占用的地址为03,空间2占用的地址为47。
B、每隔1秒,保存当前时钟值。第一次保存到空间1,第二次保存到空间2,第三次保存带空间1,依此类推。(如果只有一个空间,则可能出现写数据过程中断电,从而得不到完整数据情况)
C、支持8位的CRC,生成多项式 ,初始值为全1。
D、每次保存的值,时十位、时个位、分十位、分个位、秒十位和秒个位各占4bit,共3个字节,加上1个字节的CRC,一共4个字节。
E、上电后,FPGA将读取两个空间的数值,并作CRC检验。如果两组数据的CRC检验均失败,则不重新加载;如果有一组数据CRC检验失败,则加载正确的一组数据;如果两组数据CRC检验均正确,则加载数值较大的一组数据。
1.1.3 系统结构框图
系统结构框图如下所示:
图一
1.1.4 模块功能
键盘(按键)扫描模块实现功能
1、将外来异步信号打两拍处理,将异步信号同步化。
2、实现20ms按键消抖功能。
3、实现矩阵键盘或者普通案件的检测功能,并输出有效按键信号。
时钟数据产生模块实现功能
负责产生数字时钟需要的时钟信息。包括:
1、按数字时钟方式进行计数
2、设置数字时钟的值
3、将时钟数据输出给外部模块使用
4、从数据处理模块得到时钟数据,并重新加载
数码管显示模块实现功能
1、对时钟数据进行译码,然后发到数码管显示
2、逐一显示时分秒的值
数据处理模块实现功能
负责写到AT93C46的数据,或者从AT93C46读到数据后的处理,包括:
1、上电后,发送EWEN命令,打开AT93C46的写保护。
2、发送EWEN命令后,开始读存储在AT93C46的两组时钟数据;对数据进行检验,然后选择适合的数据给时钟数据产生模块加载
3、每隔1秒从时钟数据产生模块获取时分秒的值,并产生CRC值,最后写道AT93C46上
CRC处理模块实现功能
负责CRC算法的模块,在数据处理模块内部使用
AT93C46模块实现功能
根据上游模块的EWEN、WRITE和READ命令,产生AT93C46的相应时序,从而写数据或者读到数据。至于数据是什么、有什么用,不关心,只关心AT93C46的时序。
1.1.5 顶层信号
信号名 | 接口方向 | 定义 |
---|---|---|
clk | 输入 | 系统时钟,50Mhz |
rst_n | 输入 | 低电平复位信号 |
Key_col | 输入 | 4位矩阵键盘列信号,默认高电平,开发板按键为普通按键时,不需要该信号 |
Key_row | 输出 | 4位矩阵键盘行信号,默认低电平,开发板按键为普通按键时,不需要该信号 |
Key | 输入 | 3位按键信号,开发板按键为矩阵键盘时,不需要该信号 |
Segment | 输出 | 8位数码管段选信号 |
Seg_sel | 输出 | 6位数码管位选信号 |
Mo | 输出 | At93c46数据输出 |
Mi | 输入 | At93c46数据输入 |
Cs | 输出 | At93c46片选信号 |
Sk | 输出 | At93c46时钟信号 |
1.1.6 参考代码
1.`define KEY_SCAN
2.module at93c46_top(
3. clk ,
4. rst_n ,
5. key_col ,
6. mo ,
7. cs ,
8. mi ,
9. sk ,
10. key_row ,
11. seg_sel ,
12. seg_data
13. );
14.
15. parameter TIME_1S = 50_000_000;
16. input clk ;
17. input rst_n ;
18. input [3:0] key_col ;
19. input mo ;
20.
21. output cs ;
22. output mi ;
23. output sk ;
24. output[3:0] key_row ;
25. output[5:0] seg_sel ;
26. output[7:0] seg_data ;
27.
28. wire rdy ;
29. wire rdata_vld ;
30. wire [3:0] key_en ;
31. wire [23:0] data_load ;
32. wire data_load_vld;
33. wire [23:0] clk_data_out ;
34. wire [6:0] addr ;
35. wire [1:0] mode ;
36. wire start ;
37. wire [7:0] wdata ;
38. wire [7:0] rdata ;
39.
40.
41. `ifdef KEY_SCAN
42. key_scan u_key_scan(
43. .clk (clk ),
44. .rst_n (rst_n ),
45. .key_col (key_col),
46. .key_row (key_row),
47. .key_en (key_en )
48. );
49. `else
50. key_module u_key_module(
51. .clk (clk ),
52. .rst_n (rst_n ),
53. .key_in (~key_col),
54. .key_vld (key_en )
55. );
56.
57. `endif
58. clock_data#(.TIME_1S(TIME_1S)) u_clock_data(
59. .clk (clk ),
60. .rst_n (rst_n ),
61. .data_load (data_load ),
62. .data_load_vld (data_load_vld ),
63. .key_en (key_en ),
64. .data_out (clk_data_out )
65. );
66.
67. seg_disp#(.SEG_NUM(6)) u_seg_disp(
68. .rst_n (rst_n ),
69. .clk (clk ),
70. .din (clk_data_out),
71. .din_vld ({6{1'b1}} ),
72. .seg_sel (seg_sel ),
73. .segment (seg_data )
74. );
75.
76.
77. data_processor#(.TIME_1S(TIME_1S)) u_data_pro(
78. .clk (clk ) ,
79. .rst_n (rst_n ) ,
80. .din (clk_data_out) ,
81. .start (start ) ,
82. .mode (mode ) ,
83. .addr (addr ) ,
84. .wdata (wdata ) ,
85. .rdata (rdata ) ,
86. .rdata_vld (rdata_vld ) ,
87. .rdy (rdy ) ,
88. .dout (data_load ) ,
89. .dout_vld (data_load_vld )
90.
91. );
92.
93.
94. at93c46_mix u_at93c46_mix(
95. .clk (clk ) ,
96. .rst_n (rst_n ) ,
97. .start (start ) ,
98. .mode (mode ) ,
99. .addr (addr ) ,
100. .wdata (wdata ) ,
101. .rdata (rdata ) ,
102. .rdata_vld (rdata_vld ) ,
103. .rdy (rdy ) ,
104. .do (mo ) ,
105. .di (mi ) ,
106. .cs (cs ) ,
107. .sk (sk )
108. );
109.endmodule
本工程会应用于不同的开发板,主要区别在于使用普通按键还是矩阵键盘,顶层代码中针对这一点进行了设计,如何开发板使用的是矩阵键盘,则顶层代码不需要改,如果使用的是普通按键,只需要将顶层代码最上面的一行删除或者注释掉就可以了。
1.2 键盘(按键)扫描模块设计
1.2.1 接口信号
下面为使用矩阵键盘时的接口信号:
信号 | 接口方向 | 定义 |
---|---|---|
clk | 输入 | 系统时钟 |
rst_n | 输入 | 低电平复位信号 |
key_col | 输入 | 矩阵键盘列输入信号 |
Key_row | 输出 | 矩阵键盘行输出信号 |
Key_en | 输出 | 按键按下位置指示信号 |
下面是使用普通按键时的接口信号:
信号 | 接口方向 | 定义 |
---|---|---|
clk | 输入 | 系统时钟 |
rst_n | 输入 | 低电平复位信号 |
Key_in | 输入 | 按键输入信号 |
Key_vld | 输出 | 按键按下指示信号 |
1.2.2 设计思路
在前面的按键控制数字时钟的案例中已经有介绍,所以这里不在过多介绍,详细介绍请看下方链接:
http://fpgabbs.com/forum.php?mod=viewthread&tid=310
1.2.3 参考代码
1.//矩阵键盘
2.always @(posedge clk or negedge rst_n)begin
3. if(rst_n==1'b0)begin
4. key_col_ff0 <= 4'b1111;
5. key_col_ff1 <= 4'b1111;
6. end
7. else begin
8. key_col_ff0 <= key_col ;
9. key_col_ff1 <= key_col_ff0;
10. end
11.end
12.
13.
14.always @(posedge clk or negedge rst_n) begin
15. if (rst_n==0) begin
16. shake_cnt <= 0;
17. end
18. else if(add_shake_cnt) begin
19. if(end_shake_cnt)
20. shake_cnt <= 0;
21. else
22. shake_cnt <= shake_cnt+1 ;
23. end
24.end
25.assign add_shake_cnt = key_col_ff1!=4'hf;
26.assign end_shake_cnt = add_shake_cnt && shake_cnt == TIME_20MS-1 ;
27.
28.
29.always @(posedge clk or negedge rst_n)begin
30. if(rst_n==1'b0)begin
31. state_c <= CHK_COL;
32. end
33. else begin
34. state_c <= state_n;
35. end
36.end
37.
38.always @(*)begin
39. case(state_c)
40. CHK_COL: begin
41. if(col2row_start )begin
42. state_n = CHK_ROW;
43. end
44. else begin
45. state_n = CHK_COL;
46. end
47. end
48. CHK_ROW: begin
49. if(row2del_start)begin
50. state_n = DELAY;
51. end
52. else begin
53. state_n = CHK_ROW;
54. end
55. end
56. DELAY : begin
57. if(del2wait_start)begin
58. state_n = WAIT_END;
59. end
60. else begin
61. state_n = DELAY;
62. end
63. end
64. WAIT_END: begin
65. if(wait2col_start)begin
66. state_n = CHK_COL;
67. end
68. else begin
69. state_n = WAIT_END;
70. end
71. end
72. default: state_n = CHK_COL;
73. endcase
74.end
75.assign col2row_start = state_c==CHK_COL && end_shake_cnt;
76.assign row2del_start = state_c==CHK_ROW && row_index==3 && end_row_cnt;
77.assign del2wait_start= state_c==DELAY && end_row_cnt;
78.assign wait2col_start= state_c==WAIT_END && key_col_ff1==4'hf;
79.
80.always @(posedge clk or negedge rst_n)begin
81. if(rst_n==1'b0)begin
82. key_row <= 4'b0;
83. end
84. else if(state_c==CHK_ROW)begin
85. key_row <= ~(1'b1 << row_index);
86. end
87. else begin
88. key_row <= 4'b0;
89. end
90.end
91.
92.
93.always @(posedge clk or negedge rst_n) begin
94. if (rst_n==0) begin
95. row_index <= 0;
96. end
97. else if(add_row_index) begin
98. if(end_row_index)
99. row_index <= 0;
100. else
101. row_index <= row_index+1 ;
102. end
103. else if(state_c!=CHK_ROW)begin
104. row_index <= 0;
105. end
106.end
107.assign add_row_index = state_c==CHK_ROW && end_row_cnt;
108.assign end_row_index = add_row_index && row_index == 4-1 ;
109.
110.
111.always @(posedge clk or negedge rst_n) begin
112. if (rst_n==0) begin
113. row_cnt <= 0;
114. end
115. else if(add_row_cnt) begin
116. if(end_row_cnt)
117. row_cnt <= 0;
118. else
119. row_cnt <= row_cnt+1 ;
120. end
121.end
122.assign add_row_cnt = state_c==CHK_ROW || state_c==DELAY;
123.assign end_row_cnt = add_row_cnt && row_cnt == 16-1 ;
124.
125.
126.always @(posedge clk or negedge rst_n)begin
127. if(rst_n==1'b0)begin
128. key_col_get <= 0;
129. end
130. else if(state_c==CHK_COL && end_shake_cnt ) begin
131. if(key_col_ff1==4'b1110)
132. key_col_get <= 0;
133. else if(key_col_ff1==4'b1101)
134. key_col_get <= 1;
135. else if(key_col_ff1==4'b1011)
136. key_col_get <= 2;
137. else
138. key_col_get <= 3;
139. end
140.end
141.
142.
143.always @(posedge clk or negedge rst_n)begin
144. if(rst_n==1'b0)begin
145. key_out <= 0;
146. end
147. else if(state_c==CHK_ROW && end_row_cnt)begin
148. key_out <= {row_index,key_col_get};
149. end
150. else begin
151. key_out <= 0;
152. end
153.end
154.
155.always @(posedge clk or negedge rst_n)begin
156. if(rst_n==1'b0)begin
157. key_vld <= 1'b0;
158. end
159. else if(state_c==CHK_ROW && end_row_cnt && key_col_ff1[key_col_get]==1'b0)begin
160. key_vld <= 1'b1;
161. end
162. else begin
163. key_vld <= 1'b0;
164. end
165.end
166.
167.
168.always @(*)begin
169. if(rst_n==1'b0)begin
170. key_en = 0;
171. end
172. else if(key_vld && key_out==0)begin
173. key_en = 4'b0001;
174. end
175. else if(key_vld && key_out==1)begin
176. key_en = 4'b0010;
177. end
178. else if(key_vld && key_out==2)begin
179. key_en = 4'b0100;
180. end
181. else begin
182. key_en = 0;
183. end
184.End
185.
186./*********************
187.普通按键
188.*********************/
189.always @(posedge clk or negedge rst_n)begin
190. if(rst_n==1'b0)begin
191. cnt <= 20'b0;
192. end
193. else if(add_cnt)begin
194. if(end_cnt)
195. cnt <= 20'b0;
196. else
197. cnt <= cnt + 1'b1;
198. end
199. else begin
200. cnt <= 0;
201. end
202.end
203.
204.assign add_cnt = flag_add==1'b0 && (&key_in_ff1==0);
205.assign end_cnt = add_cnt && cnt == TIME_20MS - 1;
206.
207.
208.always @(posedge clk or negedge rst_n)begin
209. if(rst_n==1'b0)begin
210. flag_add <= 1'b0;
211. end
212. else if(end_cnt)begin
213. flag_add <= 1'b1;
214. end
215. else if(&key_in_ff1==1)begin
216. flag_add <= 1'b0;
217. end
218.end
219.
220.
221.always @(posedge clk or negedge rst_n)begin
222. if(rst_n==1'b0)begin
223. key_in_ff0 <= {{KEY_W}{1'b1}};
224. key_in_ff1 <= {{KEY_W}{1'b1}};
225. end
226. else begin
227. key_in_ff0 <= key_in ;
228. key_in_ff1 <= key_in_ff0;
229. end
230.end
231.
232.
233.always @(posedge clk or negedge rst_n)begin
234. if(rst_n==1'b0)begin
235. key_vld <= 0;
236. end
237. else if(end_cnt)begin
238. key_vld <= ~key_in_ff1;
239. end
240. else begin
241. key_vld <= 0;
242. end
243.end
1.3 时间数据产生模块设计
1.3.1 接口信号
信号 | 接口方向 | 定义 |
---|---|---|
clk | 输入 | 系统时钟 |
rst_n | 输入 | 低电平复位信号 |
Data_load | 输入 | 重载的时钟数据,每四个一组,共6组。由高位至低位,分别是时十位、时个位、分十位、分个位、秒十位和秒个位 |
Data_load_vld | 输入 | 重载的时钟数据有效指示信号,当有效时,采用data_load的数据为最新的时钟数据 |
Key_num | 输入 | 按键位置输入信号,key_vld有效时,该信号有效 |
Key_vld | 输入 | 按键值有效信号,为1时表示检测到一个按键 |
data_out | 输出 | 当前的时钟数据,每四个1组,共六组,由高位至低位,分别是时十位、时个位、分十位、分个位、秒十位和秒个位 |
1.3.2 设计思路
本模块相对于前面的按键控制数字时钟案例中的时间数据产生模块来说,总体的设计思路是相同的,只是增加了一个重载的时钟信号,对于此信号的设计也比较简单,只需要在时分秒的个位和十位计数器中增加一句:在重载的时钟数据有效的时候,使计数器输出重载的时钟对应的数据即可,比如秒个位计数器应该输出重载时钟数据的第0到第3位数据,秒十位计数器应该输出重载时钟数据的第4到第7位数据,以此类推。
其他详细的设计思路可以看一下往期按键控制数字时钟的文章:
1.3.3 参考代码
1.module clock_data(
2. clk ,
3. rst_n ,
4. data_load ,
5. data_load_vld,
6. key_en ,
7. data_out
8. );
9.
10. parameter TIME_1S = 50_000_000 ;
11.
12. input clk ;
13. input rst_n ;
14. input data_load_vld;
15. input [23:0] data_load ;
16. input [ 3:0] key_en ;
17. output[23:0] data_out ;
18. wire [23:0] data_out ;
19.
20.
21. reg [25:0] cnt_1s ;
22. reg [3:0] miao_ge ;
23. reg [3:0] miao_shi ;
24. reg [3:0] fen_ge ;
25. reg [3:0] fen_shi ;
26. reg [3:0] shi_ge ;
27. reg [3:0] shi_shi ;
28. reg [2:0] set_sel ;
29. reg set_flag ;
30. wire add_set_sel ;
31. wire add_cnt_1s ;
32. wire add_miao_ge ;
33. wire add_miao_shi ;
34. wire add_fen_ge ;
35. wire add_fen_shi ;
36. wire add_shi_ge ;
37. wire add_shi_shi ;
38. wire end_cnt_1s ;
39. wire end_set_sel ;
40. wire end_miao_ge ;
41. wire end_miao_shi ;
42. wire end_fen_ge ;
43. wire end_fen_shi ;
44. wire end_shi_ge ;
45. wire end_shi_shi ;
46. wire set_miao_ge ;
47. wire set_miao_shi ;
48. wire set_fen_ge ;
49. wire set_fen_shi ;
50. wire set_shi_ge ;
51. wire set_shi_shi ;
52.
53. reg [ 3:0] x ;
54. reg [ 2:0] y ;
55.
56. always @(posedge clk or negedge rst_n)begin
57. if(rst_n==1'b0)begin
58. set_flag <= 1'b0;
59. end
60. else if(key_en[0]) begin
61. set_flag <= ~ set_flag;
62. end
63. end
64.
65.
66.
67. always @(posedge clk or negedge rst_n)begin
68. if(!rst_n)begin
69. set_sel <= 0;
70. end
71. else if(add_set_sel)begin
72. if(end_set_sel)
73. set_sel <= 0;
74. else
75. set_sel <= set_sel + 1;
76. end
77. else if(set_flag==0)begin
78. set_sel <= 0;
79. end
80. end
81.
82. assign add_set_sel = set_flag && key_en[1];
83. assign end_set_sel = add_set_sel && set_sel==6-1 ;
84.
85. always @(posedge clk or negedge rst_n)begin
86. if(!rst_n)begin
87. cnt_1s <= 0;
88. end
89. else if(add_cnt_1s)begin
90. if(end_cnt_1s)
91. cnt_1s <= 0;
92. else
93. cnt_1s <= cnt_1s + 1;
94. end
95. end
96.
97. assign add_cnt_1s = set_flag==0 ;
98. assign end_cnt_1s = add_cnt_1s && cnt_1s==TIME_1S-1 ;
99.
100.
101. always @(posedge clk or negedge rst_n)begin
102. if(!rst_n)begin
103. miao_ge <= 0;
104. end
105. else if(add_miao_ge)begin
106. if(end_miao_ge)
107. miao_ge <= 0;
108. else
109. miao_ge <= miao_ge + 1;
110. end
111. else if(data_load_vld)begin
112. miao_ge <= data_load[3:0];
113. end
114. end
115.
116. assign add_miao_ge = (end_cnt_1s || set_miao_ge) ;
117. assign end_miao_ge = add_miao_ge && miao_ge==10-1 ;
118. assign set_miao_ge = set_flag && set_sel==0 && key_en[2];
119.
120.
121. always @(posedge clk or negedge rst_n)begin
122. if(!rst_n)begin
123. miao_shi <= 0;
124. end
125. else if(add_miao_shi)begin
126. if(end_miao_shi)
127. miao_shi <= 0;
128. else
129. miao_shi <= miao_shi + 1;
130. end
131. else if(data_load_vld)begin
132. miao_shi <= data_load[7:4];
133. end
134. end
135.
136. assign add_miao_shi = (end_miao_ge || set_miao_shi);
137. assign end_miao_shi = add_miao_shi && miao_shi==6-1;
138. assign set_miao_shi = set_flag && set_sel==1 && key_en[2];
139.
140.
141. always @(posedge clk or negedge rst_n)begin
142. if(!rst_n)begin
143. fen_ge <= 0;
144. end
145. else if(add_fen_ge)begin
146. if(end_fen_ge)
147. fen_ge <= 0;
148. else
149. fen_ge <= fen_ge + 1;
150. end
151. else if(data_load_vld)begin
152. fen_ge <= data_load[11:8];
153. end
154. end
155.
156. assign add_fen_ge = (end_miao_shi || set_fen_ge);
157. assign end_fen_ge = add_fen_ge && fen_ge==10-1;
158. assign set_fen_ge = set_flag && set_sel==2 && key_en[2];
159.
160.
161. always @(posedge clk or negedge rst_n)begin
162. if(!rst_n)begin
163. fen_shi <= 0;
164. end
165. else if(add_fen_shi)begin
166. if(end_fen_shi)
167. fen_shi <= 0;
168. else
169. fen_shi <= fen_shi + 1;
170. end
171. else if(data_load_vld)begin
172. fen_shi <= data_load[15:12];
173. end
174. end
175.
176. assign add_fen_shi = (end_fen_ge || set_fen_shi);
177. assign end_fen_shi = add_fen_shi && fen_shi==6-1;
178. assign set_fen_shi = set_flag && set_sel==3 && key_en[2];
179.
180. always @(posedge clk or negedge rst_n)begin
181. if(!rst_n)begin
182. shi_ge <= 0;
183. end
184. else if(add_shi_ge)begin
185. if(end_shi_ge)
186. shi_ge <= 0;
187. else
188. shi_ge <= shi_ge + 1;
189. end
190. else if(data_load_vld)begin
191. shi_ge <= data_load[19:16];
192. end
193. end
194.
195. assign add_shi_ge = (end_fen_shi || set_shi_ge);
196. assign end_shi_ge = add_shi_ge && shi_ge==x-1;
197. assign set_shi_ge = set_flag && set_sel==4 && key_en[2];
198.
199.
200. always @(posedge clk or negedge rst_n)begin
201. if(!rst_n)begin
202. shi_shi <= 0;
203. end
204. else if(add_shi_shi)begin
205. if(end_shi_shi)
206. shi_shi <= 0;
207. else
208. shi_shi <= shi_shi + 1;
209. end
210. else if(data_load_vld)begin
211. shi_shi <= data_load[23:20];
212. end
213. end
214.
215. assign add_shi_shi = (end_shi_ge || set_shi_shi);
216. assign end_shi_shi = add_shi_shi && shi_shi==y-1;
217. assign set_shi_shi = set_flag && set_sel==5 && key_en[2];
218.
219. always @(*)begin
220. if(shi_shi<2)
221. x = 10;
222. else
223. x = 4;
224. end
225.
226. always @(*)begin
227. if(set_flag && set_sel==5 && shi_ge>=4)
228. y = 2;
229. else
230. y = 3;
231. end
232. assign data_out = {shi_shi,shi_ge,fen_shi,fen_ge,miao_shi,miao_ge};
233.
234.endmodule
1.4 数码管显示模块设计
1.4.1 接口信号
信号 | 接口方向 | 定义 |
---|---|---|
clk | 输入 | 系统时钟,50MHz |
rst_n | 输入 | 低电平复位信号 |
din | 输入 | 当前的时钟数据,每四位一组,共 6 组。由高位至低位,分别是时十位、时个位、分十位、分个位、秒十位和秒个位的值。 |
Seg_sel | 输出 | 数码管位选信号 |
Segment | 输出 | 数码管段选信号 |
1.4.2 设计思路
数码管显示在前面的案例文章已经有讲述,这里不再进行介绍,想了解的可以看一下往期文章:
1.4.3 参考代码
235.always @(posedge clk or negedge rst_n)begin
236. if(!rst_n)begin
237. cnt_2ms <= 0;
238. end
239. else if(add_cnt_2ms)begin
240. if(end_cnt_2ms)
241. cnt_2ms <= 0;
242. else
243. cnt_2ms <= cnt_2ms + 1;
244. end
245.end
246.
247.assign add_cnt_2ms = 1;
248.assign end_cnt_2ms = add_cnt_2ms && cnt_2ms==TIME_2MS-1 ;
249.
250.always @(posedge clk or negedge rst_n)begin
251. if(!rst_n)begin
252. cnt_sel <= 0;
253. end
254. else if(add_cnt_sel)begin
255. if(end_cnt_sel)
256. cnt_sel <= 0;
257. else
258. cnt_sel <= cnt_sel + 1;
259. end
260.end
261.
262.assign add_cnt_sel = end_cnt_2ms;
263.assign end_cnt_sel = add_cnt_sel && cnt_sel== SEG_NUM-1;
264.
265.
266.always @(posedge clk or negedge rst_n)begin
267. if(rst_n==1'b0)begin
268. seg_sel <= {SEG_NUM{1'b1}};
269. end
270. else begin
271. seg_sel <= ~(1'b1 << cnt_sel);
272. end
273.end
274.
275.always @(posedge clk or negedge rst_n)begin
276. if(rst_n==1'b0)begin
277. din_ff0 <= 0;
278. end
279. else begin
280. for(ii=0;ii<SEG_NUM;ii=ii+1)begin
281. if(din_vld[ii]==1'b1)begin
282. din_ff0[(ii+1)*4-1 -:4] <= din[(ii+1)*4-1 -:4];
283. end
284. else begin
285. din_ff0[(ii+1)*4-1 -:4] <= din_ff0[(ii+1)*4-1 -:4];
286. end
287. end
288. end
289.end
290.
291.always @(*)begin
292. seg_tmp = din_ff0[(cnt_sel+1)*4-1 -:4];
293.end
294.
295.
296.always@(posedge clk or negedge rst_n)begin
297. if(rst_n==1'b0)begin
298. segment<=NUM_0;
299. end
300. else begin
301. case(seg_tmp)
302. 4'd0:segment <= NUM_0;
303. 4'd1:segment <= NUM_1;
304. 4'd2:segment <= NUM_2;
305. 4'd3:segment <= NUM_3;
306. 4'd4:segment <= NUM_4;
307. 4'd5:segment <= NUM_5;
308. 4'd6:segment <= NUM_6;
309. 4'd7:segment <= NUM_7;
310. 4'd8:segment <= NUM_8;
311. 4'd9:segment <= NUM_9;
312. default:begin
313. segment <= NUM_ERR
314. end
315. endcase
316. end
317.End
1.5 数据处理模块设计
1.5.1 接口信号
信号 | 接口方向 | 定义 |
---|---|---|
clk | 输入 | 系统时钟 |
rst_n | 输入 | 低电平复位信号 |
din | 输入 | 时钟数据,每四位1组,共 6 组。由高位至低位,分别是时十位、时个位、分十位、分个位、秒十位和秒个位的值 |
dout | 输出 | 用于加载的时钟数据,每四位1组,共 6 组。由高位至低位,分别是时十位、时个位、分十位、分个位、秒十位和秒个位的值 |
dout_vld | 输出 | 时钟加载有效指示信号,高电平有效 |
Start | 输出 | 对AT93C46进行打开写保护、写数据或者读数据命令。注意,在rdy=0时,不应该使start有效 |
mode | 输出 | 对AT93C46的操作模式,start有效时,此值有效,0:打开写保护操作(EWEN),1:写数据操作(WRITE),2:读数据操作(READ),其他:不应该出现 |
addr 输出 读写AT93C46的地址信号,同时亦是EWEN的指示信号,使用EWEN命令时,此值必须为7’b1100000,在start有效时,此信号有效
Wdata 输出 写到AT93C46的数据,start=1,且mode=1时,此值有效
rdy 输入 AT93C46接口模块准备好信号,只有当其为1时,才能向接口模块发送命令
rdata 输入 从AT93C46读取的数据
rdata_vld 输入 从AT93C46读到的数据有效指示信号
1.5.2 设计思路
本模块主要负责写到AT93C46的数据或者读出的数据的处理,上电后,发送EWEN指令给AT93C46接口模块,打开AT93C46的写保护;发送EWEN命令后,开始读取存储在AT93C46保存的两组时钟数据;每隔1秒读取输入时钟数据的值,并产生CRC值,写到AT93C46上。
根据上面的功能描述,该模块采用状态机进行架构,可划分四个状态:打开写保护状态(S_EWEN)、读数据状态(S_READ)、空闲状态(S_IDLE)和写数据状态(S_WRIT),状态的跳转图如下:
由于功能要求只在刚上电或者复位的时候才读取AT93C46中的数据,因此刚上电就是写保护打开状态,或者复位有效时,进入写保护打开状态。由于复位是由按键控制的,因此在按下的时候会产生抖动,可能会导致产生很多个start,因此延时一段时间之后,如果AT93C46接口模块准备好,便进入读数据状态。数据读完之后,就进入空闲状态,等待计时1秒之后,开始将输入的时钟数据写入AT93C46中,写完四个字节数据之后重新回到空闲状态,等待计时1秒,如此循环。
下面介绍一下该模块中其他信号的设计思路:
时钟计数器time_cnt:该计数器是计数1秒的时间,从写保护打开状态跳转到读数据状态需要的延时和空闲状态跳转写数据状态需要的1秒的时间可使用此计数器表示;加一条件为1,表示一直计数;结束条件为数50000000个,表示1秒的时间,数完就清零。
写数据计数器wr_cnt:该计数器用于对要写入AT93C46的数据进行计数;加一条件为state_c==S_WRIT && rdy,表示在写数据状态的时候,如果AT93C46接口模块准备好,就开始计数;结束条件为数4个,3个字节的时钟数据加上1个字节的CRC校验,共四个字节,数完就清零。
读数据计数器:该计数器数的是从AT93C46读出,并经过CRC处理的数据字节数;加一条件为state_c==S_READ && crc_dout_vld,表示在读数据状态的时候,CRC处理完就计数一个;结束条件为数8个,AT93C46两个区域内共存有8个字节的数据,数完就清零。
写区间选择信号write_sel:初始状态为0,表示选择区间03,当写操作完成之后,该信号取反,变为1,表示选择区间47。
读写地址信号addr:初始状态为0,根据下方的表格(AT93C46的指令集),当处于写数据状态的时候,地址为7bit,由于本工程只会使用区间0~7来存储数据,因此地址为4bit0加上写区间选择信号write_sel加上写数据计数器;当处于写保护打开状态的时候,地址应为7’b11xxxxx,其中“x”表示不关注,工程中设为0即可;当处于读数据状态的时候,根据读数据计数器的变化选择地址即可,即地址为4’b0加上rd_cnt
AT93C46指令集
AT93C46开始命令start:初始状态为0,表示AT93C46不工作,当(add_wr_cnt || start_read),也就是在写数据、读数据或者写保护打开状态的时候,该信号拉高,指示AT93C46工作。
写数据wdata:该信号表示要往AT93C46中写入的数据,初始状态为0。前三个字节写入输入的时钟数据,第四个字节写入CRC。
AT93C46返回数据dout_temp:从AT93C46读出的8个字节数据,经过串并转换之后保存在该信号中。
第一区间CRC错误指示信号dout0_err:初始状态为0,表示没有错误;当读数据计数器数到第4个的时候,表示CRC模块已经完成了第一区间数据的校验,如果校验结果为0,表示正确,如果校验结果不等于0,表示输出错误。
第二区间CRC错误指示信号dout1_err:为了使错误指示信号跟数据能对齐,采用组合逻辑设计,当CRC模块输出数据为0,表示没有错误,该信号为0,如果CRC模块输出数据不为0,表示有错误,该信号为1。
用于加载的时钟数据dout:初始状态为0;当读数据计数器数完的时候,如果区间1和区间2的检验都没有错误,则比较AT93C46返回数据的高4字节和低4字节的大小,输出大的;如果区间1检验正确,区间2检验错误,则输出高4字节数据,反之则输出低4字节数据。
时钟加载有效指示信号dout_vld:初始状态为0,表示时钟数据无效;当读数据计数器数完的时候,如果第一区间和第二区间有最少一个正确,该信号就拉高,表示待加载的时钟数据有效,其他情况不拉高。
1.5.3 参考代码
318. always @(posedge clk or negedge rst_n)begin
319. if(rst_n==1'b0)begin
320. state_c <= S_EWEN;
321. end
322. else begin
323. state_c <= state_n;
324. end
325. end
326.
327. always @(*)begin
328. case(state_c)
329. S_EWEN : begin
330. if(ewen2read_start)begin
331. state_n = S_READ ;
332. end
333. else begin
334. state_n = state_c;
335. end
336. end
337. S_READ : begin
338. if(read2idle_start)begin
339. state_n = S_IDLE;
340. end
341. else begin
342. state_n = state_c;
343. end
344. end
345. S_IDLE : begin
346. if(idle2write_start)begin
347. state_n = S_WRIT;
348. end
349. else begin
350. state_n = state_c;
351. end
352. end
353. S_WRIT : begin
354. if(write2idle_start)begin
355. state_n = S_IDLE;
356. end
357. else begin
358. state_n = state_c;
359. end
360. end
361. default : begin
362. state_n = S_EWEN;
363. end
364. endcase
365. end
366.
367.
368. assign ewen2read_start = state_c==S_EWEN && add_time_cnt && time_cnt==1000-1 && rdy;
369. assign read2idle_start = state_c==S_READ && end_rd_cnt ;
370. assign idle2write_start = state_c==S_IDLE && end_time_cnt ;
371. assign write2idle_start = state_c==S_WRIT && end_wr_cnt ;
372.
373.
374. always @(posedge clk or negedge rst_n)begin
375. if(!rst_n)begin
376. time_cnt <= 0;
377. end
378. else if(add_time_cnt)begin
379. if(end_time_cnt)
380. time_cnt <= 0;
381. else
382. time_cnt <= time_cnt + 1;
383. end
384. end
385.
386. assign add_time_cnt = 1;
387. assign end_time_cnt = add_time_cnt && time_cnt==TIME_1S-1 ;
388.
389.
390. always @(posedge clk or negedge rst_n)begin
391. if(!rst_n)begin
392. wr_cnt <= 0;
393. end
394. else if(add_wr_cnt)begin
395. if(end_wr_cnt)
396. wr_cnt <= 0;
397. else
398. wr_cnt <= wr_cnt + 1;
399. end
400. end
401.
402. assign add_wr_cnt = state_c==S_WRIT && rdy;
403. assign end_wr_cnt = add_wr_cnt && wr_cnt == WR_NUM-1 ;
404.
405.
406. always @(posedge clk or negedge rst_n)begin
407. if(!rst_n)begin
408. rd_cnt <= 0;
409. end
410. else if(add_rd_cnt)begin
411. if(end_rd_cnt)
412. rd_cnt <= 0;
413. else
414. rd_cnt <= rd_cnt + 1;
415. end
416. end
417.
418. assign add_rd_cnt = state_c==S_READ && crc_dout_vld;
419. assign end_rd_cnt = add_rd_cnt && rd_cnt == RD_NUM-1 ;
420.
421.
422. assign start_read = state_c==S_READ && flag_wait_crc==0 && rdy;
423.
424. always @(posedge clk or negedge rst_n)begin
425. if(rst_n==1'b0)begin
426. flag_wait_crc <= 0;
427. end
428. else if(start_read)begin
429. flag_wait_crc <= 1;
430. end
431. else if(crc_dout_vld)begin
432. flag_wait_crc <= 0;
433. end
434. end
435.
436. always @(posedge clk or negedge rst_n)begin
437. if(rst_n==1'b0)begin
438. mode <= EWEN;
439. end
440. else if(state_c==S_EWEN) begin
441. mode <= EWEN;
442. end
443. else if(state_c==S_WRIT)begin
444. mode <= WRITE;
445. end
446. else if(state_c==S_READ)begin
447. mode <= READ;
448. end
449. end
450.
451.
452.
453. always @(posedge clk or negedge rst_n)begin
454. if(rst_n==1'b0)begin
455. crc_din <= 0;
456. end
457. else if(add_wr_cnt && end_wr_cnt==0) begin
458. crc_din <= din[8*(D_LEN-wr_cnt)-1 -:8];
459. end
460. else begin
461. crc_din <= rdata;
462. end
463. end
464.
465. always @(posedge clk or negedge rst_n)begin
466. if(rst_n==1'b0)begin
467. crc_din_vld <= 1'b0;
468. end
469. else if(add_wr_cnt && end_wr_cnt==0) begin
470. crc_din_vld <= 1'b1;
471. end
472. else begin
473. crc_din_vld <= rdata_vld;
474. end
475. end
476.
477. always @(posedge clk or negedge rst_n)begin
478. if(rst_n==1'b0)begin
479. crc_clr <= 1'b0;
480. end
481. else if((add_rd_cnt && rd_cnt==4-1)|| read2idle_start || write2idle_start) begin
482. crc_clr <= 1'b1;
483. end
484. else begin
485. crc_clr <= 1'b0;
486. end
487. end
488.
489. always @(posedge clk or negedge rst_n)begin
490. if(rst_n==1'b0)begin
491. addr <= 0;
492. end
493. else if(state_c==S_WRIT) begin
494. addr <= {4'b0,write_sel,wr_cnt[1:0]};
495. end
496. else if(state_c==S_EWEN) begin
497. addr <= 7'b1100000;
498. end
499. else begin
500. addr <= {4'b0,rd_cnt};
501. end
502. end
503.
504. always @(posedge clk or negedge rst_n)begin
505. if(rst_n==1'b0)begin
506. write_sel <= 1'b0;
507. end
508. else if(write2idle_start) begin
509. write_sel <= ~write_sel;
510. end
511. end
512.
513. always @(posedge clk or negedge rst_n)begin
514. if(rst_n==1'b0)begin
515. din_ff0 <= 0;
516. end
517. else if(write2idle_start) begin
518. din_ff0 <= din;
519. end
520. end
521.
522.
523. always @(posedge clk or negedge rst_n)begin
524. if(rst_n==1'b0)begin
525. wdata <= 0;
526. end
527. else if(wr_cnt==WR_NUM-1) begin
528. wdata <= crc_dout;
529. end
530. else begin
531. wdata <= din[8*(D_LEN-wr_cnt)-1 -:8];
532. end
533. end
534.
535. always @(posedge clk or negedge rst_n)begin
536. if(rst_n==1'b0)begin
537. start <= 1'b0;
538. end
539. else if(add_wr_cnt || start_read || ewen2read_start)begin
540. start <= 1'b1;
541. end
542. else begin
543. start <= 1'b0;
544. end
545. end
546.
547. always @(posedge clk or negedge rst_n)begin
548. if(rst_n==1'b0)begin
549. dout_temp<= 0;
550. end
551. else if(rdata_vld) begin
552. dout_temp[(8-rd_cnt)*8-1 -:8] <= rdata;
553. end
554. end
555.
556. always @(posedge clk or negedge rst_n)begin
557. if(rst_n==1'b0)begin
558. dout0_err <= 1'b0;
559. end
560. else if(add_rd_cnt && rd_cnt==4-1) begin
561. if(crc_dout!=0)
562. dout0_err <= 1'b1;
563. else
564. dout0_err <= 1'b0;
565. end
566. end
567.
568. always @(*)begin
569. if(crc_dout!=0)
570. dout1_err = 1'b1;
571. else
572. dout1_err = 1'b0;
573. end
574.
575.
576. assign dout0_temp = dout_temp[8*8-1 -:24];
577. assign dout1_temp = dout_temp[4*8-1 -:24];
578.
579.
580.
581.
582. always @(posedge clk or negedge rst_n)begin
583. if(rst_n==1'b0)begin
584. dout <= 0;
585. end
586. else if(end_rd_cnt) begin
587. if(dout0_err==1'b0 && dout1_err==1'b0)begin
588. if(dout0_temp > dout1_temp)
589. dout <= dout0_temp;
590. else
591. dout <= dout1_temp;
592. end
593. else if(dout0_err==1'b0)begin
594. dout <= dout0_temp;
595. end
596. else begin
597. dout <= dout1_temp;
598. end
599. end
600. end
601.
602.
603. always @(posedge clk or negedge rst_n)begin
604. if(rst_n==1'b0)begin
605. dout_vld <= 1'b0;
606. end
607. else if(end_rd_cnt) begin
608. if(dout0_err && dout1_err)begin
609. dout_vld <= 1'b0;
610. end
611. else begin
612. dout_vld <= 1'b1;
613. end
614. end
615. else begin
616. dout_vld <= 1'b0;
617. end
618. end
619.
620.
621. crc8_d8 u_crc8_d8(
622. .clk (clk ),
623. .rst_n (rst_n ),
624. .clr (crc_clr ),
625. .din_vld (crc_din_vld ),
626. .din (crc_din ),
627. .dout_vld (crc_dout_vld ),
628. .dout (crc_dout )
629. );
1.6 CRC处理模块设计
1.6.1 接口信号
信号 | 接口方向 | 定义 |
---|---|---|
clk 输入 | 系统时钟 | |
rst_n | 输入 | 低电平复位信号 |
Clr | 输入 | 清零信号,将当前的CRC运算复位,重新开始新的运算。 |
din_vld | 输入 | CRC输入数据有效信号 |
din | 输入 | CRC运算输入的数据 |
dout | 输出 | CRC运算结果 |
dout_vld | 输出 | CRC输出有效指示信号 |
1.6.2 设计思路
该模块主要的作用是负责CRC运算,在数据处理模块内部使用,多项式为,本模块代码不需要设计,使用网上的生成工具(https://www.easics.com/crctool/
),输入多项式即可生成,具体设置请看下图。
关于CRC的原理和并行、串行实现的方法,有另外的视频进行讲解,这里不在进行介绍。
1.6.3 参考代码
630. assign d = din ;
631. assign c = dout ;
632.
633.
634. always @(posedge clk or negedge rst_n)begin
635. if(rst_n==1'b0)begin
636. dout <= 0;
637. end
638. else if(clr)begin
639. dout <= 0;
640. end
641. else if(din_vld) begin
642. dout[0] <= d[7] ^ d[6] ^ d[0] ^ c[0] ^ c[6] ^ c[7];
643. dout[1] <= d[6] ^ d[1] ^ d[0] ^ c[0] ^ c[1] ^ c[6];
644. dout[2] <= d[6] ^ d[2] ^ d[1] ^ d[0] ^ c[0] ^ c[1] ^ c[2] ^ c[6];
645. dout[3] <= d[7] ^ d[3] ^ d[2] ^ d[1] ^ c[1] ^ c[2] ^ c[3] ^ c[7];
646. dout[4] <= d[4] ^ d[3] ^ d[2] ^ c[2] ^ c[3] ^ c[4];
647. dout[5] <= d[5] ^ d[4] ^ d[3] ^ c[3] ^ c[4] ^ c[5];
648. dout[6] <= d[6] ^ d[5] ^ d[4] ^ c[4] ^ c[5] ^ c[6];
649. dout[7] <= d[7] ^ d[6] ^ d[5] ^ c[5] ^ c[6] ^ c[7];
650. end
651. end
652.
653. always @(posedge clk or negedge rst_n)begin
654. if(rst_n==1'b0)begin
655. dout_vld <= 0;
656. end
657. else begin
658. dout_vld <= din_vld;
659. end
660. end
1.7 AT93C46接口模块设计
1.7.1 接口信号
信号 | 接口方向 | 定义 |
---|---|---|
clk | 输入 | 系统时钟 |
rst_n | 输入 | 低电平复位信号 |
addr | 输入 | 地址信号,在start有效时,此值有效 |
Wdata | 输入 | 写数据信号,在start有效时,此值有效 |
start | 输入 | 开始命令,仅在rdy=1时才有效 |
mode | 输出 | 操作模式指示信号,start有效时,此值有效 |
rdy | 输入 | 准备好信号。当rdy为1时,start才有效。在rdy为0时,不能使start有效 |
rdata | 输入 | 从AT93C46读取的数据 |
rdata_vld | 输出 | 从AT93C46读到的数据有效指示信号 |
do | 输出 | AT93C46数据输出 |
di | 输出 | AT93C46数据输入 |
cs | 输出 | AT93C46片选信号 |
sk | 输出 | AT93C46时钟,200KHz |
1.7.2 设计思路
参考数据手册,本模块主要实现三个命令:打开写保护(EWEN)、读数据(READ)和写数据(WRITE)。
下面时EWEN命令的时序图,结合上文提到的AT93C46的指令集,打开写保护指令的时序应该是写10bit数据之后,等待TCS时间,然后结束。
下面是READ命令的时序图。结合上文提到的AT93C46的指令集,读数据命令对应的时序应该是写10bit数据钟后,读8bit数据,等待TCS时间,然后结束。
下面是WRITE命令的时序图。结合上文提到的AT93C46的指令集,写数据命令对应的时序应该是写18bit数据,cs拉低TCS时间,等待TWP(5ms)时间,然后结束。
根据上述的时序介绍,本模块采用3个计数器的架构,下面是计数器的架构图。
架构中的三个计数器分别为时钟计数器cnt0、比特计数器cnt1和阶段计数器cnt2,flag_work为工作状态指示信号。
时钟计数器cnt0:该计数器用来计数时钟的个数。加一条件为flag_work,表示进入工作状态就开始计数。结束条件为数x个,根据不同的工作模式和所处的阶段不同而变化。包括SK的时钟周期数、等待时间TCS、等待5ms时间。
比特计数器cnt1:该计数器用来数有多少bit数据,加一条件为end_cnt0,表示每数完1bit,就加1;结束条件为数y个,y分别为10(EWEN)、18(READ和WRITE)、1(等待TCS和5ms)。
阶段计数器cnt2:该计数器用来对每个指令需要的阶段进行计数。加一条件为end_cnt1,表示发送完一组数据就加一;结束条件为数u个,u分别为2(EWEN和READ)、3(WRITE)。
除了上述的计数器之外,还有一些比较重要的信号,我们来分析一下如何进行设计。
工作状态指示信号flag_work:初始状态为0,表示处于空闲状态;当收到开始命令start的时候,变化变为1,由空闲状态转为工作状态;当前指令的时序结束之后,也就是阶段计数器cnt2数完,就变为0,进入空闲状态。
待发送数据dout:当接收到开始命令的时候,根据AT93C46的指令集,将SB、Opcode、Address和data按照顺序拼接到一起。
模式暂存器mode_reg:为保证在发送时序期间保持不变,在接收到开始命令的时候,将操作模式指示信号进行暂存。
AT93C46时钟sk:时钟频率为200KHz,工程的系统时钟为50MHz,因此sk一个完整的周期需要250个系统时钟周期,初始状态设为低电平,当时钟计数器数到125个的时候置为高电平,时钟计数器数完,在置为低电平。
AT93C46数据输入di:在每个指令时序的第一阶段,也就是cnt2=1-1的时候,根据比特计数器要数的个数,将待发送的数据dout送给di。
AT93C46片选信号cs:在写比特数据、等待5ms期间为高,其他时候都为低,所以该信号拉高的条件为(start_vld1 || (add_cnt2 && cnt22-1 && end_cnt20))。其他时候片选信号都为低,所以拉低的条件为((add_cnt2 && cnt21-1) || end_cnt2)。
读取数据rdata:在读模式下,处于第一阶段,并且在第11~18bit的时候,将AT93C46输出数据do保存到rdata里面。
准备好信号rdy:由组合逻辑产生,当接收到开始命令,或者处于工作状态的时候,为低电平,表示没有准备好;其他时刻为高电平,表示准备好。
1.7.3 参考代码
1. assign start_vld = flag_work==0 && start;
2.
3. always @(*)begin
4. if(mode==EWEN)
5. opcode = 3'b100;
6. else if(mode==WRITE)
7. opcode = 3'b101;
8. else
9. opcode = 3'b110;
10. end
11.
12. always @(posedge clk or negedge rst_n)begin
13. if(rst_n==1'b0)begin
14. dout <= 0;
15. end
16. else if(start_vld) begin
17. dout <={opcode,addr,wdata};
18. end
19. end
20.
21.
22. always @(posedge clk or negedge rst_n)begin
23. if(rst_n==1'b0)begin
24. mode_reg <= 0;
25. end
26. else if(start_vld) begin
27. mode_reg <= mode;
28. end
29. end
30.
31. always @(posedge clk or negedge rst_n)begin
32. if(rst_n==1'b0)begin
33. flag_work <= 0;
34. end
35. else if(start_vld) begin
36. flag_work <= 1;
37. end
38. else if(end_cnt2)begin
39. flag_work <= 0;
40. end
41. end
42.
43.
44.
45. always @(posedge clk or negedge rst_n)begin
46. if(!rst_n)begin
47. cnt0 <= 0;
48. end
49. else if(add_cnt0)begin
50. if(end_cnt0)
51. cnt0 <= 0;
52. else
53. cnt0 <= cnt0 + 1;
54. end
55. end
56.
57. assign add_cnt0 = flag_work;
58. assign end_cnt0 = add_cnt0 && cnt0== x-1;
59.
60. always @(posedge clk or negedge rst_n)begin
61. if(!rst_n)begin
62. cnt1 <= 0;
63. end
64. else if(add_cnt1)begin
65. if(end_cnt1)
66. cnt1 <= 0;
67. else
68. cnt1 <= cnt1 + 1;
69. end
70. end
71.
72. assign add_cnt1 = end_cnt0;
73. assign end_cnt1 = add_cnt1 && cnt1==y-1 ;
74.
75.
76. always @(posedge clk or negedge rst_n)begin
77. if(!rst_n)begin
78. cnt2 <= 0;
79. end
80. else if(add_cnt2)begin
81. if(end_cnt2)
82. cnt2 <= 0;
83. else
84. cnt2 <= cnt2 + 1;
85. end
86. end
87.
88. assign add_cnt2 = end_cnt1;
89. assign end_cnt2 = add_cnt2 && cnt2==u-1;
90.
91.
92. assign en_sk1 = add_cnt0 && cnt0==x/2-1 && cnt2==1-1;
93. assign en_sk0 = end_cnt0 ;
94. always @(posedge clk or negedge rst_n)begin
95. if(rst_n==1'b0)begin
96. sk <= 0;
97. end
98. else if(add_cnt0 && cnt0==x/2-1 && cnt2==1-1)begin
99. sk <= 1;
100. end
101. else if(end_cnt0)begin
102. sk <= 0;
103. end
104. end
105.
106. assign en_di = add_cnt0 && cnt0==1-1 && cnt2==1-1;
107.
108. always @(posedge clk or negedge rst_n)begin
109. if(rst_n==1'b0)begin
110. di <= 0;
111. end
112. else if(en_di) begin
113. di <= dout[17-cnt1];
114. end
115. end
116.
117.
118. always @(posedge clk or negedge rst_n)begin
119. if(rst_n==1'b0)begin
120. cs <= 0;
121. end
122. else if(start_vld==1 || (add_cnt2 && cnt2==2-1 && end_cnt2==0)) begin
123. cs <= 1;
124. end
125. else if((add_cnt2 && cnt2==1-1) || end_cnt2)begin
126. cs <= 0;
127. end
128. end
129.
130.
131. always @(posedge clk or negedge rst_n)begin
132. if(rst_n==1'b0)begin
133. rdata <= 0;
134. end
135. else if(end_cnt0 && cnt1 >=10 && cnt2==1-1 && mode_reg==READ ) begin
136. rdata[17-cnt1] <= do;
137. end
138. end
139.
140. always @(posedge clk or negedge rst_n)begin
141. if(rst_n==1'b0)begin
142. rdata_vld <= 0;
143. end
144. else begin
145. rdata_vld <= end_cnt2 && mode_reg==READ;
146. end
147. end
148.
149. always @(*)begin
150. if(start || flag_work)
151. rdy = 1'b0;
152. else
153. rdy = 1'b1;
154. end
155.
156.
157. always @(*)begin
158. if(mode_reg==WRITE && cnt2==1-1)begin
159. x = 250;
160. y = 18;
161. u = 3;
162. end
163. else if(mode_reg==WRITE && (cnt2==2-1 ))begin
164. x = 250 ;
165. y = 1 ;
166. u = 3 ;
167. end
168. else if(mode_reg==WRITE && cnt2==3-1)begin
169. x = 500000;
170. y = 1 ;
171. u = 3 ;
172. end
173. else if(mode_reg==READ && cnt2==1-1)begin
174. x = 250;
175. y = 18;
176. u = 2;
177. end
178. else if(mode_reg==READ && cnt2==2-1)begin
179. x = 250 ;
180. y = 1 ;
181. u = 2 ;
182. end
183. else if(mode_reg==EWEN && cnt2==1-1)begin
184. x = 250;
185. y = 10;
186. u = 2;
187. end
188. else begin
189. x = 250 ;
190. y = 1 ;
191. u = 2 ;
192. end
193. end
1.8 效果和总结
本工程上板之后,可通过复位来验证现象,若要通过断电来进行验证,需要将工程烧录进开发板才行。
1.8.1 db603开发板
由于本工程现象是一个动态的过程,所以从下面图片中看不出具体实现的效果,想要看上板效果的话可以看一下工程上板的视频。
1.8.2 mp801开发板
由于本工程现象是一个动态的过程,所以从下面图片中看不出具体实现的效果,想要看上板效果的话可以看一下工程上板的视频。
1.8.3 ms980试验箱
由于本工程现象是一个动态的过程,所以从下面图片中看不出具体实现的效果,想要看上板效果的话可以看一下工程上板的视频。
感兴趣的朋友也可以访问明德扬论坛进行FPGA相关工程设计学习,也可以看一下我们往期的文章: