官方论坛
官方淘宝
官方博客
微信公众号
点击联系吴工 点击联系周老师
您的当前位置:主页 > FPGA学习资料 >

【每周FPGA案例】 断电重加载时钟工程

发布时间:2020-09-17   作者:admin 浏览量:
本文为明德扬原创及录用文章,转载请注明出处
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芯片大部分都采用EEPROMElectrically 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占用的地址为0~3,空间2占用的地址为4~7。

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模块实现功能
根据上游模块的EWENWRITEREAD命令,产生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_scan(
  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.     parameter    TIME_1S = 50_000_000;


  15.     input               clk          ;
  16.     input               rst_n        ;
  17.     input [3:0]         key_col      ;
  18.     input               mo           ;

  19.     output              cs           ;
  20.     output              mi           ;
  21.     output              sk           ;
  22.     output[3:0]         key_row      ;
  23.     output[5:0]         seg_sel      ;
  24.     output[7:0]         seg_data     ;

  25.     wire                rdy          ;
  26.     wire                rdata_vld    ;
  27.     wire  [3:0]         key_en       ;

  28.     wire  [23:0]        data_load    ;
  29.     wire                data_load_vld;

  30.     wire  [23:0]        clk_data_out ;

  31.     wire  [6:0]         addr         ;
  32.     wire  [1:0]         mode         ;
  33.     wire                start        ;

  34.     wire  [7:0]         wdata        ;
  35.     wire  [7:0]         rdata        ;




  36.              `ifdef KEY_SCAN
  37.     key_scan  u_key_scan(
  38.                 .clk     (clk    ),
  39.                 .rst_n   (rst_n  ),
  40.                 .key_col (key_col),
  41.                 .key_row (key_row),
  42.                 .key_en  (key_en )
  43.              );
  44.             `else            
  45.    key_module  u_key_module(
  46.                 .clk     (clk     ),
  47.                 .rst_n   (rst_n   ),
  48.                 .key_in  (~key_col),
  49.                 .key_vld (key_en  )
  50.              );

  51.             `endif
  52.     clock_data#(.TIME_1S(TIME_1S)) u_clock_data(
  53.                 .clk           (clk           ),
  54.                 .rst_n         (rst_n         ),
  55.                 .data_load     (data_load     ),
  56.                 .data_load_vld (data_load_vld ),
  57.                 .key_en        (key_en        ),
  58.                 .data_out      (clk_data_out  )
  59.     );

  60.     seg_disp#(.SEG_NUM(6)) u_seg_disp(
  61.                 .rst_n       (rst_n       ),
  62.                 .clk         (clk         ),
  63.                 .din         (clk_data_out),
  64.                 .din_vld     ({6{1'b1}}   ),
  65.                 .seg_sel     (seg_sel     ),
  66.                 .segment     (seg_data    )
  67.              );
  68.    
  69.    
  70.    data_processor#(.TIME_1S(TIME_1S)) u_data_pro(
  71.                       .clk        (clk         )  ,
  72.                       .rst_n      (rst_n       )  ,
  73.                       .din        (clk_data_out)  ,
  74.                       .start      (start       )  ,
  75.                       .mode       (mode        )  ,
  76.                       .addr       (addr        )  ,
  77.                       .wdata      (wdata       )  ,
  78.                       .rdata      (rdata       )  ,
  79.                       .rdata_vld  (rdata_vld   )  ,
  80.                       .rdy        (rdy         )  ,
  81.                       .dout       (data_load   )  ,
  82.                       .dout_vld   (data_load_vld )   
  83.    
  84.     );


  85.     at93c46_mix u_at93c46_mix(
  86.                    .clk        (clk        )  ,
  87.                    .rst_n      (rst_n      )  ,
  88.                    .start      (start      )  ,
  89.                    .mode       (mode       )  ,
  90.                    .addr       (addr       )  ,
  91.                    .wdata      (wdata      )  ,
  92.                    .rdata      (rdata      )  ,
  93.                    .rdata_vld  (rdata_vld  )  ,
  94.                    .rdy        (rdy        )  ,
  95.                    .do         (mo         )  ,
  96.                    .di         (mi         )  ,
  97.                    .cs         (cs         )  ,
  98.                    .sk         (sk         )   
  99.     );


  100. 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. module  key_scan(
  2.                  clk    ,
  3.                  rst_n  ,
  4.                  key_col,
  5.                  key_row,
  6.                  key_en   
  7.                );


  8.     parameter      KEY_W    =   4      ;
  9.     parameter      CHK_COL  =   0      ;
  10.     parameter      CHK_ROW  =   1      ;
  11.     parameter      DELAY    =   2      ;
  12.     parameter      WAIT_END =   3      ;
  13.     parameter      COL_CNT  =   16     ;
  14.     parameter      TIME_20MS=   1000000;

  15.     input               clk              ;
  16.     input               rst_n            ;
  17.     input [3:0]         key_col          ;

  18.     output[3:0]         key_en           ;
  19.     output[KEY_W-1:0]   key_row          ;

  20.     reg   [3:0]         key_out          ;
  21.     reg   [KEY_W-1:0]   key_row          ;
  22.     reg                 key_vld          ;


  23.     reg   [3:0]         key_col_ff0      ;
  24.     reg   [3:0]         key_col_ff1      ;
  25.     reg   [1:0]         key_col_get      ;
  26.     reg   [3:0]         key_en           ;
  27.     wire                end_shake_cnt    ;
  28.     reg                 end_shake_cnt_ff0;
  29.     reg   [3:0]         state_c          ;
  30.     reg   [19:0]        shake_cnt        ;
  31.     reg   [3:0]         state_n          ;
  32.     reg   [1:0]         row_index        ;
  33.     reg   [15:0]        row_cnt          ;
  34.     wire                col2row_start    ;
  35.     wire                row2del_start    ;
  36.     wire                del2wait_start   ;
  37.     wire                wait2col_start   ;
  38.     wire                add_row_cnt      ;
  39.     wire                end_row_cnt      ;
  40.     wire                add_shake_cnt    ;
  41.     wire                add_row_index    ;
  42.     wire                end_row_index    ;


  43. always  @(posedge clk or negedge rst_n)begin
  44.     if(rst_n==1'b0)begin
  45.         key_col_ff0 <= 4'b1111;
  46.         key_col_ff1 <= 4'b1111;
  47.     end
  48.     else begin
  49.         key_col_ff0 <= key_col    ;
  50.         key_col_ff1 <= key_col_ff0;
  51.     end
  52. end


  53. always @(posedge clk or negedge rst_n) begin
  54.     if (rst_n==0) begin
  55.         shake_cnt <= 0;
  56.     end
  57.     else if(add_shake_cnt) begin
  58.         if(end_shake_cnt)
  59.             shake_cnt <= 0;
  60.         else
  61.             shake_cnt <= shake_cnt+1 ;
  62.    end
  63. end
  64. assign add_shake_cnt = key_col_ff1!=4'hf;
  65. assign end_shake_cnt = add_shake_cnt  && shake_cnt == TIME_20MS-1 ;


  66. always  @(posedge clk or negedge rst_n)begin
  67.     if(rst_n==1'b0)begin
  68.         state_c <= CHK_COL;
  69.     end
  70.     else begin
  71.         state_c <= state_n;
  72.     end
  73. end

  74. always  @(*)begin
  75.     case(state_c)
  76.         CHK_COL: begin
  77.                      if(col2row_start )begin
  78.                          state_n = CHK_ROW;
  79.                      end
  80.                      else begin
  81.                          state_n = CHK_COL;
  82.                      end
  83.                  end
  84.         CHK_ROW: begin
  85.                      if(row2del_start)begin
  86.                          state_n = DELAY;
  87.                      end
  88.                      else begin
  89.                          state_n = CHK_ROW;
  90.                      end
  91.                  end
  92.         DELAY :  begin
  93.                      if(del2wait_start)begin
  94.                          state_n = WAIT_END;
  95.                      end
  96.                      else begin
  97.                          state_n = DELAY;
  98.                      end
  99.                  end
  100.         WAIT_END: begin
  101.                      if(wait2col_start)begin
  102.                          state_n = CHK_COL;
  103.                      end
  104.                      else begin
  105.                          state_n = WAIT_END;
  106.                      end
  107.                   end
  108.        default: state_n = CHK_COL;
  109.     endcase
  110. end
  111. assign col2row_start = state_c==CHK_COL  && end_shake_cnt;
  112. assign row2del_start = state_c==CHK_ROW  && row_index==3 && end_row_cnt;
  113. assign del2wait_start= state_c==DELAY    && end_row_cnt;
  114. assign wait2col_start= state_c==WAIT_END && key_col_ff1==4'hf;

  115. always  @(posedge clk or negedge rst_n)begin
  116.     if(rst_n==1'b0)begin
  117.         key_row <= 4'b0;
  118.     end
  119.     else if(state_c==CHK_ROW)begin
  120.         key_row <= ~(1'b1 << row_index);
  121.     end
  122.     else begin
  123.         key_row <= 4'b0;
  124.     end
  125. end





  126. always @(posedge clk or negedge rst_n) begin
  127.     if (rst_n==0) begin
  128.         row_index <= 0;
  129.     end
  130.     else if(add_row_index) begin
  131.         if(end_row_index)
  132.             row_index <= 0;
  133.         else
  134.             row_index <= row_index+1 ;
  135.    end
  136.    else if(state_c!=CHK_ROW)begin
  137.        row_index <= 0;
  138.    end
  139. end
  140. assign add_row_index = state_c==CHK_ROW && end_row_cnt;
  141. assign end_row_index = add_row_index  && row_index == 4-1 ;


  142. always @(posedge clk or negedge rst_n) begin
  143.     if (rst_n==0) begin
  144.         row_cnt <= 0;
  145.     end
  146.     else if(add_row_cnt) begin
  147.         if(end_row_cnt)
  148.             row_cnt <= 0;
  149.         else
  150.             row_cnt <= row_cnt+1 ;
  151.    end
  152. end
  153. assign add_row_cnt = state_c==CHK_ROW || state_c==DELAY;
  154. assign end_row_cnt = add_row_cnt  && row_cnt == 16-1 ;



  155. always  @(posedge clk or negedge rst_n)begin
  156.     if(rst_n==1'b0)begin
  157.         key_col_get <= 0;
  158.     end
  159.     else if(state_c==CHK_COL && end_shake_cnt ) begin
  160.         if(key_col_ff1==4'b1110)
  161.             key_col_get <= 0;
  162.         else if(key_col_ff1==4'b1101)
  163.             key_col_get <= 1;
  164.         else if(key_col_ff1==4'b1011)
  165.             key_col_get <= 2;
  166.         else
  167.             key_col_get <= 3;
  168.     end
  169. end


  170. always  @(posedge clk or negedge rst_n)begin
  171.     if(rst_n==1'b0)begin
  172.         key_out <= 0;
  173.     end
  174.     else if(state_c==CHK_ROW && end_row_cnt)begin
  175.         key_out <= {row_index,key_col_get};
  176.     end
  177.     else begin
  178.         key_out <= 0;
  179.     end
  180. end

  181. always  @(posedge clk or negedge rst_n)begin
  182.     if(rst_n==1'b0)begin
  183.         key_vld <= 1'b0;
  184.     end
  185.     else if(state_c==CHK_ROW && end_row_cnt && key_col_ff1[key_col_get]==1'b0)begin
  186.         key_vld <= 1'b1;
  187.     end
  188.     else begin
  189.         key_vld <= 1'b0;
  190.     end
  191. end


  192. always  @(*)begin
  193.     if(rst_n==1'b0)begin
  194.         key_en = 0;
  195.     end
  196.     else if(key_vld && key_out==0)begin
  197.         key_en = 4'b0001;
  198.     end
  199.     else if(key_vld && key_out==1)begin
  200.         key_en = 4'b0010;
  201.     end
  202.     else if(key_vld && key_out==2)begin
  203.         key_en = 4'b0100;
  204.     end
  205.     else begin
  206.         key_en = 0;
  207.     end
  208. 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.     parameter      TIME_1S    =       50_000_000   ;

  10.     input               clk          ;
  11.     input               rst_n        ;
  12.     input               data_load_vld;
  13.     input [23:0]        data_load    ;
  14.     input [ 3:0]        key_en       ;
  15.     output[23:0]        data_out     ;
  16.     wire  [23:0]        data_out     ;


  17.     reg   [25:0]        cnt_1s       ;
  18.     reg   [3:0]         miao_ge      ;
  19.     reg   [3:0]         miao_shi     ;
  20.     reg   [3:0]         fen_ge       ;
  21.     reg   [3:0]         fen_shi      ;
  22.     reg   [3:0]         shi_ge       ;
  23.     reg   [3:0]         shi_shi      ;
  24.     reg   [2:0]         set_sel      ;
  25.     reg                 set_flag     ;
  26.     wire                add_set_sel  ;
  27.     wire                add_cnt_1s   ;
  28.     wire                add_miao_ge  ;
  29.     wire                add_miao_shi ;
  30.     wire                add_fen_ge   ;
  31.     wire                add_fen_shi  ;
  32.     wire                add_shi_ge   ;
  33.     wire                add_shi_shi  ;
  34.     wire                end_cnt_1s   ;
  35.     wire                end_set_sel  ;
  36.     wire                end_miao_ge  ;
  37.     wire                end_miao_shi ;
  38.     wire                end_fen_ge   ;
  39.     wire                end_fen_shi  ;
  40.     wire                end_shi_ge   ;
  41.     wire                end_shi_shi  ;
  42.     wire                set_miao_ge  ;
  43.     wire                set_miao_shi ;
  44.     wire                set_fen_ge   ;
  45.     wire                set_fen_shi  ;
  46.     wire                set_shi_ge   ;
  47.     wire                set_shi_shi  ;

  48.     reg  [ 3:0]         x            ;
  49.     reg  [ 2:0]         y            ;

  50.     always  @(posedge clk or negedge rst_n)begin
  51.         if(rst_n==1'b0)begin
  52.             set_flag <= 1'b0;
  53.         end
  54.         else if(key_en[0]) begin
  55.             set_flag <= ~ set_flag;
  56.         end
  57.     end



  58.     always @(posedge clk or negedge rst_n)begin
  59.         if(!rst_n)begin
  60.             set_sel <= 0;
  61.         end
  62.         else if(add_set_sel)begin
  63.             if(end_set_sel)
  64.                 set_sel <= 0;
  65.             else
  66.                 set_sel <= set_sel + 1;
  67.         end
  68.         else if(set_flag==0)begin
  69.             set_sel <= 0;
  70.         end
  71.     end

  72.     assign add_set_sel = set_flag && key_en[1];      
  73.     assign end_set_sel = add_set_sel && set_sel==6-1 ;   

  74.     always @(posedge clk or negedge rst_n)begin
  75.         if(!rst_n)begin
  76.             cnt_1s <= 0;
  77.         end
  78.         else if(add_cnt_1s)begin
  79.             if(end_cnt_1s)
  80.                 cnt_1s <= 0;
  81.             else
  82.                 cnt_1s <= cnt_1s + 1;
  83.         end
  84.     end

  85.     assign add_cnt_1s = set_flag==0 ;      
  86.     assign end_cnt_1s = add_cnt_1s && cnt_1s==TIME_1S-1 ;   





  87.     always @(posedge clk or negedge rst_n)begin
  88.         if(!rst_n)begin
  89.             miao_ge <= 0;
  90.         end
  91.         else if(add_miao_ge)begin
  92.             if(end_miao_ge)
  93.                 miao_ge <= 0;
  94.             else
  95.                 miao_ge <= miao_ge + 1;
  96.         end
  97.         else if(data_load_vld)begin
  98.             miao_ge <=  data_load[3:0];
  99.         end
  100.     end

  101.     assign add_miao_ge = (end_cnt_1s || set_miao_ge) ;      
  102.     assign end_miao_ge = add_miao_ge && miao_ge==10-1 ;
  103.     assign set_miao_ge = set_flag    && set_sel==0 && key_en[2];   



  104.     always @(posedge clk or negedge rst_n)begin
  105.         if(!rst_n)begin
  106.             miao_shi <= 0;
  107.         end
  108.         else if(add_miao_shi)begin
  109.             if(end_miao_shi)
  110.                 miao_shi <= 0;
  111.             else
  112.                 miao_shi <= miao_shi + 1;
  113.         end
  114.         else if(data_load_vld)begin
  115.             miao_shi <= data_load[7:4];
  116.         end
  117.     end

  118.     assign add_miao_shi = (end_miao_ge || set_miao_shi);      
  119.     assign end_miao_shi = add_miao_shi && miao_shi==6-1;
  120.     assign set_miao_shi = set_flag     && set_sel==1 && key_en[2];   


  121.     always @(posedge clk or negedge rst_n)begin
  122.         if(!rst_n)begin
  123.             fen_ge <= 0;
  124.         end
  125.         else if(add_fen_ge)begin
  126.             if(end_fen_ge)
  127.                 fen_ge <= 0;
  128.             else
  129.                 fen_ge <= fen_ge + 1;
  130.         end
  131.         else if(data_load_vld)begin
  132.             fen_ge <= data_load[11:8];
  133.         end
  134.     end

  135.     assign add_fen_ge = (end_miao_shi || set_fen_ge);      
  136.     assign end_fen_ge = add_fen_ge && fen_ge==10-1;
  137.     assign set_fen_ge = set_flag     && set_sel==2 && key_en[2];   


  138.     always @(posedge clk or negedge rst_n)begin
  139.         if(!rst_n)begin
  140.             fen_shi <= 0;
  141.         end
  142.         else if(add_fen_shi)begin
  143.             if(end_fen_shi)
  144.                 fen_shi <= 0;
  145.             else
  146.                 fen_shi <= fen_shi + 1;
  147.         end
  148.         else if(data_load_vld)begin
  149.             fen_shi <= data_load[15:12];
  150.         end
  151.     end

  152.     assign add_fen_shi = (end_fen_ge || set_fen_shi);      
  153.     assign end_fen_shi = add_fen_shi && fen_shi==6-1;
  154.     assign set_fen_shi = set_flag     && set_sel==3 && key_en[2];   

  155.    always @(posedge clk or negedge rst_n)begin
  156.         if(!rst_n)begin
  157.             shi_ge <= 0;
  158.         end
  159.         else if(add_shi_ge)begin
  160.             if(end_shi_ge)
  161.                 shi_ge <= 0;
  162.             else
  163.                 shi_ge <= shi_ge + 1;
  164.         end
  165.         else if(data_load_vld)begin
  166.             shi_ge <= data_load[19:16];
  167.         end
  168.     end

  169.     assign add_shi_ge = (end_fen_shi || set_shi_ge);      
  170.     assign end_shi_ge = add_shi_ge && shi_ge==x-1;
  171.     assign set_shi_ge = set_flag     && set_sel==4 && key_en[2];   


  172.     always @(posedge clk or negedge rst_n)begin
  173.         if(!rst_n)begin
  174.             shi_shi <= 0;
  175.         end
  176.         else if(add_shi_shi)begin
  177.             if(end_shi_shi)
  178.                 shi_shi <= 0;
  179.             else
  180.                 shi_shi <= shi_shi + 1;
  181.         end
  182.         else if(data_load_vld)begin
  183.             shi_shi <= data_load[23:20];
  184.         end
  185.     end

  186.     assign add_shi_shi = (end_shi_ge || set_shi_shi);      
  187.     assign end_shi_shi = add_shi_shi && shi_shi==y-1;
  188.     assign set_shi_shi = set_flag     && set_sel==5 && key_en[2];   

  189.     always  @(*)begin
  190.         if(shi_shi<2)
  191.             x =  10;
  192.         else
  193.             x =   4;
  194.     end

  195.     always  @(*)begin
  196.         if(set_flag && set_sel==5 && shi_ge>=4)
  197.             y = 2;
  198.         else
  199.             y = 3;
  200.     end
  201.     assign data_out = {shi_shi,shi_ge,fen_shi,fen_ge,miao_shi,miao_ge};


  202. endmodule
复制代码



1.4 数码管显示模块设计

1.4.1接口信号
  
信号
  
接口方向
定义
clk
输入
系统时钟,50MHz
rst_n
输入
低电平复位信号
din
输入
当前的时钟数据,每四位一组,共 6 组。由高位至低位,分
  
别是时十位、时个位、分十位、分个位、秒十位和秒个位的
  
值。
Seg_sel
输出
数码管位选信号
Segment
输出
数码管段选信号


1.4.2设计思路
数码管显示在前面的案例文章已经有讲述,这里不再进行介绍,想了解的可以看一下往期文章:

1.4.3参考代码
  1. always @(posedge clk or negedge rst_n)begin
  2.     if(!rst_n)begin
  3.         cnt_2ms <= 0;
  4.     end
  5.     else if(add_cnt_2ms)begin
  6.         if(end_cnt_2ms)
  7.             cnt_2ms <= 0;
  8.         else
  9.             cnt_2ms <= cnt_2ms + 1;
  10.     end
  11. end

  12. assign add_cnt_2ms = 1;      
  13. assign end_cnt_2ms = add_cnt_2ms && cnt_2ms==TIME_2MS-1 ;   

  14. always @(posedge clk or negedge rst_n)begin
  15.     if(!rst_n)begin
  16.         cnt_sel <= 0;
  17.     end
  18.     else if(add_cnt_sel)begin
  19.         if(end_cnt_sel)
  20.             cnt_sel <= 0;
  21.         else
  22.             cnt_sel <= cnt_sel + 1;
  23.     end
  24. end

  25. assign add_cnt_sel = end_cnt_2ms;      
  26. assign end_cnt_sel = add_cnt_sel && cnt_sel== SEG_NUM-1;   



  27. always  @(posedge clk or negedge rst_n)begin
  28.     if(rst_n==1'b0)begin
  29.         seg_sel <= {SEG_NUM{1'b1}};
  30.     end
  31.     else begin
  32.         seg_sel <= ~(1'b1 << cnt_sel);
  33.     end
  34. end

  35. always  @(posedge clk or negedge rst_n)begin
  36.     if(rst_n==1'b0)begin
  37.         din_ff0 <= 0;
  38.     end
  39.     else begin
  40.         for(ii=0;ii<SEG_NUM;ii=ii+1)begin
  41.             if(din_vld[ii]==1'b1)begin
  42.                 din_ff0[(ii+1)*4-1 -:4] <= din[(ii+1)*4-1 -:4];
  43.             end
  44.             else begin
  45.                 din_ff0[(ii+1)*4-1 -:4] <= din_ff0[(ii+1)*4-1 -:4];
  46.             end
  47.         end
  48.     end
  49. end

  50. always  @(*)begin
  51.     seg_tmp = din_ff0[(cnt_sel+1)*4-1 -:4];
  52. end


  53. always@(posedge clk or negedge rst_n)begin
  54.     if(rst_n==1'b0)begin
  55.         segment<=NUM_0;
  56.     end
  57.     else begin
  58.         case(seg_tmp)
  59.             4'd0:segment <= NUM_0;
  60.             4'd1:segment <= NUM_1;
  61.             4'd2:segment <= NUM_2;
  62.             4'd3:segment <= NUM_3;
  63.             4'd4:segment <= NUM_4;
  64.             4'd5:segment <= NUM_5;
  65.             4'd6:segment <= NUM_6;
  66.             4'd7:segment <= NUM_7;
  67.             4'd8:segment <= NUM_8;
  68.             4'd9:segment <= NUM_9;
  69.             default:begin
  70.                 segment <= NUM_ERR;
  71.             end
  72.         endcase
  73.     end
  74. end

  75. endmodul
复制代码


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,表示选择区间0~3,当写操作完成之后,该信号取反,变为1,表示选择区间4~7
读写地址信号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参考代码
  1.     always  @(posedge clk or negedge rst_n)begin
  2.         if(rst_n==1'b0)begin
  3.             state_c <= S_EWEN;
  4.         end
  5.         else begin
  6.             state_c <= state_n;
  7.         end
  8.     end

  9.     always  @(*)begin
  10.         case(state_c)
  11.             S_EWEN : begin
  12.                 if(ewen2read_start)begin
  13.                     state_n = S_READ ;
  14.                 end
  15.                 else begin
  16.                     state_n = state_c;
  17.                 end
  18.             end
  19.             S_READ : begin
  20.                 if(read2idle_start)begin
  21.                     state_n = S_IDLE;
  22.                 end
  23.                 else begin
  24.                     state_n = state_c;
  25.                 end
  26.             end
  27.             S_IDLE : begin
  28.                 if(idle2write_start)begin
  29.                     state_n = S_WRITE;
  30.                 end
  31.                 else begin
  32.                     state_n = state_c;
  33.                 end
  34.             end
  35.             S_WRITE : begin
  36.                 if(write2idle_start)begin
  37.                     state_n = S_IDLE;
  38.                 end
  39.                 else begin
  40.                     state_n = state_c;
  41.                 end
  42.             end
  43.             default : begin
  44.                 state_n = S_EWEN;
  45.             end
  46.         endcase
  47.     end


  48.     assign   ewen2read_start  = state_c==S_EWEN  && add_time_cnt && time_cnt==1000-1 && rdy;
  49.     assign   read2idle_start  = state_c==S_READ  && end_rd_cnt      ;
  50.     assign   idle2write_start = state_c==S_IDLE  && end_time_cnt    ;
  51.     assign   write2idle_start = state_c==S_WRITE && end_wr_cnt      ;


  52.     always @(posedge clk or negedge rst_n)begin
  53.         if(!rst_n)begin
  54.             time_cnt <= 0;
  55.         end
  56.         else if(add_time_cnt)begin
  57.             if(end_time_cnt)
  58.                 time_cnt <= 0;
  59.             else
  60.                 time_cnt <= time_cnt + 1;
  61.         end
  62.     end

  63.     assign add_time_cnt = 1;      
  64.     assign end_time_cnt = add_time_cnt && time_cnt==TIME_1S-1 ;   


  65.     always @(posedge clk or negedge rst_n)begin
  66.         if(!rst_n)begin
  67.             wr_cnt <= 0;
  68.         end
  69.         else if(add_wr_cnt)begin
  70.             if(end_wr_cnt)
  71.                 wr_cnt <= 0;
  72.             else
  73.                 wr_cnt <= wr_cnt + 1;
  74.         end
  75.     end

  76.     assign add_wr_cnt = state_c==S_WRITE && rdy;
  77.     assign end_wr_cnt = add_wr_cnt && wr_cnt == WR_NUM-1 ;


  78.     always @(posedge clk or negedge rst_n)begin
  79.         if(!rst_n)begin
  80.             rd_cnt <= 0;
  81.         end
  82.         else if(add_rd_cnt)begin
  83.             if(end_rd_cnt)
  84.                 rd_cnt <= 0;
  85.             else
  86.                 rd_cnt <= rd_cnt + 1;
  87.         end
  88.     end

  89.     assign add_rd_cnt = state_c==S_READ && crc_dout_vld;
  90.     assign end_rd_cnt = add_rd_cnt && rd_cnt == RD_NUM-1 ;


  91.     assign start_read = state_c==S_READ && flag_wait_crc==0 && rdy;

  92.     always  @(posedge clk or negedge rst_n)begin
  93.         if(rst_n==1'b0)begin
  94.             flag_wait_crc <= 0;
  95.         end
  96.         else if(start_read)begin
  97.             flag_wait_crc <= 1;
  98.         end
  99.         else if(crc_dout_vld)begin
  100.             flag_wait_crc <= 0;
  101.         end
  102.     end

  103.     always  @(posedge clk or negedge rst_n)begin
  104.         if(rst_n==1'b0)begin
  105.             mode <= EWEN;
  106.         end
  107.         else if(state_c==S_EWEN) begin
  108.             mode <= EWEN;
  109.         end
  110.         else if(state_c==S_WRITE)begin
  111.             mode <= WRITE;
  112.         end
  113.         else if(state_c==S_READ)begin
  114.             mode <= READ;
  115.         end
  116.     end



  117.     always  @(posedge clk or negedge rst_n)begin
  118.         if(rst_n==1'b0)begin
  119.             crc_din <= 0;
  120.         end
  121.         else if(add_wr_cnt && end_wr_cnt==0) begin
  122.             crc_din <= din[8*(D_LEN-wr_cnt)-1 -:8];
  123.         end
  124.         else begin
  125.             crc_din <= rdata;
  126.         end
  127.     end

  128.     always  @(posedge clk or negedge rst_n)begin
  129.         if(rst_n==1'b0)begin
  130.             crc_din_vld <= 1'b0;
  131.         end
  132.         else if(add_wr_cnt && end_wr_cnt==0) begin
  133.             crc_din_vld <= 1'b1;
  134.         end
  135.         else begin
  136.             crc_din_vld <= rdata_vld;
  137.         end
  138.     end

  139.     always  @(posedge clk or negedge rst_n)begin
  140.         if(rst_n==1'b0)begin
  141.             crc_clr <= 1'b0;
  142.         end
  143.         else if((add_rd_cnt && rd_cnt==4-1)|| read2idle_start || write2idle_start) begin
  144.             crc_clr <= 1'b1;
  145.         end
  146.         else begin
  147.             crc_clr <= 1'b0;
  148.         end
  149.     end

  150.     always  @(posedge clk or negedge rst_n)begin
  151.         if(rst_n==1'b0)begin
  152.             addr <= 0;
  153.         end
  154.         else if(state_c==S_WRITE) begin
  155.             addr <= {4'b0,write_sel,wr_cnt[1:0]};
  156.         end
  157.         else if(state_c==S_EWEN) begin
  158.             addr <= 7'b1100000;
  159.         end
  160.         else begin
  161.             addr <= {4'b0,rd_cnt};
  162.         end
  163.     end

  164.     always  @(posedge clk or negedge rst_n)begin
  165.         if(rst_n==1'b0)begin
  166.             write_sel <= 1'b0;
  167.         end
  168.         else if(write2idle_start) begin
  169.             write_sel <= ~write_sel;
  170.         end
  171.     end

  172.   


  173.     always  @(posedge clk or negedge rst_n)begin
  174.         if(rst_n==1'b0)begin
  175.             wdata <= 0;
  176.         end
  177.         else if(wr_cnt==WR_NUM-1) begin
  178.             wdata <= crc_dout;
  179.         end
  180.         else begin
  181.             wdata <= din[8*(D_LEN-wr_cnt)-1 -:8];
  182.         end
  183.     end

  184.     always  @(posedge clk or negedge rst_n)begin
  185.         if(rst_n==1'b0)begin
  186.             start <= 1'b0;
  187.         end
  188.         else if(add_wr_cnt || start_read || ewen2read_start)begin
  189.             start <= 1'b1;
  190.         end
  191.         else begin
  192.             start <= 1'b0;
  193.         end
  194.     end

  195.     always  @(posedge clk or negedge rst_n)begin
  196.         if(rst_n==1'b0)begin
  197.             dout_temp<= 0;
  198.         end
  199.         else if(rdata_vld) begin
  200.             dout_temp[(8-rd_cnt)*8-1 -:8] <= rdata;
  201.         end
  202.     end

  203.     always  @(posedge clk or negedge rst_n)begin
  204.         if(rst_n==1'b0)begin
  205.             dout0_err <= 1'b0;
  206.         end
  207.         else if(add_rd_cnt && rd_cnt==4-1) begin
  208.             if(crc_dout!=0)
  209.                 dout0_err <= 1'b1;
  210.             else
  211.                 dout0_err <= 1'b0;
  212.         end
  213.     end

  214.     always  @(*)begin
  215.         if(crc_dout!=0)
  216.             dout1_err = 1'b1;
  217.         else
  218.             dout1_err = 1'b0;
  219.     end


  220.     assign dout0_temp = dout_temp[8*8-1 -:24];
  221.     assign dout1_temp = dout_temp[4*8-1 -:24];



  222.    
  223.     always  @(posedge clk or negedge rst_n)begin
  224.         if(rst_n==1'b0)begin
  225.             dout <= 0;
  226.         end
  227.         else if(end_rd_cnt) begin
  228.             if(dout0_err==1'b0 && dout1_err==1'b0)begin
  229.                 if(dout0_temp > dout1_temp)
  230.                     dout <= dout0_temp;
  231.                 else
  232.                     dout <= dout1_temp;
  233.             end
  234.             else if(dout0_err==1'b0)begin
  235.                 dout <= dout0_temp;
  236.             end
  237.             else begin
  238.                 dout <= dout1_temp;
  239.             end
  240.         end
  241.     end


  242.     always  @(posedge clk or negedge rst_n)begin
  243.         if(rst_n==1'b0)begin
  244.             dout_vld <= 1'b0;
  245.         end
  246.         else if(end_rd_cnt) begin
  247.             if(dout0_err && dout1_err)begin
  248.                 dout_vld <= 1'b0;
  249.             end
  250.             else begin
  251.                 dout_vld <= 1'b1;
  252.             end
  253.         end
  254.         else begin
  255.             dout_vld <= 1'b0;
  256.         end
  257.     end


  258.    crc8_d8 u_crc8_d8(
  259.               .clk      (clk          ),
  260.               .rst_n    (rst_n        ),
  261.               .clr      (crc_clr      ),
  262.               .din_vld  (crc_din_vld  ),
  263.               .din      (crc_din      ),
  264.               .dout_vld (crc_dout_vld ),
  265.               .dout     (crc_dout     )      
  266.     );
复制代码


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参考代码
  1.     assign d   =    din    ;
  2.     assign c   =    dout   ;


  3.     always  @(posedge clk or negedge rst_n)begin
  4.         if(rst_n==1'b0)begin
  5.             dout <= 0;
  6.         end
  7.         else if(clr)begin
  8.             dout <= 0;
  9.         end
  10.         else if(din_vld) begin
  11.             dout[0] <= d[7] ^ d[6] ^ d[0] ^ c[0] ^ c[6] ^ c[7];
  12.             dout[1] <= d[6] ^ d[1] ^ d[0] ^ c[0] ^ c[1] ^ c[6];
  13.             dout[2] <= d[6] ^ d[2] ^ d[1] ^ d[0] ^ c[0] ^ c[1] ^ c[2] ^ c[6];
  14.             dout[3] <= d[7] ^ d[3] ^ d[2] ^ d[1] ^ c[1] ^ c[2] ^ c[3] ^ c[7];
  15.             dout[4] <= d[4] ^ d[3] ^ d[2] ^ c[2] ^ c[3] ^ c[4];
  16.             dout[5] <= d[5] ^ d[4] ^ d[3] ^ c[3] ^ c[4] ^ c[5];
  17.             dout[6] <= d[6] ^ d[5] ^ d[4] ^ c[4] ^ c[5] ^ c[6];
  18.             dout[7] <= d[7] ^ d[6] ^ d[5] ^ c[5] ^ c[6] ^ c[7];
  19.         end
  20.     end

  21.     always  @(posedge clk or negedge rst_n)begin
  22.         if(rst_n==1'b0)begin
  23.             dout_vld <= 0;
  24.         end
  25.         else begin
  26.             dout_vld <= din_vld;
  27.         end
  28.     end

  29. endmodul
复制代码


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时间,等待TWP5ms)时间,然后结束。


根据上述的时序介绍,本模块采用3个计数器的架构,下面是计数器的架构图。


架构中的三个计数器分别为时钟计数器cnt0、比特计数器cnt1和阶段计数器cnt2flag_work为工作状态指示信号。

时钟计数器cnt0:该计数器用来计数时钟的个数。加一条件为flag_work,表示进入工作状态就开始计数。结束条件为数x个,根据不同的工作模式和所处的阶段不同而变化。包括SK的时钟周期数、等待时间TCS、等待5ms时间。

比特计数器cnt1:该计数器用来数有多少bit数据,加一条件为end_cnt0,表示每数完1bit,就加1;结束条件为数y个,y分别为10EWEN)、18READWRITE)、1(等待TCS5ms)。

阶段计数器cnt2:该计数器用来对每个指令需要的阶段进行计数。加一条件为end_cnt1,表示发送完一组数据就加一;结束条件为数u个,u分别为2EWENREAD)、3WRITE)。

除了上述的计数器之外,还有一些比较重要的信号,我们来分析一下如何进行设计。

工作状态指示信号flag_work:初始状态为0,表示处于空闲状态;当收到开始命令start的时候,变化变为1,由空闲状态转为工作状态;当前指令的时序结束之后,也就是阶段计数器cnt2数完,就变为0,进入空闲状态。

待发送数据dout:当接收到开始命令的时候,根据AT93C46的指令集,将SBOpcodeAddressdata按照顺序拼接到一起。

模式暂存器mode_reg:为保证在发送时序期间保持不变,在接收到开始命令的时候,将操作模式指示信号进行暂存。

AT93C46时钟sk:时钟频率为200KHz,工程的系统时钟为50MHz,因此sk一个完整的周期需要250个系统时钟周期,初始状态设为低电平,当时钟计数器数到125个的时候置为高电平,时钟计数器数完,在置为低电平。

AT93C46数据输入di:在每个指令时序的第一阶段,也就是cnt2=1-1的时候,根据比特计数器要数的个数,将待发送的数据dout送给di

AT93C46片选信号cs:在写比特数据、等待5ms期间为高,其他时候都为低,所以该信号拉高的条件为(start_vld==1 || (add_cnt2 && cnt2==2-1 &&end_cnt2==0))。其他时候片选信号都为低,所以拉低的条件为((add_cnt2&& cnt2==1-1) || end_cnt2)

读取数据rdata:在读模式下,处于第一阶段,并且在第11~18bit的时候,将AT93C46输出数据do保存到rdata里面。

准备好信号rdy:由组合逻辑产生,当接收到开始命令,或者处于工作状态的时候,为低电平,表示没有准备好;其他时刻为高电平,表示准备好。

1.7.3参考代码
  1.     assign start_vld = flag_work==0 && start;

  2.     always  @(*)begin
  3.         if(mode==EWEN)
  4.             opcode = 3'b100;
  5.         else if(mode==WRITE)
  6.             opcode = 3'b101;
  7.         else
  8.             opcode = 3'b110;
  9.     end

  10.     always  @(posedge clk or negedge rst_n)begin
  11.         if(rst_n==1'b0)begin
  12.             dout <= 0;
  13.         end
  14.         else if(start_vld) begin
  15.             dout <={opcode,addr,wdata};
  16.         end
  17.     end


  18.     always  @(posedge clk or negedge rst_n)begin
  19.         if(rst_n==1'b0)begin
  20.             mode_reg <= 0;
  21.         end
  22.         else if(start_vld) begin
  23.             mode_reg <= mode;
  24.         end
  25.     end

  26.     always  @(posedge clk or negedge rst_n)begin
  27.         if(rst_n==1'b0)begin
  28.             flag_work <= 0;
  29.         end
  30.         else if(start_vld) begin
  31.             flag_work <= 1;
  32.         end
  33.         else if(end_cnt2)begin
  34.             flag_work <= 0;
  35.         end
  36.     end


  37.    
  38.     always @(posedge clk or negedge rst_n)begin
  39.         if(!rst_n)begin
  40.             cnt0 <= 0;
  41.         end
  42.         else if(add_cnt0)begin
  43.             if(end_cnt0)
  44.                 cnt0 <= 0;
  45.             else
  46.                 cnt0 <= cnt0 + 1;
  47.         end
  48.     end

  49.     assign add_cnt0 = flag_work;      
  50.     assign end_cnt0 = add_cnt0 && cnt0== x-1;   

  51.     always @(posedge clk or negedge rst_n)begin
  52.         if(!rst_n)begin
  53.             cnt1 <= 0;
  54.         end
  55.         else if(add_cnt1)begin
  56.             if(end_cnt1)
  57.                 cnt1 <= 0;
  58.             else
  59.                 cnt1 <= cnt1 + 1;
  60.         end
  61.     end

  62.     assign add_cnt1 = end_cnt0;      
  63.     assign end_cnt1 = add_cnt1 && cnt1==y-1 ;


  64.     always @(posedge clk or negedge rst_n)begin
  65.         if(!rst_n)begin
  66.              cnt2 <= 0;
  67.          end
  68.          else if(add_cnt2)begin
  69.              if(end_cnt2)
  70.                   cnt2 <= 0;
  71.              else
  72.                   cnt2 <= cnt2 + 1;
  73.           end
  74.     end
  75.    
  76.     assign add_cnt2 = end_cnt1;      
  77.     assign end_cnt2 = add_cnt2 && cnt2==u-1;   


  78.     assign en_sk1 = add_cnt0 && cnt0==x/2-1 && cnt2==1-1;
  79.     assign en_sk0 = end_cnt0                            ;
  80.     always  @(posedge clk or negedge rst_n)begin
  81.         if(rst_n==1'b0)begin
  82.             sk <= 0;
  83.         end
  84.         else if(add_cnt0 && cnt0==x/2-1 && cnt2==1-1)begin
  85.             sk <= 1;
  86.         end
  87.         else if(end_cnt0)begin
  88.             sk <= 0;
  89.         end
  90.     end

  91.     assign en_di = add_cnt0 && cnt0==1-1 && cnt2==1-1;

  92.     always  @(posedge clk or negedge rst_n)begin
  93.         if(rst_n==1'b0)begin
  94.             di <= 0;
  95.         end
  96.         else if(en_di) begin
  97.             di <= dout[17-cnt1];
  98.         end
  99.     end


  100.     always  @(posedge clk or negedge rst_n)begin
  101.         if(rst_n==1'b0)begin
  102.             cs <= 0;
  103.         end
  104.         else if(start_vld==1 || (add_cnt2 && cnt2==2-1 && end_cnt2==0)) begin
  105.             cs <= 1;
  106.         end
  107.         else if((add_cnt2 && cnt2==1-1) || end_cnt2)begin
  108.             cs <= 0;
  109.         end
  110.     end


  111.     always  @(posedge clk or negedge rst_n)begin
  112.         if(rst_n==1'b0)begin
  113.             rdata <= 0;
  114.         end
  115.         else if(end_cnt0 && cnt1 >=10 && cnt2==1-1 && mode_reg==READ ) begin
  116.             rdata[17-cnt1] <= do;
  117.         end
  118.     end

  119.     always  @(posedge clk or negedge rst_n)begin
  120.         if(rst_n==1'b0)begin
  121.             rdata_vld <= 0;
  122.         end
  123.         else begin
  124.             rdata_vld <= end_cnt2 && mode_reg==READ;
  125.         end
  126.     end

  127.     always  @(*)begin
  128.         if(start || flag_work)
  129.             rdy = 1'b0;
  130.         else
  131.             rdy = 1'b1;
  132.     end


  133.     always  @(*)begin
  134.         if(mode_reg==WRITE && cnt2==1-1)begin
  135.             x = 250;
  136.             y =  18;
  137.             u =   3;  
  138.         end
  139.         else if(mode_reg==WRITE && (cnt2==2-1 ))begin
  140.             x = 250 ;
  141.             y =   1 ;
  142.             u =   3 ;  
  143.         end
  144.         else if(mode_reg==WRITE && cnt2==3-1)begin
  145.             x = 500000;  
  146.             y =   1 ;
  147.             u =   3 ;  
  148.         end
  149.         else if(mode_reg==READ && cnt2==1-1)begin
  150.             x = 250;
  151.             y =  18;
  152.             u =   2;  
  153.         end
  154.         else if(mode_reg==READ && cnt2==2-1)begin
  155.             x = 250 ;
  156.             y =   1 ;
  157.             u =   2 ;  
  158.         end
  159.         else if(mode_reg==EWEN && cnt2==1-1)begin
  160.             x = 250;
  161.             y =  10;
  162.             u =   2;  
  163.         end
  164.         else begin
  165.             x = 250 ;  
  166.             y =   1 ;
  167.             u =   2 ;  
  168.         end
  169.     end


  170. endmodul
复制代码


1.8 效果和总结
本工程上板之后,可通过复位来验证现象,若要通过断电来进行验证,需要将工程烧录进开发板才行。

1.8.1db603开发板
由于本工程现象是一个动态的过程,所以从下面图片中看不出具体实现的效果,想要看上板效果的话可以看一下工程上板的视频。


1.8.2mp801开发板
由于本工程现象是一个动态的过程,所以从下面图片中看不出具体实现的效果,想要看上板效果的话可以看一下工程上板的视频。




1.8.3ms980试验箱
由于本工程现象是一个动态的过程,所以从下面图片中看不出具体实现的效果,想要看上板效果的话可以看一下工程上板的视频。


详细的设计视频教程和工程源码请到明德扬论坛学习!

MDY最新推出《FPGA至简设计案例200例》项目,每周固定更新至少1个案例,可到明德扬论坛www.fpgabbs.cn下载学习。

  •   
  •   
  •   
  •  
  • FPGA教育领域第一品牌
  • 咨询热线:020-39002701
  • 技术交流Q群:544453837