赞
踩
1.计数器的标准形式
module top_module ( input clk, input a, output [3:0] q ); wire add_flag; wire end_flag; wire en; wire rst; reg [3:0] cnt; assign en = 1'b1; assign rst = 1'b1; parameter end_counter = 7; assign q = cnt; assign add_flag = en; assign end_flag = add_flag && (cnt == (end_counter - 1)); always@(posedge clk) begin if(!rst) begin cnt <= 0; end else if(a) begin cnt <=4; //置位端 end else if(add_flag) begin if(end_flag) begin cnt <=0; end else begin cnt <=cnt+1; end end end endmodule
分频器永远是降频
即2N分频,计数到N-1时翻转
即4分频,计数到1则反转
2分频。计数到0反转,也就是直接反转
modlue div( input clk, input rst_n, output div_clk ); parameter N = 4 //常量参数 wire [2:0] cnt ; wire add_flag,end_flag; assign add_flag = 1'b1; assign ennd_flag = add_flag && (cnt == N-1) always@(posedge clk) begin if(!rst_n) div_clk <= 0 ; else if(end_flag) cnt <= 0; else if(add_flag) cnt <= cnt + 1'b1; end endmodule
奇数分频器的生成原理是获取奇数分频的半个时钟周期,根据半个时钟周期拟合波形。
对于奇数分频可以像偶数分频一样,通过计数反转电路即可,
如果实现非50%占空比的 波形,可以通过计数到任意占空比处反转电路即可完成。
module fenp(clk,rst,out); parameter n=5;//分频数 output wire out; input rst,clk; reg clk1,clk2; reg [4:0] count1; reg [7:0] count; always @(posedge clk or negedge rst)begin if (!rst)begin count1<=5'b0; end else if (count1==n-1) count1<=0; else count1<=count1+1; end always@(posedge clk or negedge rst)begin if (!rst) clk1<=0; else if((count1==n-1 || count1 == 0))//cnt == 0是1:4,,时许电路计数器0-4 clk1<=!clk1; end assign out=clk1; endmodule
如果实现50%占空比的波形则必须要通过上升下降沿电路实现
假如2n+1 = 5,要进行5分频电路,即需要获取0.5个时钟才能对信号进行占空比的分频
1.需要求出高电平的持续时间,2.5时钟周期,低电平时间2.5时钟周期
2.求出来高电平n =2 ,剩下的就是低电平时间。最后时钟相或,(n-1)/2,n =2 的时候,是第三个时钟的时候。
module fenp(clk,rst,out); parameter n=5; output wire out; input rst,clk; reg clk1,clk2; reg [4:0] count1,count2; reg [7:0] count; // always @(posedge clk or negedge rst)begin if (!rst)begin count1<=5'b0; end else if (count1==n-1) count1<=0; else count1<=count1+1; end always@(posedge clk or negedge rst)begin if (!rst) count2<=5'b0; else if (count2==n-1) count2<=0; else count2<=count2+1; end always@(posedge clk or negedge rst)begin if (!rst) clk1<=0; //n = 2 的时候,就是采集到第三个上升沿时钟的时候 else if((count1==n-1 || count1 == (n-1)/2))// clk1<=!clk1; end //下降沿的操作电路 always@(negedge clk or negedge rst)begin if (!rst) clk2<=0; else if((count2==n-1) || (count2 == (n-1)/2)) clk2<=!clk2; end assign out=clk1|clk2; endmodule
小数分频即实现原频率是10ns
使用数字电路的方法永远获取不到标准的波形,
小数分频两个设计原则:1.时钟抖动小;2.占空比接近50%。
意义不大小数分频不如pll分频
pll分频,
1.一个计数器用于产生周期
module pwm( input clk, input rst_n, output pwm ); local parameter endcount = 1000; reg reg cnt ;//用于产生pwm周期 wire add_flag; wire end_flag; reg wave; reg pulse_width;//用于pwm阈值宽度判断 assign end_flag = add_flag &&(cnt == endcount -1); assign add_flag = 1; always@(posedge clk or negedge rst_n) begin if(!rst_n) begin cnt<=0; end else if(add_flag) begin cnt<=cnt +1; end else if(end_flag) cnt<=0; end always @(posedge clk or negedge reset_n) begin if(!rst_n) wave <= 0; else if(cnt<pulse_width && add_flag) wave <= 1; else wave <= 0; end assign pwm = wave;//产生固定波形的pwm endmodule
产生可变pwm的波形
//pwm counter,用于产生pwm周期 localparam pwm_cnt_max = 27'd500000 ; always @(posedge clk or negedge rst) begin if(!rst) pwm_cnt <= 27'd0; else if(pwm_cnt >= pwm_cnt_max) pwm_cnt <= 27'd0; else pwm_cnt <= pwm_cnt + 1'b1; end //用于产生呼吸灯加或者减少, always @(posedge clk) begin if (thre_cnt == 27'd0) cnt_flag = 1'b0; else if(thre_cnt == pwm_cnt_max) cnt_flag = 1'b1; end //用于产出宽度的不间断变化 always @(posedge clk or negedge rst) begin if(!rst) thre_cnt <=27'd0; else if (pwm_cnt == pwm_cnt_max) begin if (cnt_flag) thre_cnt <= thre_cnt - 10'd500; else thre_cnt <= thre_cnt + 10'd500; end end assign led = pwm_cnt >= thre_cnt ? 1'b1:1'b0;
时钟切换如果不考虑电路毛刺的问题,可以直接用
assign clk_out = (select == 1)?clkout1:clkout2;
考虑到时钟毛刺。采用在下降沿的时候采样信号输出
module clk_select( input clk0; input clk1; input rst_n; input select; output clk_out ); reg out0; reg out1; always@(negedge clk0 or negedge rst_n) begin if(!rst_n) out0 <= 0; else out0 <= ~out1 & !select end always@(negedge clk1 or negedge rst_n) begin if(!rst_n) out1 <= 0; else out1 <= ~out0 & select end assign clk_out = !clk0&out0 | !clk1&out1
时钟切换考虑吧毛刺的问题,需要在时钟下降沿的时候采样,对于时钟切换存在异步情况会出现亚稳态,需要用两级同步触发器进行同步采样信号,在clk1的时候同步clk0寄存器的输出的d1
//==================================================================================================================// // 1) CLOCK MUX // //==================================================================================================================// // // // The following (glitch free) clock mux is implemented as following: // // // // // // // // out_r0 out0 // // +-----. +--------+ +--------+ // // select_in >>----+-------------O| \ | | | | +-----. // // | | |---| D Q |---| D Q |--+-------| \ // // | +-------O| / | | | | | | |O-+ // // | | +-----' | | | | | +--O| / | // // | | | /\ | | /\ | | | +-----' | // // | | +--+--+--+ +--+--+--+ | | | // // | | | O | | | // // | | | | | | | +-----. // // clk_in0 >>----------------------------------+------------+-----------+ +--\ \ // // | | | | |----<< clk_out// // | | +---------------------------------------+ +--/ / // // | | | | +-----' // // | +---------------------------------------------+ | // // | | out_r1 out1 | | // // | | +-----. +--------+ +--------+ | | // // | +-O| \ | | | | | +-----. | // // | | |---| D Q |---| D Q |--+-------| \ | // // +--------------| / | | | | | |O-+ // // +-----' | | | | +--O| / // // | /\ | | /\ | | +-----' // // +--+--+--+ +--+--+--+ | // // | O | // // | | | // // clk_in1 >>----------------------------------+------------+-----------+ // // // // // //==================================================================================================================//
module select_clk(input clka;input clkb;input select;output clkout;); reg out_r1; reg out1; reg out_r0; reg out0; always @(posedge clk1 or negedge rst_n)begin if(rst_n == 1'b0)begin out_r1 <= 0; end else begin out_r1 <= ~out0 & select; end end always @(negedge clk1 or negedge rst_n)begin if(rst_n == 1'b0)begin out1 <= 0; end else begin out1 <= out_r1; end end always @(posedge clk0 or negedge rst_n)begin if(rst_n == 1'b0)begin out_r0 <= 0; end else begin out_r0 <= ~select & ~out1; end end always @(negedge clk0 or negedge rst_n)begin if(rst_n == 1'b0)begin out0 <= 0; end else begin out0 <= out_r0; end end assign outclk = (out1 & clk1) | (out0 & clk0);
门控时钟如果不加限制,en段输出频率过快也会产生毛刺。
如果直接组合电路
assign clkout = clk & en;
如果使用寄存器在下降沿的时候采样输出可以避免
always@(negedge clk) begin if(en) clokout <= clk;end
门控时钟插入后会一定程度上减少后级电路的面积,如果后级D触发器的位宽太多,将会有更好的功耗优化。
在不用的时候把数据设成0并不能减少功耗,保持数据不变化才能减少toggle,降低功耗!
小于N(4)个时钟周期的数据为毛刺
module edge_detector( input clk, input rstn, input data_in, output reg data_out ); reg [1:0] cunt_h; reg [1:0] cunt_l; // 检测高电平毛刺 always @(posedge clk) begin if(!rstn) cunt_h <= 2'd0; else if(!data_in) cunt_h <= 2'd0; else cunt_h <= cunt_h + 1'b1; end // 检测低电平毛刺 always @(posedge clk) begin if(!rstn) cunt_l <= 2'd0; else if (data_in) cunt_l <= 2'd0; else cunt_l <= cunt_l +1'b1; end always @(posedge clk) begin if(!rstn) data_out <= 1'b0; else if((cunt_h == 2'b11 && data_in)| (cunt_l == 2'b11 && ~data_in)) data_out <= data_in; end endmodule
一般来说电压越低,功耗越低,频率越低,!电压越大,电路延迟越小
动态功耗计算公式:1.p=1/2 V
静态功耗主要有漏电流引起的p=VIleak电压
1.时钟分频,各模块使用够用的时钟,容易产生毛刺,使用锁存器避免该毛刺的形成
2.门控时钟,不使用时关闭,逻辑与en&clk,
3.合理选择算法,例如使用查找表的方法代替乘除运算,有时可以减少一些功耗。
4.利用握手信号完成异步设计,省去全局时钟,也可以减少功耗。
5.并行与流水一定程度上可以降低功耗,相当于将原来的电路流水线处理后,电容会变成串联,容量减小后,充满电的电容需要的电压减小,功耗也就降低。
低功耗按照类型分类呢,其构成主要有动态功耗、静态功耗、浪涌功耗这三种。
动态功耗:开关功耗{翻转功耗}、短路功耗Pdynamic = VddVddCL*f,,短路功耗就是,晶体管在一瞬间会同时导通,其中开关功耗在动态功耗中占大部分比例;从上面的两个式子中我们可以看到,动态功耗主要跟电源的供电电压、翻转率、负载电容有关。
静态功耗:静态功耗主要是漏电流引起的功耗,
浪涌功耗:短路功耗,反相器,两个mos管在阈值电压处,同时导通时的短路功耗。
RTL层降低功耗,就是在编码的时候维持数据不变化,让rtl的代码利于自动生成对于门控时钟的生成的编码风格,
程序控制类指令包括跳转指令,循环指令,子程序指令以及中断指令,这些指令控制程序的执行顺序。
算法实现
有源的一般是晶振四个脚,不需要起振电路,内部集成,输出波形为方波或正弦波
无缘的一般是晶体两个脚,需要起振电路才能开始震荡,输出波形为正弦波
电路中同时存在阻抗与容抗,当电路频率的频率达到一定频率的时候,阻抗容抗相互抵消。
综合,就是在标准单元库和特定的设计约束基础上,把数字设计的高层次描述转换为优化的门级网表的过程。
不可综合 initial events time,defparam,$finish,fork,join,initial,delays,UDP,wait。
所有都支持的有:always(),assign,begin,end,case(选择器),wire,tri三态线(tri),(supply0,电源supply1),reg,integer,default,for,function,and,nand(与非),or,nor,xor,xnor,buf(模拟延迟),not,bufif0,bufif1,notif0,notif1,if,inout,input,instantitation(例化模块),module,negedge,posedge,operators(单目、双目、三目运算符等),output,parameter。
三态门有:
bufif0 bufif1 notif0 notif1,,使能后才有输出,,否则输出高阻态,三态门通常用于驱动总线
工具支持与不支持不确定
casex,casez,wand,triand,wor,trior(三态线网),real(双精度浮点),disable,forever,arrays,memories,repeat,task,while。
casez、casex真值表
task没有返回值,一般不支持综合,可以互相调用task 、funtion
module add4( input cin, input [3:0]a, input [3:0]b, output reg[4:0] sum, output reg cout);task add( input cin, input [3:0] a, input [3:0] b, output [4:0] sum, output cout);begin {cout,sum}=a+b+cin;endendtaskalways@(a or b or cin)begin add(cin,a,b,sum,cout);endendmodule
中不能包含时序控制,不能相互调用,
一般支持综合,返回值是函数的输出,需要在函数开头处定义
1.只能在函数内部定义、引用,不用包含任何非阻塞操作,
2.在模块中定义,可以应用于模块内任何地方,作用域 只在该模块内部
3.只有一个返回值,且没有输出,通过funtion_ID传递返回值,且只有一个返回值,不配置位宽,默认为1
4.函数可以调用其他函数,但是不能调用任务·
module endian_rvs #(parameter N = 4)( input en, //enable control input [N-1:0] a , output [N-1:0] b); reg [N-1:0] b_temp ; always @(*) begin if (en) begin b_temp = data_rvs(a); end else begin b_temp = 0 ; end end assign b = b_temp ; //function entity function [N-1:0] data_rvs ; input [N-1:0] data_in ; parameter MASK = 32'h3 ; integer k ; begin for(k=0; k<N; k=k+1) begin data_rvs[N-k-1] = data_in[k] ; end end endfunction endmodule
module round_robin_bus_arbiter( input clk, input rst_n, input [2:0] req, //假如需要给3个主机分配总线 output reg [1:0] grant_out //2'b00 A获得总线, 2‘b01 B获得总线 , 2'10 c获得总线 ); always @ (posedge clk or negedge rst_n) begin if (!rst_n) grant_out <= 1'b11; else case(grant_out) 2'b00: //之前A获得总线 case (req) 3'b000: grant_out <= 2'b00; 3'b001: grant_out <= 2'b00; 3'b010: grant_out <= 2'b01; 3'b011: grant_out <= 2'b01; 3'b100: grant_out <= 2'b10; 3'b101: grant_out <= 2'b10; 3'b110: grant_out <= 2'b01; 3'b111: grant_out <= 2'b01; default: grant_out <= 2'b00; endcase 2'b01: //之前B获得总线 case (req) 3'b000: grant_out <= 2'b01; 3'b001: grant_out <= 2'b00; 3'b010: grant_out <= 2'b01; 3'b011: grant_out <= 2'b00; 3'b100: grant_out <= 2'b10; 3'b101: grant_out <= 2'b10; 3'b110: grant_out <= 2'b01; 3'b111: grant_out <= 2'b01; default: grant_out <= 2'b01; endcase 2'b01: //之前C获得总线 case (req) 3'b000: grant_out <= 2'b10; 3'b001: grant_out <= 2'b00; 3'b010: grant_out <= 2'b01; 3'b011: grant_out <= 2'b00; 3'b100: grant_out <= 2'b10; 3'b101: grant_out <= 2'b00; 3'b110: grant_out <= 2'b01; 3'b111: grant_out <= 2'b00; default: grant_out <= 2'b10; endcase default: grant_out <= 2'b00; endcase end endmodule
1.慢到快
打两拍,防止亚稳态
2.快到慢
展宽脉冲信号,一般时钟频率是已知的,将原来的型号展宽至慢时钟2倍以上的频率采集.1.5个时钟沿
3。mcp(Multi-Cycle Path),多周期路径去同步,可跨时钟域同步多个位
电平同步器:其实就是两级同步触发器,限制是要同步的电平信号 需要大于2个同步时钟周期,至少要1个同步周期,刚好在上升沿采集到,。同步后输入信号需要恢复到无效,如果要获取与同步周期等宽的脉冲信号则不适用,慢时钟到快时钟,电平同步器所引起的延迟区间为[2Dsetup + T2, T1 + 2T2]。由快到慢的时候,有可能会采集不到,(T2 + Dsetup)和(2T2),信号在两个新时钟有效沿之后,就成为了新时钟域下的有效信号。快到慢这种跨越方式不合理,可以不考虑
//同步至新时钟域 always @(posedge clk_2 or negedge rst_n) begin if(rst_n == 1'b0) begin src_state_d0 <= 1'b0; src_state_d1 <= 1'b0; end else begin src_state_d0 <= src_state; src_state_d1 <= src_state_d0; end end assign dout = src_state_d1;
边沿同步器:间隔 ≥ 慢时钟域的两个周期,在电平同步器的基础上,通过输出端的逻辑组合,可以完成对于信号边沿的提取,识别上升沿,下降沿以及双边沿,并发出相应的脉冲,最后得到的是脉冲信号,仅适合从慢时钟域跨到快时钟域的信号。边沿同步器成功的将慢时钟域下的一个信号(宽度不定),转化为了快时钟域下的一个与周期同宽的脉冲信号。边沿同步器的延时区间为[T2 + Dsetup + Tlogic, 2T2 + Tlogic]。
//边沿同步器module edge_syc( input wire clk_1, input wire clk_2, input wire din, input wire rst_n, output wire dout_r, output wire dout_f, output wire dout_e ); reg src_state; reg src_state_d0, src_state_d1, src_state_d2; //原时钟域下脉冲信号转变为电平信号 always @(posedge clk_1 or negedge rst_n) begin if(rst_n == 1'b0) src_state <= 1'b0; else src_state <= din; end //同步至新时钟域 always @(posedge clk_2 or negedge rst_n) begin if(rst_n == 1'b0) begin src_state_d0 <= 1'b0; src_state_d1 <= 1'b0; src_state_d2 <= 1'b0; end else begin src_state_d0 <= src_state; src_state_d1 <= src_state_d0; src_state_d2 <= src_state_d1; end end //边沿检测产生新的脉冲 assign dout_r = src_state_d1 & ~src_state_d2; assign dout_f = !src_state_d1 & src_state_d2; assign dout_e = src_state_d1 ^ src_state_d2; endmodule
脉冲同步器:
可以同步一个时钟周期的脉冲,但是输入脉冲之间的最小间隔必须大于等于两个同步器时钟周期,该方法目的是通过电平信号产生脉冲信号,实质上还是同步电平信号,可以用于快到慢周期同步
将src_clk时钟域的输入脉冲转换为src_clk时钟域的电平信号src_state;
对src_state电平信号进行打拍(打两拍)同步到dst_clk时钟域;
对dst_clk时钟域的电平信号进行检测,产生dst_clk时钟域脉冲;、
相当于在目的时钟域的信号,通过打一拍的方式,,打一拍异或相当于产生了脉冲时钟,可适用于快时钟域到慢时钟域。
脉冲同步器的延时时间与边沿同步器相同:[T2 + Dsetup + Tlogic, 2T2 + Tlogic]。
module pulse_sync(src_clk, src_rst_n, src_pulse, dst_clk, dst_rst_n, dst_pulse); input src_clk; //source clock input src_rst_n; //source reset input src_pulse; //source pulse in input dst_clk; //destination clock input dst_rst_n; //destination reset output dst_pulse; //destination pulse out//Internal singles reg src_state;//!!!用于标定脉冲信号的产生 reg state_delay1; reg state_delay2; reg dst_state; wire dst_puase;//==============MODULE MAIN CODE=========================//1.输入脉冲转成电平信号,确保时钟B可以采到 always@(posedge src_clk or negedge src_rst_n)begin if(src_rst_n==0) src_state <= 0; else if(src_pulse) src_state <= ~src_state; end//2.//源时钟域的src时钟下电平信号转成时钟dst下的脉冲信号 always@(posedge dst_clk or negedge dst_rst_n)begin if(dst_rst_n)begin state_delay1 <= 0; state_delay2 <= 0; dst_state <= 0; end else begin state_delay1 <= src_state; state_delay2 <= state_delay1; dst_state <= state_delay2; end end assign dst_pulse = dst_state^state_delay2;endmodule
如果输入脉冲的最小间隔必须等于两个新时钟的时钟周期,处理办法是使用握手信号。
主要原理是1:源时钟产生握手请求,将电平展宽,在收到响应的时候将源请求清空,
2.目的时钟2级同步请求,同步收到后将相应信号拉高。
3.源时钟2级同步目的时钟的相应,接收到相应后将源请求清空,。
4.目的时钟的请求信号拉低后,同步后的时钟也拉低,随之拉低相应信号,
需要增加同步的失败的判断,在响应信号与请求信号未拉底的 时候再次同步信号,这是同步就会失败,
‘A时钟域的请求信号跨越至B时钟域,所消耗的时间为**(TB,2TB](此时,请求信号顺利跨越到B时钟域);
B时钟域检测到同步来的请求信号,消耗时间为TB**(此时,时序已稳定,消耗时间可视为常数);
B时钟域产生的应答信号,跨时钟域到A时钟域,所消耗的时间为(TA, 2TA];
A时钟域检测到同步的应答信号,将请求信号置为无效,消耗时间为TA;
无效的请求信号仍要同步到B时钟域,所消耗的时间为**(TB,2TB];
请求信号无效,应答信号随之拉低,消耗时间为TB**;
无效的应答信号需要再次跨越到A时钟域,所消耗的时间为(TA, 2TA];
通过上述推导,可得最坏情况下所需要的时钟周期为**(5TA+6TB)**才能完成整个操作,同步器才能再次被置为空闲
//脉冲同步器 module pulse_syc_handshake( input wire din, input wire clk_A, input wire rst_n_A, input reg clk_B, input reg rst_n_B, output wire sync_idle, //给出同步器是否空闲的信号 output reg sync_fail, //同步失败:位于原时钟域 output wire dout ); reg src_sync_req; //原时钟产生同步请求 reg req_state_dly1; reg req_state_dly2; //同步器的输出 reg req_state_dly3; //目的时钟延后信号,以保证脉冲输出 reg dst_sync_ack; //目的时钟产生应答 reg ack_state_dly1; reg ack_state_dly2; //同步器的输出 wire src_sync_ack; //原时钟接收应答 //同步器空闲状态的判断:原时钟下:请求和应答信号同时无效 assign sync_idle = ~(src_sync_req | src_sync_ack ); //同步失败的判断 always @(posedge clk_A or negedge rst_n_A) begin if(rst_n_A == 1'b0) sync_fail <= 1'b0; else if(din & (~sync_idle)) //源时钟脉冲到来,此时同步器不空闲,给出同步失败 sync_fail <= 1'b1; else sync_fail <= 1'b0; end //原时钟产生请求信号,请求信号的产生相当于将脉冲转化为了电平 always @(posedge clk_A or negedge rst_n_A) begin if(rst_n_A == 1'b0) src_sync_req <= 1'b0; else if(din & sync_idle) //源时钟脉冲到来,且源时钟空闲,传递请求。同时完成了脉冲转电平 src_sync_req <= 1'b1; else if(src_sync_ack) //检测到应答以后,清除请求 src_sync_req <= 1'b0; end //同步原时钟请求信号到目的时钟,利用请求信号跨时钟域 always @(posedge clk_B or negedge rst_n_B) begin if(rst_n_B == 1'b0) begin req_state_dly1 <= 1'b0; req_state_dly2 <= 1'b0; req_state_dly3 <= 1'b0; end else begin req_state_dly1 <= src_sync_req; req_state_dly2 <= req_state_dly1; //打两拍结束 req_state_dly3 <= req_state_dly2; //再外接一个寄存器,以保证脉冲输出 end end //上升沿检测,产生输出脉冲吗,,这个地方实际是检测一个上升沿。 assign dout = (~req_state_dly3) & req_state_dly2; //完成输出脉冲 //目的时钟产生应答信号 always @(posedge clk_B or negedge rst_n_B) begin if(rst_n_B == 1'b0) dst_sync_ack <= 1'b0; else if (req_state_dly2) //同步高电平已到达 dst_sync_ack <= 1'b1; else dst_sync_ack <= 1'b0; end //同步目的时钟产生的应答信号到原时钟 always @(posedge clk_A or negedge rst_n_A) begin if(rst_n_A == 1'b0) begin ack_state_dly1 <= 1'b0; ack_state_dly2 <= 1'b0; end else begin ack_state_dly1 <= dst_sync_ack; ack_state_dly2 <= ack_state_dly1; end end assign src_sync_ack = ack_state_dly2; endmodule module pulse_syc_handshake_tb; reg clk_A; reg rst_n_A; reg din; wire sync_idle; wire sync_fail; reg clk_B; reg rst_n_B; wire dout; always #10 clk_A = ~clk_A; always #40 clk_B = ~clk_B; initial fork clk_A = 1'b1; din = 1'b0; clk_B = 1'b0; #10 rst_n_A = 1'b0; #20 rst_n_B = 1'b0; #60 rst_n_A = 1'b1; #70 rst_n_B = 1'b1; #300 din = 1'b0; #320 din = 1'b1; #340 din = 1'b0; //脉冲间隔大于等于5Ta+6Tb #920 din = 1'b1; #940 din = 1'b0; //脉冲间隔小于5Ta+6Tb #1400 din = 1'b1; #1420 din = 1'b0; join pulse_syc_handshake u1(.clk_A(clk_A), .rst_n_A(rst_n_A), .din(din), .sync_idle(sync_idle), .sync_fail(sync_fail), .clk_B(clk_B), .rst_n_B(rst_n_B), .dout(dout)); endmodule
那么信号在快时钟域到底需要多宽,才能保证在慢时钟域安全的被采样到呢?比较安全的宽度是,快时钟域的信号宽度必须是慢时钟域时钟周期的1.5倍以上。也就是要持续3个时钟沿以上(上升沿和下降沿都算)。这个被称为:“三时钟沿”要求。握手信号[需要编写verilog]
异步fifo
又称循环二进制码
格雷码是一个数&该数右移一位
G = A^A>>1;
对于每一位来说,首位不变,首位永远与0异或,所以还是本身,次位开始,是原数据的对应位与后一位异或即可,相当于把元数据右移1位
G[i] = A[i]^A[i-1];
由此可以得出A 与移位A数据进行相关操作,相当于A位数之间相互操作
格雷码逆运算,转二进制,高位保持不变
bin[n] = gray[n]
bin[n-1] = bin[n] ^ gray[n-1] = gray[n]^gray[n-1]
bin[n-2] = bin[n-1]^gray[n-2] = gray[n]gray[n-1]gray[n-2]
…
ps:由于异或、同或等逻辑操作支持逆运算,。
G[i] = A[i]^A[i-1];
A = g ^ A>>1
所以求二进制码只需要将首位不变,二进制右移得到
状态较少时(4-24个状态)用独热码效果好,状态多时格雷码(状态数大于24)
独热码适合写条件复杂但是状态少的状态机;
格雷码适合写条件不复杂但是状态多的状态机。
独热码不需要解码,节约组合逻辑电路。
fifo接口规范
read_clk(clk) 、read_enable、read_data(out)、empty_flag
write_clk(clk)、write_enable 、write_data(in)、full_flag
rst_n//
fifo的读写地址是循环执行的,每当写地址
fifo有2中方法实现
a.采取计数器分析mem,计数器用于统计mem中的数据个数,写了加1 读减1,表示的是有效数据,即写入还没有被读取的数据。
计数器中的用途主要是用于判断mem中的实际有效数量来判断fifo是否空满。读写指针读写完一圈后自动继续,
//对于fifo来说只有读写使能选项,没有地址选项,module fifo #(parameter WIDTH=8, parameter ADDR_BIT = 8 parameter DEPTH = 256 )(input clk,input rst_n,input wr_en,input rd_en,input [WIDTH-1:0] data_in,output full_flag,output empty_flag,output reg [WIDTH-1:0] data_out); wire add_flag,sub_flag; reg [WIDTH - 1:0] rd_addr;//0-15-0-15 reg [WIDTH - 1:0] wr_addr; reg [WIDTH - 1:0] mem[DEPTH - 1:0];//mem空间 reg [ADDR_BIT-1:0] cnt ;//0-255 assign add_flag = !full_flag || wr_en == 1'b1; assign sub_flag = !empty_flag ||rd_en == 1'b1 ; //计数器电路 always@(posedge clk or negedge rst_n) begin if(!rst_n) begin cnt <= 0; end else if(add_flag) cnt <= cnt + 1 ; else if(sub_flag); cnt <= cnt - 1 ; end//读电路处理读地址 always@(posedge clk or negedge rst_n) begin if(!rst_n) begin rd_addr <= 0; end else if(!empty_flag ||rd_en == 1'b1) rd_addr <= rd_addr + 1 ; else rd_addr <= rd_addr ; end //写电路处理写地址 always@(posedge clk or negedge rst_n) begin if(!rst_n) begin wr_addr <= 0; end else if(!full_flag || wr_en == 1'b1) wr_addr <= wr_addr + 1 ; else wr_addr <= wr_addr; end //mem处理,满不能写 always@(posedge clk) begin if(!full_flag || wr_en == 1'b1)// mem[wr_addr] <= data_in ; else mem[wr_addr] <= mem[wr_addr]; end //mem处理,空不能读 always@(posedge clk) begin if(!empty_flag ||rd_en == 1'b1)// data_out <= mem[rd_addr]; else data_out <= data_out; end endmodule
b.扩展地址位,使用地址线多一位的方式,判断,读写地址是否有溢出
module fifo #(parameter WIDTH = 8, //数据位宽 parameter DEPTH = 16, parameter ADDR_BIT = 4) //深度数据位数 (input clk, rst, wr_en, rd_en, input [WIDTH-1:0] wr_data, //输入数据 没有进行reg缓存,输出数据用到了 output reg [WIDTH-1:0] rd_data, //尽量将reg与output声明到一起避免提示变量重复声明 output full,empty ); wire [ADDR_BIT-1:0] rd_addr,wr_addr; //中间信号不必在端口中声明 reg [WIDTH-1:0] memory [DEPTH-1:0]; reg [ADDR_BIT:0] rd_addr_1; //扩展一位地址用于判断空满 reg [ADDR_BIT:0] wr_addr_1; //扩展一位地址用于判断空满 assign wr_addr = wr_addr_1[ADDR-1:0];//取地址低的位数用于mem使用 assign rd_addr = rd_addr_1[ADDR-1:0];//取地址低的位数用于mem使用always @(posedge clk or negedge rst) //写数据 begin if(!rst) wr_addr_1 <= 'h0; else if(!full && wr_en)begin wr_addr_1 <= wr_addr_1 + 1'b1; //扩展位数加一下,用于判断是否空满 memory[wr_addr] <= wr_data; end endalways @(posedge clk or negedge rst) //读数据 begin if(!rst) rd_addr_1 <= 'h0; else if(!empty && rd_en)begin rd_addr_1 <= rd_addr_1 + 1'b1; rd_data <= memory[rd_addr]; end end assign empty = (w_addr_1==rd_addr_1)?1:0; //地址判断读地址追上了写地址表示当前地址为空, assign full = ((w_addr_1[ADDR]!=rd_addr_1[ADDR]) && (w_addr_1[ADDR-1:0]==rd_addr_1[ADDR-1:0]))?1:0;//读写地址不相等,追上了一圈endmodule
异步fifo一般使用扩展地址位的方法处理,
单位宽涉及到跨时钟域的出。慢到快,两级同步触发器做异步处理
多位宽:异步fifo,如果是多位宽计数值,使用格雷码,然后经过同步器
格雷码不能使用非偶数位的格 b雷码
格雷码出错时。会认为是上次的数据,。
扩展地址位不进行格雷码转换
异步fifo判断空满标志需要使用读写地址,但是由于时钟是异步的,需要将读写指针分别同步到对应的时钟域中才能进行二次判断,
ps:异步fifo使用的地址虽然是使用格雷码同步,但是需要转化成实际的数字才能进行取地址,写入读取ram.格雷码的地址只用于同步地址信号,别的意义不大
module asys_fifo #(parameter DATA_WIDTH=8, parameter ADDR_BIT = 8, parameter DATA_DEPTH = 256) ( input rd, input wr, input [DATA_WIDTH-1:0] din, output reg [DATA_WIDTH-1:0] dout, input rd_clk, input wr_clk, input rst_n, output empty, output full, output reg valid ); //ram用的地址 wire [ADDR_BIT-1:0] rd_addr ; wire [ADDR_BIT-1:0] wr_addr ; //扩展地址位指针 reg [ADDR_BIT:0] rd_addr_ptr ; reg [ADDR_BIT:0] wr_addr_ptr ; //格雷码指针,位宽与扩展相同.格雷码用于对原地址指针进行生成,不参与时许运算,直接由原地址码异或产生 wire [ADDR_BIT:0] rd_addr_gray ; wire [ADDR_BIT:0] wr_addr_gray ; //用于同步的指针d1.d2寄存器 reg [ADDR_BIT:0] rd_addr_to_wr_d1 ; reg [ADDR_BIT:0] rd_addr_to_wr_d2 ; reg [ADDR_BIT:0] wr_addr_to_rd_d1 ; reg [ADDR_BIT:0] wr_addr_to_rd_d2 ; //定义ram reg [DATA_WIDTH-1:0] fifo_ram [DATA_DEPTH-1:0]; //处理ram地址,使用的还是二进制地址 assign rd_addr = rd_addr_ptr[ADDR_BIT-1-:ADDR_BIT]; assign wr_addr = wr_addr_ptr[ADDR_BIT-1-:ADDR_BIT];//wr_addr_ptr[ADDR_BIT-1-:ADDR_BIT],从最7未开始一直到8位,地址一共9位 assign rd_addr_gray = rd_addr_ptr^rd_addr_ptr>>1; assign wr_addr_gray = wr_addr_ptr^wr_addr_ptr>>1; //开始处理ram写数据,在写时钟域 always@(posedge wr_clk or negedge rst_n) begin if(!rst_n) begin fifo_ram[wr_addr] <= 0; end else if(wr && !full) fifo_ram[wr_addr] <= din; else fifo_ram[wr_addr] <=fifo_ram[wr_addr]; end //开始处理ram读数据,在读时钟域 always@(posedge wr_clk or negedge rst_n) begin if(!rst_n) begin dout <= 0; valid <= 1'b0; end else if(rd && !empty) begin dout <= fifo_ram[rd_addr]; valid <= 1'b1; end else begin dout <= 0; valid <= 1'b0; end end //开始处理du指针增加,此处完全可以与ram数据处理放在一块进行处理 always@(posedge rd_clk or negedge rst_n) begin if(!rst_n) begin rd_addr_ptr<= 0; end else if(wr && !full) rd_addr_ptr<=rd_addr_ptr+1; else rd_addr_ptr<=rd_addr_ptr; end //处理xie时钟域的指针变化 always@(posedge wr_clk or negedge rst_n) begin if(!rst_n) begin wr_addr_ptr <= 0 ; end else if(rd && !empty) wr_addr_ptr <= wr_addr_ptr+1; else wr_addr_ptr <= wr_addr_ptr; end //开始处理写指针的格雷码异步同步 always@(posedge wr_clk or negedge rst_n) begin if(!rst_n) begin rd_addr_to_wr_d1<=0; rd_addr_to_wr_d2<=0; end else begin rd_addr_to_wr_d1<= rd_addr_gray; rd_addr_to_wr_d2<= rd_addr_to_wr_d1; end end //开始处理读指针的格雷码异步同步 always@(posedge rd_clk or negedge rst_n) begin if(!rst_n) begin wr_addr_to_rd_d1<=0; wr_addr_to_rd_d2<=0; end else begin wr_addr_to_rd_d1<= wr_addr_gray; wr_addr_to_rd_d2<= wr_addr_to_rd_d1; end end //开始处理空信号,同步过来的写时钟域指针等于读时钟域指针的格雷码 assign empty = (wr_addr_to_rd_d2 ==rd_addr_gray); //处理满信号,同步过来的读时钟域的读指针的格雷码与写时钟域的格雷码比较 assign full = (wr_addr_gray == {~(rd_addr_to_wr_d2[ADDR_BIT-:2]),rd_addr_to_wr_d2[ADDR_BIT-2:0]}) ;//高两位不同 endmodule
fifo深度主要就是计算读写时钟不一致时,写时钟过快引起的数据没有被读取导致的数据丢失的情况。
基本计算方法,计算单个数据写需要的时间,计算总数据下需要的时间,计算总时间下的读了多少数据,总数据减去读了的数据为fifo深度,公式Depth = Data(1-R/W),从公式可以看出 当读写频率发生变化时。相应的深度也会发生变化。读写时钟的变化可以是多周期写多周期读。
1.位宽不同情况(新)
2.跨时钟域情况
见前文
1.一个nmos与pmos并联可以做传输门,串联可以做非门,上p下n是非门
门电路相关的操作
2.上面两个pmos 串联,下面两个nmos并联,或非门电路
3.上面两个pmos并联,下面两个nmos串联与非门电路
相同面积的cmos与非门和或非门哪个更快——与非门会更优
cmos Complementary Metal Oxide Semiconductor,互补金属氧化物半导体,pmos\nmos成对出现,栅极导通电压不一样,总一个导通、一个截止
用于优化读写时序问题,节约双端口sram面积问题。,如果数据的读写没有以上的约束,没必要使用这个
乒乓buffer主要应用在以下场景进行带宽的提升:
下游必须等到上游数据全部写完或者积累到某个程度才能开始读
上游必须等到下游数据全部读完或者读到某个程度才能开始写
module pingpang ( input clk, input rst_n, input [7:0] data_in, // 输入数据 output reg [7:0] data_out // 输出数据 );// ------------------------------------------------------ // reg [7:0] buffer1; // 缓存1 reg [7:0] buffer2; // 缓存2 reg wr_flag; // 写标志,wr_flag=0,写buffer1,wr_flag=1,写buffer2 reg rd_flag; // 读标志,rd_flag=0,读buffer2,rd_flag=1,读buffer1 reg state; // 状态机,0:写1读2,1:写2读1,状态转移和输出分开编码// ------------------------------------------------------ // // 状态转移 always @ (posedge clk or negedge rst_n) begin if(rst_n == 1'b0) begin state <= 1'b0; end else begin case(state) 1'b0 : state <= 1'b1; // 写1读2->写2读1 1'b1 : state <= 1'b0; // 写2读1->写1读2 default : state <= 1'b0; endcase end end// ------------------------------------------------------ // // 状态输出 always @ (state) begin case(state) 1'b0: begin wr_flag = 1'b0; // 写1 rd_flag = 1'b0; // 读2 end 1'b1: begin wr_flag = 1'b1; // 写2 rd_flag = 1'b1; // 读1 end default: begin wr_flag = 1'b0; rd_flag = 1'b0; end endcase end// ------------------------------------------------------ // // 写buffer数据 always @ (posedge clk or negedge rst_n) begin if(rst_n == 1'b0) begin buffer1 <= 8'b0; buffer2 <= 8'b0; end else begin case(wr_flag) 1'b0 : buffer1 <= data_in; // wr_flag = 0,写buffer1 1'b1 : buffer2 <= data_in; // wr_flag = 1,写buffer2 default: begin buffer1 <= 8'b0; buffer2 <= 8'b0; end endcase end end // ------------------------------------------------------ // // 读buffer数据 always @ (posedge clk or negedge rst_n) begin if(rst_n == 1'b0) begin data_out <= 8'b0; end else begin case(rd_flag) 1'b0 : data_out <= buffer2; // rd_flag=0,读buffer2 1'b1 : data_out <= buffer1; // rd_flag=1,读buffer1 default : data_out <= 8'b0; endcase end end// ------------------------------------------------------ // endmodule
两个cmos交叉项链,会存在2个稳态结构,基于双稳态结构构造SR锁存器
一个D触发器是由2个D锁存器组成,两个锁存器级联,clk取反,让一个在高电平工作,一个在低电平工作,在一个clk周期内输出信号,又称主从触发器,
SR锁存器有与非门组成的、或非门组成,或非门需要满足S&R = 0
与非门满足输入SR = 1
RS锁存器,上R下S,上Q下Q‘,或非门的RS锁存器,R端置1,q输出为0,S端置1,Q’为0,,s端置1端,r端置0端
根据RS锁存器,需要建立稳态时间,如果S端下降沿的时候,在没有等Q’端数据返回,如果Q‘端数据没有返回,则就会进入亚稳态,s输入低电平的信号宽度2倍的 门电路的传输延迟时间,2倍的延迟时间就是门电路的延迟时间,
由于可以简化为2个级联sr锁存器与与门构成,建立时间是sr锁存器在时钟上升沿来临的时候,s端数据传输需要的保持不变的时间,,,保持时间是后级Sr锁存器,s端数据传输需要的时间。
输入数据D在时钟上升沿到来之前稳定的时间就表现为SR锁存器的S/R端信号的脉冲宽度。
所以建立与保持时间的来历就是sr锁存器的传输延时时间
面积优化,提高资源利用率以降低功耗要求:串行化(节约逻辑资源),资源共享,逻辑优化
速度优化,提高运行速度:流水线设计(并行),寄存器配平(电路的整体频率取决于延时最大的那条,把延时最大的路径中的门电路拆分到小的部分,达到总总体平衡),关键路径优化(找到延时最大的路径进行时许优化)。
根据电路图写verilog代码比较简单
逻辑组合电路:直接通过逻辑关系写assign a = b&clk
时序电路:以触发器为中心开始写电路,每个触发器写一个always,找到输入输出即可
根据代码画电路图
if综合电路具有优先级,优先级越高越靠近后级电路。
先找always,找到q输出端与输入端,根据if case等判断完成画图
设计约束一般包括时序、负载、面积、功耗等方面的约束。
三个always块
// 1.参数定义() parameter S0=,S1= //2.描述当前状态跳转(非阻塞) always @(posedge clk, posedge areset) begin if (areset) state <= B; // Reset to state B else state <= next; // Otherwise, //cause the state to transition end //3.述下一个状态跳转(阻塞) always @(*) beign case(current_state) if() next_current = endcase end //4.各状态的输出(阻塞与非阻塞) //阻塞 always @(*) beign case(current_state) //非阻塞 always @(posedge clk, posedge areset) case(next_state)
2.四段式状态机,多了状态跳转判断
1.触发器方式实现了、卡诺图化简,卡诺图化简需要4位输入以内的可以,多了就麻烦了
2.每个多位加法器实现 的时候,都是由单位加法器拼接而成
3逻辑表达式往往是与或形式,两次取反后则可以变成与非形式,这就是电路化简的原理。就是用与非门实现电路的基本原理
FPGA 设计中,可以直接调用 IP 核来生成一个高性能的乘法器。在位宽较小的时候,一个周期内就可以输出结果,位宽较大时也可以流水输出。在能满足要求的前提下,可以谨慎的用 ***** 或直接调用 IP 来完成乘法运算。一般情况下N位的信号的乘法需要N个时钟周期,乘法由移位的相加实现,。所以乘法的周期相当于加法的周期。如果一个32位的数据进行计算,在不使用流水线的情况下,最少需要消耗5个时钟周期。即每个时钟周期完成一次加法运算,最多2位数据相加
有时候数字电路在一个周期内并不能够完成多个变量同时相加的操作。所以数字设计中,最保险的加法操作是同一时刻只对 2 个数据进行加法运算,最差设计是同一时刻对 4 个及以上的数据进行加法运算。
A = A<<1 ; *//完成A \* 2*A = (A<<1) + A ; *//对应A \* 3*A = (A<<3) + (A<<2) + (A<<1) + A ; *//对应A \* 15*A=(A<<N) + (A<<N-1) +(A<<N-2)·····+A //A * (2^N +2^(N-1)+····+2^0) //也可以是乘以大的整数便于移位再减去多余的数
普通乘法移位相加
移位相减,除法器的得到的结果一般是非浮点型数据,浮点出除法设计,
所以一般情况下设计的除法器,的被除数大于除数,而且位数一般相等
乘法器流水线的实现的时候,假如一个乘法消耗4个周期,非流水线的输出是。给一个输入4个周期后输出,给一个4个周期后输出,一个信号需要持续4个周期输入。
使用流水线之后,第一个数据需要延迟4个周期 ,,如果第一个信号之后,总体信号相对于输入都有4个周期的延迟,
扩展方法
1.位扩展(深度)
2.字扩展(宽度)
3.字位同时扩展
先进行位扩展,扩展深度
再进行字扩展,复制所有的位。
4.使用ram实现组合逻辑函数分析
n输入m输出相当于n位地址线,m位数据线
需要先将所有地址输入位补全。按照最小项的表达式的方式展开。需要先将地址线译码,补全所有项,再按照地址线于逻辑,数据线或逻辑进行分析。
序列检测器不简单,关键点在于画出状态转移电路图:
比如检测1101.,输入11101
1.要用状态机去实现
检测00101100
首先有
/************************************************************** Author:FPGA探索者** Times :2020-7-7************************************************************/module FSM_SequDetection_1( clk, rst_n, data_in, data_valid); input clk;input rst_n;input data_in;output reg data_valid; //定义状态,这里采用的独热码(One-Hot),FPGA中推荐用独热码和格雷码(Gray)//状态较少时(4-24个状态)用独热码效果好,状态多时格雷码(状态数大于24)效果好parameter IDLE = 5'b00001;parameter S1 = 5'b00010;parameter S2 = 5'b00100;parameter S3 = 5'b01000;parameter S4 = 5'b10000; reg [4:0] current_state; //现态reg [4:0] next_state; //次态 //三段式FSM,第一段,同步时序逻辑,描述状态切换,这里的写法固定always @ ( posedge clk )begin if( !rst_n ) begin current_state <= IDLE; end else begin current_state <= next_state; end end //三段式FSM,第二段,组合逻辑,判断状态转移条件,描述状态转移规律//这里面用"="赋值和用"<="没区别always @ (*)begin if( !rst_n ) begin next_state <= IDLE; end else begin case( current_state ) IDLE: begin if( data_in == 1 ) next_state <= S1; else next_state <= IDLE; end S1 : begin if( data_in == 1 ) next_state <= S2; else next_state <= IDLE; end S2 : begin if( data_in == 0 ) next_state <= S3; else next_state <= S2; end S3 : begin if( data_in == 1 ) next_state <= S4; else next_state <= IDLE; end S4 : begin if( data_in == 1 ) next_state <= S2; else next_state <= IDLE; end default : begin next_state <= IDLE; end endcase end end //三段式FSM,第三段,同步时序逻辑,描述状态输出,摩尔型输出always @ ( posedge clk )begin if( !rst_n ) begin data_valid <= 1'b0; end else begin case( next_state ) S4 : data_valid <= 1'b1; default : data_valid <= 1'b0; endcase end end endmodule ``` # 序列生成器 序列产生器从高位输出, 反馈移位型和计数型两种。 **1.寄存器型,**可以按照该位宽输入该序列,然后,循环移位产生该序列信号。该方法使用**最多**的寄存器的方法实现,即输入该序列的,内部寄存该序列,寄存器的位宽等于输入位宽, ``````verilog module signal_generator_shifter_reg(clk, rst, din, dout); input clk, rst; input [5:0] din; output dout; reg dout; reg [5:0] temp; always@(posedge clk) begin if(rst == 1'b1) temp <= din; else begin dout <= temp[5]; temp <= {temp[4:0], temp[5]}; end endendmodule ``` 最少寄存器的数量,**如果想要节省资源,就需要对序列进行化简,看最少使用多少个触发器能够正确输出整个序列。** 序列化简的意义是看看使用最少寄存器实现寄存器的状态的转移 保证寄存器的首位输出合适的序列。如果序列1101011,普通的话用移位寄存器需要7位,化简:最少使用3个,110 - 101 - 010-101出现重复的,则不可以实现, 1101-1010-0101-1011-0110-1100-1000 1101-1010-0101-1011-0110-1100-1001 保证没有重复的寄存器跳转,。assign out = Q0; 1101- 1010- 0101- 1011- 0111- 1111- 1110 1101- 1010- 0101- 1011- 0111- 1110- 1100 也就是有4中方式实现, 然后化简逻辑表达式,按照Q4Q3Q2Q1的次态方程求出寄存器的表达式。 verilog 产生001011的序列检测器 按照001-010-101-011-110-100(Q2Q1Q0)循环计数,注意应该需要置位端 ```verilog module sequence( input clk; input rst_n; output dout; ); reg [2:0] state: wire [2:0] state1; always @(posedge clk or negedge rst_n) begin if(!rst_n) state <= 3'h1; else begin state[0] <= state[2]&state[1] |!state[2]&state[1]&!state[0]; state[1] <= !state[1]&state[0] |!state[2]&state[1]&state[0]; state[2] <= ((!state[2])&state[1]) | state[2]&state[1]&! state[0]; end end assign dout = state[2]; end module
2.计数器型
该方法可以使用最小的寄存器
根据序列长度,按照计数器的计数范围来产生序列,001011
则需要3位二进制数,来产生,即计数器000时产生0,001的时候产生0,计数010的时候,产生1,,可以化简得到计数器的每个寄存的相关的逻辑表达式,Q就是计数器中的缓存器,
module counter_sequence(clk, rst, dout); input clk, rst; output dout; reg [2:0] counter; always @(posedge clk) begin if(rst == 1'b1) counter <= 3'b000; else if(counter == 3'b101) counter <= 3'b000; else counter <= counter + 1'b1; end assign dout = ((~counter[0]) & counter[1]) | counter[2];endmodule
该计数方法试用于位数不太长的序列,需要化简逻辑表达式,。
1.环形计数器就是一个状态,循环移位,使用了n位也就是n个状态,所以没有使用的状态2^n-n状态,经过n个周期后状态变为原来的状态
直接使用移位寄存器的方式实现,最开始的数据一定是要有的。对于3位寄存器状态j移为001-010-100.不存在000状态使用verilog
output dout[]reg [2:0] cnt;always @(posedge clk) begin if(!rst_n) cnt <=1; else cnt <= {cnt[2:0],cnt[3]} end
扭环形计数器{约翰逊计数器}的状态转移其实对于n位的寄存器状态,经过n个时钟后其状态变为与原来相反。再经过n个周期后状态相同。所以使用了2n个状态,最少寄存器的话就是2个,可以自动实现扭环形计数器00-01-11-10
扭环形计数器的基本实现是将最高位取反后移位实现:
output dout[]reg [2:0] cnt;always @(posedge clk) begin if(!rst_n) cnt <=1; else cnt <= {cnt[2:0],!cnt[3]} end
LFSR,线性反馈移位寄存器(linear feedback shift register, LFSR)通常由动态或静态主从型触发器构成。反馈回路由异或门构成,特征多项式,CRC校验码,伪随机序列的生成,重要。该处好好理解,。LFSR性质完全由其反馈函数决定,状态周期最多2^n-1,使周期达到最大值的序列成为m序列,,通常用于在数字电路中产生伪随机数,
当抽头使用XOR门时,全0的模式不会出现。 当抽头使用XNOR门时,全1的模式将不会出现。 此两种情况LFSR将停止运行。LFSR是伪随机的。输出模式是确定性,可以通过了解XOR门的位置以及当前模式来确定下一个状态。
LFSR特性:
1.如果初始状态相同,则最终会得到相同的输出序列(即输出序列是确定的);估计就是用来输出伪随机序列的,初始状态称为种子
2.该电路通常可以表示为一个多项式G(x)=gnxn+ …+g1x1+g0;gn称为抽头。g0一定存在,当抽头不一样的时候,移位寄存器可用的状态也不一样,使用固定的抽头可以产生满状态,满状态也就是使用了全部的非0的状态,此时也就能 产生m序列
3。常有的类型有斐波那契型与伽罗瓦型,速度更快,因为寄存器之间的逻辑电路只有1个,需要画图出来
4.能达到最长序列的多项式不是唯一的,也就是说m序列也不是唯一的。
5.移位相加特性
一个m序列m1与其经任意延迟移位产生的另一序列m2模2相加,得到的仍是m1的某次延迟移位序列 m3,即m1与m2 异或为m3。
6.抽头只能选择0或者1,即有还是没有
m序列只用到了2^n-1个状态,没有使用全零状态
7.要使LFSR得到最长的周期,这个抽头序列构成的多项式加1必须是一个本原多项式(所有系数的最大公因数为1的多项式,互质),也就是说这个多项式不可约,如:
8.可以使用斐波那契与伽罗瓦同时实现两个lfsr
https://zhuanlan.zhihu.com/p/400841058
y=X8+X6+X^4+1为例子,
首位必须有反馈最高位与最低位一定有
1.数据流无论怎样都是从地位移位寄存到高位,D0->D1->……D8
2.数据都是移位到数据输出D8反馈到输入端
3.斐波那契的是对应Q的输出连续异或进入首位输入。Q8QQ4->Q1(从1开始)
伽罗瓦的是,最高位反馈到最低位,有输出的每一位的输出Q与移位寄存器的输出最高位异或进入下一位,
4.crc校验一般采用的是伽罗瓦型的,画图首先,输出端与输入异或进入输入,然后找到有输出的位异或进行输入。
模2运算:模2加法减法,不发生进位 的运算,其实相当于一种异或运算,模2乘法运算,乘法顺序不变,相加时依然不发生进位操作,偶数个相加
crc校验关键点是求余数
因为生成多项式的位数为5,根据前面的介绍,得知CRC校验码的位数为4(校验码的位数比生成多项式的位数少1)
crc有多项式,
CRC12 | x^12 + x^11 + x^3 + x^2 + x + 1 | 0x80F |
---|---|---|
CRC16 | x^16 + x^15 + x^2 + 1 | 0x8005 |
CRC16-CCITT | x^16 + x^12 + x^5 + 1 | 0x1021 |
CRC32 | x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11+ x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1 | 0x04C11DB7 |
crc12对应二进制表示有13位 最低位为100000001111,,分别对应多项式的系数。为除数
被除数为输入序列。串行crc每输入一位数据就会进行一位数据的crc检验,
步骤
1.crc12生成多项式的位数为13,其对应的CRC校验码的位数为12。在进行除法运算的时候,
由多项式写出verilog
2.crc校验的lfsr是有输入的 ,普通lfsr的没有输入。
3.M(x)是输入序列多项式,
1.首部校验和,计算首部校验和时,将首部校验和置0计算,计算得到超过16位,拆分后相加。接收端验证的时候,需要计算带有校验和的数据。同样的计算方法后得到的校验和为 0
2.FCS:frame check sequence 帧校验序列,以太网算法是CRC-32
3.以太网的crc检验采取校验全部数据的方式,每个数据检验完毕后作为下一个crc检验的初始值
4.每两个帧之间存在帧间隙IFG(Inter Frame Gap)或者说IPG(Inter-Packet Gap) ,这个时间正好相当于传送96bit数据的时间。也就是12个字节。
module crc8( input datain,//串行输入 output [7:0] dataout,//crc输出 input crc_start, input clk, input rst_n, output dout_valid ); parameter end_counter = 8; reg [2:0]cnt; reg [7:0] tmp_out; //计数器,用于计算有效数据 wire add_flag; wire end_flag; assign add_flag = crc_start; assign end_flag = add_flag && (cnt == end_counter -1); always@(posedge clk or negedge rst_n) begin if(!rst_n) cnt <= 0; else if(end_flag) cnt <= 0; else if(add_flag) cnt <= cnt+1; end always@(posedge clk or negedge rst_n) begin if(!rst_n) tmp_out <= 0; else begin tmp_out[0] <= datain ^ tmp_out[7]; tmp_out[1] <= datain ^ tmp_out[7]^tmp_out[0]; tmp_out[2] <= datain ^ tmp_out[7]^tmp_out[1]; tmp_out[3] <= tmp_out[2]; tmp_out[4] <= tmp_out[3]; tmp_out[5] <= tmp_out[4]; tmp_out[6] <= tmp_out[5]; tmp_out[7] <= tmp_out[6]; end end assign dataout = tmp_out; assign dout_valid = end_flag; endmodule
1 `timescale 1ns/1ps 2 module crc16_test ( 3 input wire i_clk , //时钟; 4 input wire i_rst_n , //同步复位; 5 input wire i_din_valid , //输入数据有效; 6 input wire [15:0] i_din , //输入数据; 7 output wire o_dout_valid , //输出CRC值有效; 8 output wire [15:0] o_dout //输出CRC; 9 ); 10 reg [15:0] r_dout; 11 wire [15:0] d; 12 wire [15:0] c; 13 assign d = i_din; 14 assign c = r_dout; 15 always @(posedge i_clk) begin 16 if (~i_rst_n) 17 r_dout <= 16'hffff; //初始值为ffff; 18 else if (i_din_valid) 19 begin //计算逻辑; 20 r_dout[0] = d[12] ^ d[11] ^ d[8] ^ d[4] ^ d[0] ^ c[0] ^ c[4] ^ c[8] ^ c[11] ^ c[12]; 21 r_dout[1] = d[13] ^ d[12] ^ d[9] ^ d[5] ^ d[1] ^ c[1] ^ c[5] ^ c[9] ^ c[12] ^ c[13]; 22 r_dout[2] = d[14] ^ d[13] ^ d[10] ^ d[6] ^ d[2] ^ c[2] ^ c[6] ^ c[10] ^ c[13] ^ c[14]; 23 r_dout[3] = d[15] ^ d[14] ^ d[11] ^ d[7] ^ d[3] ^ c[3] ^ c[7] ^ c[11] ^ c[14] ^ c[15]; 24 r_dout[4] = d[15] ^ d[12] ^ d[8] ^ d[4] ^ c[4] ^ c[8] ^ c[12] ^ c[15]; 25 r_dout[5] = d[13] ^ d[12] ^ d[11] ^ d[9] ^ d[8] ^ d[5] ^ d[4] ^ d[0] ^ c[0] ^ c[4] ^ c[5] ^ c[8] ^ c[9] ^ c[11] ^ c[12] ^ c[13]; 26 r_dout[6] = d[14] ^ d[13] ^ d[12] ^ d[10] ^ d[9] ^ d[6] ^ d[5] ^ d[1] ^ c[1] ^ c[5] ^ c[6] ^ c[9] ^ c[10] ^ c[12] ^ c[13] ^ c[14]; 27 r_dout[7] = d[15] ^ d[14] ^ d[13] ^ d[11] ^ d[10] ^ d[7] ^ d[6] ^ d[2] ^ c[2] ^ c[6] ^ c[7] ^ c[10] ^ c[11] ^ c[13] ^ c[14] ^ c[15]; 28 r_dout[8] = d[15] ^ d[14] ^ d[12] ^ d[11] ^ d[8] ^ d[7] ^ d[3] ^ c[3] ^ c[7] ^ c[8] ^ c[11] ^ c[12] ^ c[14] ^ c[15]; 29 r_dout[9] = d[15] ^ d[13] ^ d[12] ^ d[9] ^ d[8] ^ d[4] ^ c[4] ^ c[8] ^ c[9] ^ c[12] ^ c[13] ^ c[15]; 30 r_dout[10] = d[14] ^ d[13] ^ d[10] ^ d[9] ^ d[5] ^ c[5] ^ c[9] ^ c[10] ^ c[13] ^ c[14]; 31 r_dout[11] = d[15] ^ d[14] ^ d[11] ^ d[10] ^ d[6] ^ c[6] ^ c[10] ^ c[11] ^ c[14] ^ c[15]; 32 r_dout[12] = d[15] ^ d[8] ^ d[7] ^ d[4] ^ d[0] ^ c[0] ^ c[4] ^ c[7] ^ c[8] ^ c[15]; 33 r_dout[13] = d[9] ^ d[8] ^ d[5] ^ d[1] ^ c[1] ^ c[5] ^ c[8] ^ c[9]; 34 r_dout[14] = d[10] ^ d[9] ^ d[6] ^ d[2] ^ c[2] ^ c[6] ^ c[9] ^ c[10]; 35 r_dout[15] = d[11] ^ d[10] ^ d[7] ^ d[3] ^ c[3] ^ c[7] ^ c[10] ^ c[11]; 36 end 37 end 38 reg r_dout_valid = 0; 39 always @(posedge i_clk) //输入数据在一个时钟内完成CRC计算,下一个时钟输出; 40 begin 41 r_dout_valid <= i_din_valid; 42 end 43 44 assign o_dout_valid = r_dout_valid; 45 assign o_dout = r_dout ; 46 47 endmodule // end the crc16_test model;
cpld Complex Programmable Logic Device 复杂可编程逻辑器件
cmos 是Complementary Metal Oxide Semiconductor(互补金属氧化物半导体
fpga Field Programmable Gate Array 现场可编程门阵列
cdc: Clock Domain Crossing, 跨时钟域
ASIC:Application Specific Integrated Circuit-专用集成电路
BGA(Ball Grid Array-球栅阵列)
EDA(Electronic Design Automation-电子设计自动化
RTL: Register Transfer Level. 寄存器传输级描述)
DMA(Direct Memory Access直接内存访问)
DRAM(Dynamic Random-Access Memory. 动态随机访问存储器)
EEPROM(Electrically Erasable, Programmable Read-Only Memory.电可擦的,可编程的只读存储器)
PROM(Erasable, Programmable Read-Only Memory可擦的,可编程的只读存储器)
1.电路中有噪声出现的时候,多大范围内还能得到有效值
噪声容限必须是多个门电路之间的计算,单个门电路无法计算
VOH:维持输出为逻辑“1”的最小输出电压
VOL:维持输出为逻辑“0”的最大输出电压
VIH:仍能维持输入为逻辑 “0” 的最大输入电压
VIL 维持输入逻辑1的最小输入电路
VNH=VOH-VIH,前一级电路到后一级电路之间
高电平噪声容限=最小输出高电平电压-最小输入高电平电压
VNL=VIL-VOL
低电平噪声容限=最大输入低电平电压-最大输出低电平电压
噪声容限=min{高电平噪声容限,低电平噪声容限}
也就是说输出高电压的最小值也要比输入高电压的最小值要大
swd是arm公司研发,高速情况下更安全
电压水平,
与非门若接地或悬空会使输出始终为1。相当于输入0
将多余输入端接高电平,即通过限流电阻与电源相连接;
把多余输入端悬空(相当于有大电阻),输入端相当于外接高电平;
SARM、DRAM、rom:flash
SRAM 使用6晶体管实现,不需要动态刷新。缺点是晶体管太浪费
Dram使用电容实现,需要动态刷新,优势是节约晶体管,但是需要动态刷新
SRAM的结构,sr锁存器实现,就是触发器
FLASH属于广义上的ROM,和EEPROM的最大区别是FLASH按扇区操作,相对于EEPROM的改进就是擦除时不再以字节为单位,而是以块为单位,一次简化了电路,数据密度更高,降低了成本。上M的ROM一般都是FLASH。而EEPROM则按字节操作。目前Flsash主要有两种NOR Flash和NADN Flash
相关协议:
Parallel {NorFalsh |nandflash 工艺:SLC、MLC、TLC}
SPI{NOR NAND \flash 。}
eMMCFlash在NAND闪存芯片的基础上,额外集成了控制器,并将二者“打包”封装封成一颗BGA芯片,
USF2.0(1400MB,)
JEDEC即 固态技术协会是微电子产业的领导标准机构。
总结:
https://blog.csdn.net/weixin_42005205/article/details/80680272
总结:
有源滤波器顾名思义就是有电源,,在作用上最大的区别在于有源滤波器可以有增益,无源滤波器无增益是衰减的
Intel(Altera)的FPGA:-6/-7/-8,-6最快,-8最慢;
Xilinx的FPGA:-1/-2/-3,-1最慢,-3最快。
现在还有-1/-L1/-2/-L2/-3,L是低功耗Low power。
降采样相关定理
降采样这里就指定为信号抽取操作。降采样处理之前需先进行滤波,滤波的目的就是抗混叠。降采样意味着降低数据密度,
连续非周期----连续非周期
连续周期----离散非周期
离散非周期—连续周期
离线周期-----理算周期
1.当时序比较稳健 的时候,比如没有时钟偏移与抖动,这种时候可以使用同步电路复位
2.触发器一般带有异步复位,实现较简单
3.同步复位的复位信号一定要大于一个时钟周期,要不然会采集不到。
1.介绍:
到达统一门电路的时间不相同造成的时序竞争,
波形图上产生高频毛刺现象称为冒险,
2.举例:
例如同一信号进入同一门电路,一个有非门,一个没有,任何信号经过逻辑门都会产生一定的延时,所以会造成
竞争与冒险,
3.解决办法:
1.增加冗余项;Y = A’B + AC -->Y = A’B+AC+ BC
2.增加滤波电容
3.增加选通时钟
1.增加寄存器,打一拍数据
2.增加延时模块,使输入端同时到达电路
建立时间是始终上升沿来临前数据保持稳定的时间,这个时间保证了数据可以被寄存器采集到
保持时间是时钟上升沿来临之后数据保持稳定的时间,这个时间保证了数据可以被寄存器输出到q端
Tcq + Tcomb + Tsu <= Tclk + Tskew (1)Tsetup ≤ Tclk - Tco(max) - Tcomb(max)
最小时钟周期 = Tcq + Tcomb + Tsu - Tskew最快时钟频率 = 1 / (Tcq + Tcomb + Tsu - Tskew)
数据传输到下一级寄存器越快越好,要小于下一个周期时钟上升沿的到达要不然会造成建立时间的时候采集不到有效的数据
时钟skew越好对于建立时间越好,相当于建立时间保持了很久
时钟到来之后,数据还要稳定一段时间,这就要求前一级的数据延迟(data delay time)不要大于触发器的保持时间,以免数据被冲刷掉。则保持时间需要满足的表达式为:
Tcq + Tcomb >= Thd + Tskew (2)
数据的保持时间,越大越好,要大于等于数据保持时间的周期,
各个时间参数说明如下:
电路时许分析中,需要考虑建立时间与保持时间是否存在违例,需要计算前一级寄存器的输出时间到后一级寄存器的传输时间,一定要小于时钟到达的时间,
不能时钟到达了,数据还灭有到达,小于的时间差成为时间裕量
max (data path time) <= min (clock path time,时钟的周期+时钟延迟)min (data delay time) >= max (Thd + Tskew)
1.如果后一级的判断电路把低于VOL的电压判断为0,果后一级触发器把高于VOH的电压判断为1,那么在输入为VIL~VIH这个范围的电压产生的VOUT后一级的电路就不能判断当前是0还是1,有可能是0,也有可能是1,不能准确预测它的输出。
2.由于亚稳态的输出在稳定下来之前可能是毛刺、振荡、固定的某一电压值。亚稳态是在时钟边沿部分,输入的数据正好 发生变化,此时数据在阈值范围内,不一定输出的结果,
3.如果亚稳态超过一个或者两个周期,那么就会被下一个触发器采样到,这样就会造成亚稳态的传播
5.触发器与触发器之间尽量不要有组合逻辑,组合逻辑会增加输出的变化速度,如果没有组合逻辑相当于在一个时钟内数据不变化,组合逻辑会增加变化。
复位阶段不是问题,解复位才是问题。如果在触发器的活动时钟边缘或附近释放异步复位,则触发器的输出可能变为亚稳态,这样电路的复位状态可能会丢失,解复位失败。
1.亚稳态的原理,在上升沿的时候发生变化。不满足建立保持时间,
2.异步复位造成的亚稳态,异步复位同步释放
module reset_gen ( output rst_sync_n, input clk, rst_async_n); reg rst_s1, rst_s2; wire rst_sync_n ; always @ (posedge clk, posedge rst_async_n) if (rst_async_n) begin rst_s1 <= 1'b0; rst_s2 <= 1'b0; end else begin rst_s1 <= 1'b1; rst_s2 <= rst_s1; end assign rst_sync_n = rst_s2; //注意这里的rst_sync_n才是我们真正对系统输出的复位信号,该信号作为下一级触发器的复位端进行输入 endmodule
总结?:亚稳态真正处理的是防止在上升沿或者下降沿的时候,采样数据,这个时候的数据是不稳定的 ,有可能采集到1或者0,通过使用同步器延时,也就是当前时钟周期是边沿,那么下个时钟周期不是边沿,总有数据稳定的时候,这个时候采集数据,数据总会是稳定的,
波特率 = 比特率 x 调制一个码元所需要的比特数
1.对于串口来说,调制一个码元需要1bit所以波特率 = 比特
2.接收到的数据是从最低位开始的。对于保存的data数据移位形式为
data = {rx,data[7:1]};
3.对于波特率的生成取系统时钟,通过计数器延时实现。通常接收器的系统时钟是波特率的16倍,(或者64),baudate = sys_clk / baud(9600\ 115200),计数值是这个多的时候,只是统计了这个时钟总长度,要想实现该周期下的时钟clk,需要这个N/2的lk时候反转clk
波特率时钟使用16采样时,数据时钟还是原来的1/16,所以只是时钟发生频率发生了变化,但是取值的还是按照波特率执行。
CPOL和CPHA,空闲时sck信号为低则cpol为0 .空闲信号为高的时候的cpol为1
不同的cpha与cpol相位决定数据的传输方式,第一个奇数跳变沿采集数据,CPHA = 0;
时序图分析,
全双工通信,半双工通信,
组合逻辑
1.将最小项写出来
2.输入之间相与,多变量之间相互与
时序逻辑
1.一个触发器
利用mux组合其他电路,
mux 逻辑表达式:
1.1的数量。各位相加,统计0的个数就是用总位数减去1的个数,
2.最低1的位数:a&~(a-1)
3.最高1的位数
casez不一定能综合,需要换成别的方法实现 该算法,
奇校验意思是传输数据1的个数为奇数则校验位为0 ,否则为1
偶校验传输数据1个数为偶数则校验位为1,否则为1
奇偶检验的是,对序列中的数据进行数据检验,选择奇校验的时候,如果数据中的1的数量为奇数位,则奇偶检验位填1,对于数据中的 数据,首先需要统计数据中的1的个数。。。
实现方式:
1.统计数据中1的数量,计数器统计
2.异或运算。当然如果了解数据的异或运算,奇数个1异或结果为1,,偶数个1异或结果为0,然后根据奇校验还是偶校验给校验位赋值
1.双边沿。上升沿与下降沿同时检测
双边沿检测将时钟打一拍异或可以检测电路变化,适用于单位与多位电路。
2.单边沿,上升沿或者下降沿,
下降沿:取反后与原信号
always @(posedge clk)begin temp <= in;endassign capture = ~in & temp;
上升沿,后级电路脉冲还没到达,所以延后一点
assign capture = in & !temp;
verilog只能对单边沿信号敏感,如果想对双边沿电路敏感需要修改电路
如果只对双边沿电路进行敏感输出
1.mux
module top_module ( input clk, input d, output q); reg tmp1; reg tmp2; always @(posedge clk)begin tmp1 <= d;end always @(negedge clk)begin tmp2 <= d;end assign q = clk?tmp1:tmp2;endmodule
2.异或
1。查找表结构基于SRAM结构,sRAM中存储数据,对于4输入的lut ,4个输入接在了4个选择的en端用于区分信号,4输入的逻辑输入最多有16中状态,需要16的sram存储就行了 ,比如实现任意电路的逻辑。向该sram中存储想要的逻辑值就可以,
1.循环移位
2.补零移位(右)
q[3:0] <= {1'b0, q[3:1]};
对于串行输出电路,每一位的输出与前一位有关系的时候,需要设计线性移位寄存器。
这个与CRC并行实现方法有关系,。
需要考虑原码与补码的关系,
4位转8位
1100 ---- -4
11111100 -4
reg [3:0] original; //定义4位有符号数寄存器变量
reg [7:0] later; //定义8位有符号数寄存器变量
assign later = {{4{original[3]}}},original}; //高4位补original的符号位
1.右边的为无符号数,无论左边是什么类型的,高位都扩展成0
2.右边为有符号数,看右侧符号位,符号位为什么则高位就扩展成什么,这个情况下最多,一般右侧的有符号数已经是补码了,
3.有符号数转成无符号数的时候是会有错误的,尽量避免,
直接截断位数就好,
逻辑移位直接进行左移右移,都补0代替,算数移位左移相当于乘2,需要考虑溢出情况,算术右移需要考虑符号位,0补0 .1补1
组合逻辑用阻塞赋值,时序逻辑用非阻塞赋值,只有这样才能按照逻辑规则生成对应你的电路,
为什么要有阻塞与非阻塞,应为在逻辑电路中,往往是输入立马对应输出。有逻辑关系的电路也要立马输出对应的逻辑结果。所以阻塞赋值更好,
而时序电路中,往往寄存器之间的赋值都建立在时序基础上,从硬件电路上来说,寄存器的每次赋值都与本次时钟触发时刻有关系,所以需要时瞬时的状态
一行不会影响结果,
由于存在硬件逻辑结构限制,
1.vcs确实好用,将verilog编译成.C,然后使用编译可执行文件默认名称 simv. -o 选项可以指定文件输出名字 。可以使用makefile方式编译执行产生输出,-gui方式打开dve图形化界面 展示波形
2.fsdb文件是产生波形的文件,
3.synopsys收购了思源(台湾)eda公司,verdi是原公司思源的,我们使用Verdi的时候会产生novas.rc和novas.conf这两个临时文件也就能够解释了
4.vcs提供门级网表的仿真,可以仿真行为级也可以门级网表的仿真 ,也就是后仿真,一般使用来进行前仿
5.DC工具用于综合电路,将rtl综合成网表,综合生成的文件可以后续使用vcs继续仿真
verdi 可用于查看仿真过后的波形
命令行模式,
dxd 删除
yxy 选中p复制
ctrl+p自动补全
u撤销上一次操作ctrl+v选中,大写I。输入后esc后自动多行,
替换:%s/abc/ggg/gc abc替换成ggg /g全局 /gc全局询问
gg第一行,:50 到第50行。
o插入空行,相当于输入i后回车
x删除字符,相当于del
set nu 显示行号,设定之后,会在每一行的前缀显示该行的行号
:set nonu
1.模块参数化,一般情况下的模块参数化,其位数使用的是2的n次方实现,如果不写位数编译器默认32位,但是综合的时候会给出来警告,然后自动截断位数
2.不存在寄存器冗余情况,使用寄存器实现计数器的时候,每个寄存器都有其使用的时候,只是对于寄存器能表示的最大值没有使用到而已。
1.reg 类型的数据必须用always@进行赋值 。
2.不可以在两个以上always块中对同一个变量赋值
3.if else if 可用于优先判断条件,或者先决条件
4.case可用于多路选择器
5.case Z,敏感项中使用Z去判断,可以忽略掉无关数据的敏感项,如果检测多位数据中的最左边的1的位置
6.case使用往往需要敏感列表来实现,
7.在端口前面放& | ^ 表示按位处理该数据,归约运算符号
8.组合逻辑中使用非阻塞赋值没有意义一样综合成组合逻辑电路。
9.·define与c语言一样,简单的宏替换作用
10.置位段输入,需要考虑是一直输入还是,时序输入还是组合输入
11.if elseif else if有优先级,最上层if越接近后级电路,if if 也有优先级,越靠近后面,优先级越高,相反。case没有优先级,,
12.判断某位数据等于不等于,如果不相等后,取反就是相等
reg [width:0] mem[width:0] -----位宽为width的数组变量
reg [width:0] mem[0:width] ------一般为存储器ram\rom
可以使用User Defined Primitives用户定义原语来描述:
specify块:
模块路径延迟:在模块的源引脚和目标引脚之间的延迟;在关键字specify和endspecify之间给路径延迟赋值。
$display, $write, $strobe, m o n i t o r , d i s p l a y 与 c 的 p r i n t f 一样 w r i t e 可以自动换行, s t r o b e 是阻塞时执行, d i s p l a y 是非阻塞赋值 , monitor ,display与c的printf一样write可以自动换行,strobe是阻塞时执行,display是非阻塞赋值, monitor,display与c的printf一样write可以自动换行,strobe是阻塞时执行,display是非阻塞赋值,strobe 则是在其他语句执行完毕之后,变量赋值完成 以后,才执行显示,$monitor是用于检测任务,当任务中的变量发生变化了才输出显示,
fork-join并行块,块内语句同时执行,
:Verilog HDL 中提供initial块和always块这两种过程赋值块
verilog中的二维变量,reg[a:0]asd[B:0],B代表的最大深度,并不是位宽、
电路优先级编码
当所有判断条件有且仅有一个成立的情况下,我们就不需要优先级,因为结果也仅有一个,if else有优先级,条件不同的情况下,条件越靠前越靠近后级电路。
//以下电路优先级相同,综合电路完全一样 always@(*)begin if(sel == 0) dout = din[0*DSIZE+:DSIZE]; else if(sel==1) dout = din[1*DSIZE+:DSIZE]; else if(sel==2) dout = din[2*DSIZE+:DSIZE]; else if(sel==3) dout = din[3*DSIZE+:DSIZE]; end always@(*)begin case(sel) 0:begin dout = din[0*DSIZE+:DSIZE]; end 1:begin dout = din[1*DSIZE+:DSIZE]; end 2:begin dout = din[2*DSIZE+:DSIZE]; end 3:begin dout = din[3*DSIZE+:DSIZE]; end default:begin end endcase; end
21.verilog有大小端的模式。
reg[8:0] big big[0+:4] = big[3:0]
reg [0:8] little big[0+:4] = big [0:3]
模块输入输出端口例化位置固定。模块内部例化位置不固定
参数化传递参数 方式1
//模块module exam_prj #(parameter WIDTH=8) //端口内的参数只能在这使用 ( input [WIDTH-1:0] dataa,//[WIDTH-1:0] input [WIDTH-1:0] datab, output reg [WIDTH:0] result ); parameter Conuter_Top = 4'd9;//用于代码部分的参数 //代码部分省略endmodule//例化module exam_prj_tb; exam_prj//--------- #( .WIDTH(8), .Conuter_Top(4'd5) ) exam_prj_inst//------*注意例化时的名字在这个位置* ( .dataa(dataa), .datab(datab), .result(sum) );endmodule
参数化传递参数 方式2,不可以综合,可以在仿真的时候使用
//模块 module exam_prj ( input [WIDTH-1:0] dataa,//[WIDTH-1:0] input [WIDTH-1:0] datab, output reg [WIDTH:0] result ); parameter Conuter_Top = 4'd9;//用于代码部分的参数 //代码部分省略 endmodule //例化 module exam_prj_tb; defparam exam_prj_inst.Conuter_Top = x; exam_prj exam_prj_inst//------*注意例化时的名字在这个位置* ( .dataa(dataa), .datab(datab), .result(sum) ); endmodule
编程语言接口(PLI, Program Language Interface)
需要vcs在编译的时候指定tab表文件,然后文件中说名调用的文件函数。调用时使用$符号使用。
-P ../tb/pli_gyc.tab
1.万能makefile代码
1.tcl算数运算,需要结合[expr]
2.逻辑运算
if{条件}
{执行}
fork-join:程序块内的语句相对于父程序是阻塞执行的,程序块内语句需要全部执行完
fork-joinone:程序块内的语句相对于程序是非阻塞执行的,不管程序块内语句
fork-joinany:程序内的语句只要有一条执行完,解除阻塞。
1.一个run_test语句会创建一个my_driver的实例,并且会自动调用my_driver的main_phase。仔细观察run_test语句,会发现传递给
2.objection,driver中会写上raise_objection,如果有drop_objection,则执行此结束,没有的话立即结束。objection机制的主要功能是告诉验证平台何时开始一个phase,何时终止并进入到下一个phase,两者分别对应raise_objection和drop_objection。
3.类里面不能声明interface
4.config_db机制,set相当于寄信,get相当于写信,
5.'uvm_object_utils 与’uvm_compoent_utils,有区别,后者运行时间贯穿整个验证周期,前者用于注册transction,可以注册不同的tr,sequence,数据从产生到执行完成后就生命周期结束
6.sequencesr是uvm中的重要机制,用于产生transation的类型
7.uvm_filed_int\string\real是field automation机制,将散装变量进行打包成具有某种统一化功能的作用,分别对应int,real和string类型的变量。其中ARG参数为需要注册的变量名字,FLAG参数实际为一个17bit的数字,如果某个bit设置为1,则代表打开那个bit对应的功能,如copy,compare等等,若设置为UVM_ALL_ON,则其实是设置为17’b0000_0010_1010_101,代表打开copy,compare,print,record和pack功能。相当于linux文件下的文件权限操作。
8.config_db机制中,一般set与get相邻,但在某些情况下会有特例。当某个变量使用field_automation机制时,只要在build_phase中调用super.build_phase(),就可以省略对应的get语句,因为当执行到当前component的super.build_phase时,会自动执行get语句。这种做法有三个前提条件:
当前component必须使用uvm_component_utils注册。
该变量使用field_automation注册。
调用set函数时,set函数的第三个参数必须与(自动执行的)get函数的第四个参数一致。
9.uvm virtual函数用于实现多态的功能,通过virtual声明的类,接口,任务与函数,其本身自带一些方法或者函数。后续的例化或者扩展可以对原有的内容进行增加或者修改,从而实现同一函数不同方法的多种形态。用于OOP思想的继承使用。当当父类定义了virtual时,在子类中调用某task/function时,会先查找在子类中是否定义了该 task/function,如果子类没有定义,则在父类中查找。未定义virtual时,只在子类中查找,没有定义就是编译器报错。
如果某一class会被继承,则用户定义的task/function(除new(),randomized(),per_randomize(),pose_randomize()外),都应该加上virtual关键字,以备后续扩展。
10.task phase的运行机制类似fork join,即一个task phase内,多个component是并行执行的,当所有并行执行的线程结束,验证平台才会启动下一个task phase的并行执行。
task phase占用仿真时间,function phase不占用仿真时间,fnction phase是顺序执行的,task phase的并行执行。
11.phase机制、objection机制、sequencer机制
12.如driver,monitor,reference model等,内部通常都是包含需要无限循环执行的代码的,比如driver只要sequencer有传递数据,就需要将数据驱动到DUT中,因此这些模块不适合进行objection的控制——一旦无限循环,撤销objection很有可能根本不会被执行。
13.uvm_do_on宏用于显式地指定使用哪个sequencer发送此transaction,其参数形式为`uvm_do_on(SEQ_OR_ITEM,SEQR),第一个参数是transaction的指针,第二个是sequencer的指针。当使用uvm_do时,它实际等价于将uvm_do_on的第二个参数设置为了默认的sequencer,即此sequence启动时为其指定的sequencer。uvm_do_on_pri等在此基础上加上各关键词的功能,与uvm_do_pri与uvm_do之间的关系类似,因此uvm_do系列的宏本质上都是uvm_do_on_pri_with的特殊形式。
sh filename
与./filename
执行脚本是没有区别得。./filename
是因为当前目录没有在PATH中,所有”.”是用来表示当前目录的。sh filename
重新建立一个子shell,在子shell中执行脚本里面的语句,该子shell继承父shell的环境变量,但子shell新建的、改变的变量不会被带回父shell,除非使用export。source filename
:这个命令其实只是简单地读取脚本里面的语句依次在当前shell里面执行,没有建立新的子shell。那么脚本里面所有新建、改变变量的语句都会保存在当前shell里面。Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。