【每周FPGA案例】 断电重加载时钟工程 编号:00000066
断电重加载时钟工程本案例的编号为:00000066,如果有疑问,请按编号在下面贴子查找答案:MDY案例交流【汇总贴】_FPGA-明德扬科教 (mdy-edu.com)本文为明德扬原创及录用文章,转载请注明出处!--作者:小黑同学【上板现象】
断电重加载时钟工程在MP801的上板现象
https://www.bilibili.com/video/BV1Af4y117H4?p=36
断电重加载时钟工程在点拨开发板的上板现象
https://www.bilibili.com/video/BV1Af4y117H4?p=35
断电重加载时钟工程在实验箱的上板现象
https://www.bilibili.com/video/BV1Af4y117H4?p=37
本文为明德扬原创及录用文章,转载请注明出处!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占用的地址为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模块实现功能
根据上游模块的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参考代码
`define KEY_SCAN
module at93c46_top_scan(
clk ,
rst_n ,
key_col,
mo ,
cs ,
mi ,
sk ,
key_row,
seg_sel,
seg_data
);
parameter TIME_1S = 50_000_000;
input clk ;
input rst_n ;
input key_col ;
input mo ;
output cs ;
output mi ;
output sk ;
output key_row ;
output seg_sel ;
output seg_data ;
wire rdy ;
wire rdata_vld ;
wire key_en ;
wire data_load ;
wire data_load_vld;
wire clk_data_out ;
wire addr ;
wire mode ;
wire start ;
wire wdata ;
wire rdata ;
`ifdef KEY_SCAN
key_scanu_key_scan(
.clk (clk ),
.rst_n (rst_n),
.key_col (key_col),
.key_row (key_row),
.key_en(key_en )
);
`else
key_moduleu_key_module(
.clk (clk ),
.rst_n (rst_n ),
.key_in(~key_col),
.key_vld (key_en)
);
`endif
clock_data#(.TIME_1S(TIME_1S)) u_clock_data(
.clk (clk ),
.rst_n (rst_n ),
.data_load (data_load ),
.data_load_vld (data_load_vld ),
.key_en (key_en ),
.data_out (clk_data_out)
);
seg_disp#(.SEG_NUM(6)) u_seg_disp(
.rst_n (rst_n ),
.clk (clk ),
.din (clk_data_out),
.din_vld ({6{1'b1}} ),
.seg_sel (seg_sel ),
.segment (seg_data )
);
data_processor#(.TIME_1S(TIME_1S)) u_data_pro(
.clk (clk ),
.rst_n (rst_n ),
.din (clk_data_out),
.start (start ),
.mode (mode ),
.addr (addr ),
.wdata (wdata ),
.rdata (rdata ),
.rdata_vld(rdata_vld ),
.rdy (rdy ),
.dout (data_load ),
.dout_vld (data_load_vld )
);
at93c46_mix u_at93c46_mix(
.clk (clk ),
.rst_n (rst_n ),
.start (start ),
.mode (mode ),
.addr (addr ),
.wdata (wdata ),
.rdata (rdata ),
.rdata_vld(rdata_vld),
.rdy (rdy ),
.do (mo ),
.di (mi ),
.cs (cs ),
.sk (sk )
);
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参考代码modulekey_scan(
clk ,
rst_n,
key_col,
key_row,
key_en
);
parameter KEY_W = 4 ;
parameter CHK_COL= 0 ;
parameter CHK_ROW= 1 ;
parameter DELAY = 2 ;
parameter WAIT_END = 3 ;
parameter COL_CNT= 16 ;
parameter TIME_20MS= 1000000;
input clk ;
input rst_n ;
input key_col ;
output key_en ;
output key_row ;
reg key_out ;
reg key_row ;
reg key_vld ;
reg key_col_ff0 ;
reg key_col_ff1 ;
reg key_col_get ;
reg key_en ;
wire end_shake_cnt ;
reg end_shake_cnt_ff0;
reg state_c ;
reg shake_cnt ;
reg state_n ;
reg row_index ;
reg row_cnt ;
wire col2row_start ;
wire row2del_start ;
wire del2wait_start ;
wire wait2col_start ;
wire add_row_cnt ;
wire end_row_cnt ;
wire add_shake_cnt ;
wire add_row_index ;
wire end_row_index ;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_col_ff0 <= 4'b1111;
key_col_ff1 <= 4'b1111;
end
else begin
key_col_ff0 <= key_col ;
key_col_ff1 <= key_col_ff0;
end
end
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
shake_cnt <= 0;
end
else if(add_shake_cnt) begin
if(end_shake_cnt)
shake_cnt <= 0;
else
shake_cnt <= shake_cnt+1 ;
end
end
assign add_shake_cnt = key_col_ff1!=4'hf;
assign end_shake_cnt = add_shake_cnt&& shake_cnt == TIME_20MS-1 ;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
state_c <= CHK_COL;
end
else begin
state_c <= state_n;
end
end
always@(*)begin
case(state_c)
CHK_COL: begin
if(col2row_start )begin
state_n = CHK_ROW;
end
else begin
state_n = CHK_COL;
end
end
CHK_ROW: begin
if(row2del_start)begin
state_n = DELAY;
end
else begin
state_n = CHK_ROW;
end
end
DELAY :begin
if(del2wait_start)begin
state_n = WAIT_END;
end
else begin
state_n = DELAY;
end
end
WAIT_END: begin
if(wait2col_start)begin
state_n = CHK_COL;
end
else begin
state_n = WAIT_END;
end
end
default: state_n = CHK_COL;
endcase
end
assign col2row_start = state_c==CHK_COL&& end_shake_cnt;
assign row2del_start = state_c==CHK_ROW&& row_index==3 && end_row_cnt;
assign del2wait_start= state_c==DELAY && end_row_cnt;
assign wait2col_start= state_c==WAIT_END && key_col_ff1==4'hf;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_row <= 4'b0;
end
else if(state_c==CHK_ROW)begin
key_row <= ~(1'b1 << row_index);
end
else begin
key_row <= 4'b0;
end
end
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
row_index <= 0;
end
else if(add_row_index) begin
if(end_row_index)
row_index <= 0;
else
row_index <= row_index+1 ;
end
else if(state_c!=CHK_ROW)begin
row_index <= 0;
end
end
assign add_row_index = state_c==CHK_ROW && end_row_cnt;
assign end_row_index = add_row_index&& row_index == 4-1 ;
always @(posedge clk or negedge rst_n) begin
if (rst_n==0) begin
row_cnt <= 0;
end
else if(add_row_cnt) begin
if(end_row_cnt)
row_cnt <= 0;
else
row_cnt <= row_cnt+1 ;
end
end
assign add_row_cnt = state_c==CHK_ROW || state_c==DELAY;
assign end_row_cnt = add_row_cnt&& row_cnt == 16-1 ;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_col_get <= 0;
end
else if(state_c==CHK_COL && end_shake_cnt ) begin
if(key_col_ff1==4'b1110)
key_col_get <= 0;
else if(key_col_ff1==4'b1101)
key_col_get <= 1;
else if(key_col_ff1==4'b1011)
key_col_get <= 2;
else
key_col_get <= 3;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_out <= 0;
end
else if(state_c==CHK_ROW && end_row_cnt)begin
key_out <= {row_index,key_col_get};
end
else begin
key_out <= 0;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_vld <= 1'b0;
end
else if(state_c==CHK_ROW && end_row_cnt && key_col_ff1==1'b0)begin
key_vld <= 1'b1;
end
else begin
key_vld <= 1'b0;
end
end
always@(*)begin
if(rst_n==1'b0)begin
key_en = 0;
end
else if(key_vld && key_out==0)begin
key_en = 4'b0001;
end
else if(key_vld && key_out==1)begin
key_en = 4'b0010;
end
else if(key_vld && key_out==2)begin
key_en = 4'b0100;
end
else begin
key_en = 0;
end
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参考代码
module clock_data(
clk ,
rst_n ,
data_load ,
data_load_vld,
key_en ,
data_out
);
parameter TIME_1S = 50_000_000 ;
input clk ;
input rst_n ;
input data_load_vld;
input data_load ;
input [ 3:0] key_en ;
output data_out ;
wire data_out ;
reg cnt_1s ;
reg miao_ge ;
reg miao_shi ;
reg fen_ge ;
reg fen_shi ;
reg shi_ge ;
reg shi_shi ;
reg set_sel ;
reg set_flag ;
wire add_set_sel;
wire add_cnt_1s ;
wire add_miao_ge;
wire add_miao_shi ;
wire add_fen_ge ;
wire add_fen_shi;
wire add_shi_ge ;
wire add_shi_shi;
wire end_cnt_1s ;
wire end_set_sel;
wire end_miao_ge;
wire end_miao_shi ;
wire end_fen_ge ;
wire end_fen_shi;
wire end_shi_ge ;
wire end_shi_shi;
wire set_miao_ge;
wire set_miao_shi ;
wire set_fen_ge ;
wire set_fen_shi;
wire set_shi_ge ;
wire set_shi_shi;
reg[ 3:0] x ;
reg[ 2:0] y ;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
set_flag <= 1'b0;
end
else if(key_en) begin
set_flag <= ~ set_flag;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
set_sel <= 0;
end
else if(add_set_sel)begin
if(end_set_sel)
set_sel <= 0;
else
set_sel <= set_sel + 1;
end
else if(set_flag==0)begin
set_sel <= 0;
end
end
assign add_set_sel = set_flag && key_en;
assign end_set_sel = add_set_sel && set_sel==6-1 ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_1s <= 0;
end
else if(add_cnt_1s)begin
if(end_cnt_1s)
cnt_1s <= 0;
else
cnt_1s <= cnt_1s + 1;
end
end
assign add_cnt_1s = set_flag==0 ;
assign end_cnt_1s = add_cnt_1s && cnt_1s==TIME_1S-1 ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
miao_ge <= 0;
end
else if(add_miao_ge)begin
if(end_miao_ge)
miao_ge <= 0;
else
miao_ge <= miao_ge + 1;
end
else if(data_load_vld)begin
miao_ge <=data_load;
end
end
assign add_miao_ge = (end_cnt_1s || set_miao_ge) ;
assign end_miao_ge = add_miao_ge && miao_ge==10-1 ;
assign set_miao_ge = set_flag && set_sel==0 && key_en;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
miao_shi <= 0;
end
else if(add_miao_shi)begin
if(end_miao_shi)
miao_shi <= 0;
else
miao_shi <= miao_shi + 1;
end
else if(data_load_vld)begin
miao_shi <= data_load;
end
end
assign add_miao_shi = (end_miao_ge || set_miao_shi);
assign end_miao_shi = add_miao_shi && miao_shi==6-1;
assign set_miao_shi = set_flag && set_sel==1 && key_en;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
fen_ge <= 0;
end
else if(add_fen_ge)begin
if(end_fen_ge)
fen_ge <= 0;
else
fen_ge <= fen_ge + 1;
end
else if(data_load_vld)begin
fen_ge <= data_load;
end
end
assign add_fen_ge = (end_miao_shi || set_fen_ge);
assign end_fen_ge = add_fen_ge && fen_ge==10-1;
assign set_fen_ge = set_flag && set_sel==2 && key_en;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
fen_shi <= 0;
end
else if(add_fen_shi)begin
if(end_fen_shi)
fen_shi <= 0;
else
fen_shi <= fen_shi + 1;
end
else if(data_load_vld)begin
fen_shi <= data_load;
end
end
assign add_fen_shi = (end_fen_ge || set_fen_shi);
assign end_fen_shi = add_fen_shi && fen_shi==6-1;
assign set_fen_shi = set_flag && set_sel==3 && key_en;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
shi_ge <= 0;
end
else if(add_shi_ge)begin
if(end_shi_ge)
shi_ge <= 0;
else
shi_ge <= shi_ge + 1;
end
else if(data_load_vld)begin
shi_ge <= data_load;
end
end
assign add_shi_ge = (end_fen_shi || set_shi_ge);
assign end_shi_ge = add_shi_ge && shi_ge==x-1;
assign set_shi_ge = set_flag && set_sel==4 && key_en;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
shi_shi <= 0;
end
else if(add_shi_shi)begin
if(end_shi_shi)
shi_shi <= 0;
else
shi_shi <= shi_shi + 1;
end
else if(data_load_vld)begin
shi_shi <= data_load;
end
end
assign add_shi_shi = (end_shi_ge || set_shi_shi);
assign end_shi_shi = add_shi_shi && shi_shi==y-1;
assign set_shi_shi = set_flag && set_sel==5 && key_en;
always@(*)begin
if(shi_shi<2)
x =10;
else
x = 4;
end
always@(*)begin
if(set_flag && set_sel==5 && shi_ge>=4)
y = 2;
else
y = 3;
end
assign data_out = {shi_shi,shi_ge,fen_shi,fen_ge,miao_shi,miao_ge};
endmodule
1.4 数码管显示模块设计
1.4.1接口信号
信号接口方向定义
clk输入系统时钟,50MHz
rst_n输入低电平复位信号
din输入当前的时钟数据,每四位一组,共 6 组。由高位至低位,分别是时十位、时个位、分十位、分个位、秒十位和秒个位的值。
Seg_sel输出数码管位选信号
Segment输出数码管段选信号
1.4.2设计思路数码管显示在前面的案例文章已经有讲述,这里不再进行介绍,想了解的可以看一下往期文章:
1.4.3参考代码
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_2ms <= 0;
end
else if(add_cnt_2ms)begin
if(end_cnt_2ms)
cnt_2ms <= 0;
else
cnt_2ms <= cnt_2ms + 1;
end
end
assign add_cnt_2ms = 1;
assign end_cnt_2ms = add_cnt_2ms && cnt_2ms==TIME_2MS-1 ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_sel <= 0;
end
else if(add_cnt_sel)begin
if(end_cnt_sel)
cnt_sel <= 0;
else
cnt_sel <= cnt_sel + 1;
end
end
assign add_cnt_sel = end_cnt_2ms;
assign end_cnt_sel = add_cnt_sel && cnt_sel== SEG_NUM-1;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
seg_sel <= {SEG_NUM{1'b1}};
end
else begin
seg_sel <= ~(1'b1 << cnt_sel);
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
din_ff0 <= 0;
end
else begin
for(ii=0;ii<SEG_NUM;ii=ii+1)begin
if(din_vld==1'b1)begin
din_ff0[(ii+1)*4-1 -:4] <= din[(ii+1)*4-1 -:4];
end
else begin
din_ff0[(ii+1)*4-1 -:4] <= din_ff0[(ii+1)*4-1 -:4];
end
end
end
end
always@(*)begin
seg_tmp = din_ff0[(cnt_sel+1)*4-1 -:4];
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
segment<=NUM_0;
end
else begin
case(seg_tmp)
4'd0:segment <= NUM_0;
4'd1:segment <= NUM_1;
4'd2:segment <= NUM_2;
4'd3:segment <= NUM_3;
4'd4:segment <= NUM_4;
4'd5:segment <= NUM_5;
4'd6:segment <= NUM_6;
4'd7:segment <= NUM_7;
4'd8:segment <= NUM_8;
4'd9:segment <= NUM_9;
default:begin
segment <= NUM_ERR;
end
endcase
end
end
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参考代码 always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
state_c <= S_EWEN;
end
else begin
state_c <= state_n;
end
end
always@(*)begin
case(state_c)
S_EWEN : begin
if(ewen2read_start)begin
state_n = S_READ ;
end
else begin
state_n = state_c;
end
end
S_READ : begin
if(read2idle_start)begin
state_n = S_IDLE;
end
else begin
state_n = state_c;
end
end
S_IDLE : begin
if(idle2write_start)begin
state_n = S_WRITE;
end
else begin
state_n = state_c;
end
end
S_WRITE : begin
if(write2idle_start)begin
state_n = S_IDLE;
end
else begin
state_n = state_c;
end
end
default : begin
state_n = S_EWEN;
end
endcase
end
assign ewen2read_start= state_c==S_EWEN&& add_time_cnt && time_cnt==1000-1 && rdy;
assign read2idle_start= state_c==S_READ&& end_rd_cnt ;
assign idle2write_start = state_c==S_IDLE&& end_time_cnt ;
assign write2idle_start = state_c==S_WRITE && end_wr_cnt ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
time_cnt <= 0;
end
else if(add_time_cnt)begin
if(end_time_cnt)
time_cnt <= 0;
else
time_cnt <= time_cnt + 1;
end
end
assign add_time_cnt = 1;
assign end_time_cnt = add_time_cnt && time_cnt==TIME_1S-1 ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
wr_cnt <= 0;
end
else if(add_wr_cnt)begin
if(end_wr_cnt)
wr_cnt <= 0;
else
wr_cnt <= wr_cnt + 1;
end
end
assign add_wr_cnt = state_c==S_WRITE && rdy;
assign end_wr_cnt = add_wr_cnt && wr_cnt == WR_NUM-1 ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rd_cnt <= 0;
end
else if(add_rd_cnt)begin
if(end_rd_cnt)
rd_cnt <= 0;
else
rd_cnt <= rd_cnt + 1;
end
end
assign add_rd_cnt = state_c==S_READ && crc_dout_vld;
assign end_rd_cnt = add_rd_cnt && rd_cnt == RD_NUM-1 ;
assign start_read = state_c==S_READ && flag_wait_crc==0 && rdy;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_wait_crc <= 0;
end
else if(start_read)begin
flag_wait_crc <= 1;
end
else if(crc_dout_vld)begin
flag_wait_crc <= 0;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
mode <= EWEN;
end
else if(state_c==S_EWEN) begin
mode <= EWEN;
end
else if(state_c==S_WRITE)begin
mode <= WRITE;
end
else if(state_c==S_READ)begin
mode <= READ;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
crc_din <= 0;
end
else if(add_wr_cnt && end_wr_cnt==0) begin
crc_din <= din;
end
else begin
crc_din <= rdata;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
crc_din_vld <= 1'b0;
end
else if(add_wr_cnt && end_wr_cnt==0) begin
crc_din_vld <= 1'b1;
end
else begin
crc_din_vld <= rdata_vld;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
crc_clr <= 1'b0;
end
else if((add_rd_cnt && rd_cnt==4-1)|| read2idle_start || write2idle_start) begin
crc_clr <= 1'b1;
end
else begin
crc_clr <= 1'b0;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
addr <= 0;
end
else if(state_c==S_WRITE) begin
addr <= {4'b0,write_sel,wr_cnt};
end
else if(state_c==S_EWEN) begin
addr <= 7'b1100000;
end
else begin
addr <= {4'b0,rd_cnt};
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
write_sel <= 1'b0;
end
else if(write2idle_start) begin
write_sel <= ~write_sel;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wdata <= 0;
end
else if(wr_cnt==WR_NUM-1) begin
wdata <= crc_dout;
end
else begin
wdata <= din;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
start <= 1'b0;
end
else if(add_wr_cnt || start_read || ewen2read_start)begin
start <= 1'b1;
end
else begin
start <= 1'b0;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
dout_temp<= 0;
end
else if(rdata_vld) begin
dout_temp[(8-rd_cnt)*8-1 -:8] <= rdata;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
dout0_err <= 1'b0;
end
else if(add_rd_cnt && rd_cnt==4-1) begin
if(crc_dout!=0)
dout0_err <= 1'b1;
else
dout0_err <= 1'b0;
end
end
always@(*)begin
if(crc_dout!=0)
dout1_err = 1'b1;
else
dout1_err = 1'b0;
end
assign dout0_temp = dout_temp;
assign dout1_temp = dout_temp;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
dout <= 0;
end
else if(end_rd_cnt) begin
if(dout0_err==1'b0 && dout1_err==1'b0)begin
if(dout0_temp > dout1_temp)
dout <= dout0_temp;
else
dout <= dout1_temp;
end
else if(dout0_err==1'b0)begin
dout <= dout0_temp;
end
else begin
dout <= dout1_temp;
end
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
dout_vld <= 1'b0;
end
else if(end_rd_cnt) begin
if(dout0_err && dout1_err)begin
dout_vld <= 1'b0;
end
else begin
dout_vld <= 1'b1;
end
end
else begin
dout_vld <= 1'b0;
end
end
crc8_d8 u_crc8_d8(
.clk (clk ),
.rst_n (rst_n ),
.clr (crc_clr ),
.din_vld(crc_din_vld),
.din (crc_din ),
.dout_vld (crc_dout_vld ),
.dout (crc_dout )
);
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参考代码
assign d = din ;
assign c = dout ;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
dout <= 0;
end
else if(clr)begin
dout <= 0;
end
else if(din_vld) begin
dout <= d ^ d ^ d ^ c ^ c ^ c;
dout <= d ^ d ^ d ^ c ^ c ^ c;
dout <= d ^ d ^ d ^ d ^ c ^ c ^ c ^ c;
dout <= d ^ d ^ d ^ d ^ c ^ c ^ c ^ c;
dout <= d ^ d ^ d ^ c ^ c ^ c;
dout <= d ^ d ^ d ^ c ^ c ^ c;
dout <= d ^ d ^ d ^ c ^ c ^ c;
dout <= d ^ d ^ d ^ c ^ c ^ c;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
dout_vld <= 0;
end
else begin
dout_vld <= din_vld;
end
end
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时间,等待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_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参考代码
assign start_vld = flag_work==0 && start;
always@(*)begin
if(mode==EWEN)
opcode = 3'b100;
else if(mode==WRITE)
opcode = 3'b101;
else
opcode = 3'b110;
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
dout <= 0;
end
else if(start_vld) begin
dout <={opcode,addr,wdata};
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
mode_reg <= 0;
end
else if(start_vld) begin
mode_reg <= mode;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_work <= 0;
end
else if(start_vld) begin
flag_work <= 1;
end
else if(end_cnt2)begin
flag_work <= 0;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt0 <= 0;
end
else if(add_cnt0)begin
if(end_cnt0)
cnt0 <= 0;
else
cnt0 <= cnt0 + 1;
end
end
assign add_cnt0 = flag_work;
assign end_cnt0 = add_cnt0 && cnt0== x-1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 0;
end
else if(add_cnt1)begin
if(end_cnt1)
cnt1 <= 0;
else
cnt1 <= cnt1 + 1;
end
end
assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1==y-1 ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt2 <= 0;
end
else if(add_cnt2)begin
if(end_cnt2)
cnt2 <= 0;
else
cnt2 <= cnt2 + 1;
end
end
assign add_cnt2 = end_cnt1;
assign end_cnt2 = add_cnt2 && cnt2==u-1;
assign en_sk1 = add_cnt0 && cnt0==x/2-1 && cnt2==1-1;
assign en_sk0 = end_cnt0 ;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
sk <= 0;
end
else if(add_cnt0 && cnt0==x/2-1 && cnt2==1-1)begin
sk <= 1;
end
else if(end_cnt0)begin
sk <= 0;
end
end
assign en_di = add_cnt0 && cnt0==1-1 && cnt2==1-1;
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
di <= 0;
end
else if(en_di) begin
di <= dout;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
cs <= 0;
end
else if(start_vld==1 || (add_cnt2 && cnt2==2-1 && end_cnt2==0)) begin
cs <= 1;
end
else if((add_cnt2 && cnt2==1-1) || end_cnt2)begin
cs <= 0;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rdata <= 0;
end
else if(end_cnt0 && cnt1 >=10 && cnt2==1-1 && mode_reg==READ ) begin
rdata <= do;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rdata_vld <= 0;
end
else begin
rdata_vld <= end_cnt2 && mode_reg==READ;
end
end
always@(*)begin
if(start || flag_work)
rdy = 1'b0;
else
rdy = 1'b1;
end
always@(*)begin
if(mode_reg==WRITE && cnt2==1-1)begin
x = 250;
y =18;
u = 3;
end
else if(mode_reg==WRITE && (cnt2==2-1 ))begin
x = 250 ;
y = 1 ;
u = 3 ;
end
else if(mode_reg==WRITE && cnt2==3-1)begin
x = 500000;
y = 1 ;
u = 3 ;
end
else if(mode_reg==READ && cnt2==1-1)begin
x = 250;
y =18;
u = 2;
end
else if(mode_reg==READ && cnt2==2-1)begin
x = 250 ;
y = 1 ;
u = 2 ;
end
else if(mode_reg==EWEN && cnt2==1-1)begin
x = 250;
y =10;
u = 2;
end
else begin
x = 250 ;
y = 1 ;
u = 2 ;
end
end
endmodul
1.8 效果和总结
本工程上板之后,可通过复位来验证现象,若要通过断电来进行验证,需要将工程烧录进开发板才行。
1.8.1db603开发板
由于本工程现象是一个动态的过程,所以从下面图片中看不出具体实现的效果,想要看上板效果的话可以看一下工程上板的视频。
1.8.2mp801开发板
由于本工程现象是一个动态的过程,所以从下面图片中看不出具体实现的效果,想要看上板效果的话可以看一下工程上板的视频。
1.8.3ms980试验箱
由于本工程现象是一个动态的过程,所以从下面图片中看不出具体实现的效果,想要看上板效果的话可以看一下工程上板的视频。
感兴趣的朋友也可以访问明德扬论坛(http://www.fpgabbs.cn/)进行FPGA相关工程设计学习,也可以看一下我们往期的文章:《至简设计系列_LCD入门案例_边框显示》《至简设计系列_BCD译码实现》《至简设计系列_简易计算器》《至简设计系列_基于FPGA的超声波测距系统设计》《至简设计系列_串口回环工程》《至简设计系列_矩阵按键检测》《至简设计系列_闹钟》《至简设计系列_7段数码管显示》《阻塞赋值与非阻塞赋值》《参数例化时自动计算位宽的解决办法》
1.9 公司简介明德扬是一家专注于FPGA领域的专业性公司,公司主要业务包括开发板、教育培训、项目承接、人才服务等多个方向。点拨开发板——学习FPGA的入门之选。
MP801开发板——千兆网、ADDA、大容量SDRAM等,学习和项目需求一步到位。网络培训班——不管时间和空间,明德扬随时在你身边,助你快速学习FPGA。周末培训班——明天的你会感激现在的努力进取,升职加薪明德扬来助你。就业培训班——七大企业级项目实训,获得丰富的项目经验,高薪就业。专题课程——高手修炼课:提升设计能力;实用调试技巧课:提升定位和解决问题能力;FIFO架构设计课:助你快速成为架构设计师;时序约束、数字信号处理、PCIE、综合项目实践课等你来选。项目承接——承接企业FPGA研发项目。人才服务——提供人才推荐、人才代培、人才派遣等服务。
【设计教程下载】
【设计视频教程】
https://www.bilibili.com/video/BV1Af4y117H4?p=38
https://www.bilibili.com/video/BV1Af4y117H4?p=39
https://www.bilibili.com/video/BV1Af4y117H4?p=40
【工程源码】
页:
[1]