学习笔记:
1时序电路的优点:
1: 可以容忍组合逻辑电路产生的毛刺; 组合逻辑综合出来的电路可能因为路径不同导致延时不同,易出现毛刺;利用时钟沿可以等待输出数据稳定之后(因毛刺导致的错误输出结束之后),再将数据采入;
2: 纯组合逻辑只能由当前输入决定当前输出,无法实现带反馈的逻辑;否则就会出现死循环;这种带反馈的逻辑,只能先通过寄存器暂存起来,然后由时钟来控制输出的反馈和更新;
2异步复位:
就是将复位信号写在敏感列表中,代表我异步复位不受时钟的控制; * always @ (posedge clk or negedge rst_n)* 相反,不写代表同步复位,说明只有在时钟上升沿来临之时,我检查我的复位信号有没有效;
3:状态机部分知识:
分类:moore:状态机输出只与当前的状态有关;(状态多一点) mealy: 状态机输出和当前的状态以及当前输入有关;(判断输出时还需要考虑当前输入)
三段式状态机:
第一个逻辑块:
为组合逻辑,always@(*) ... case(c_state).. 描述状态转移条件;
第二个逻辑块:
为时序逻辑,是一个寄存器,将当前状态寄存,将n_state的值赋给c_state。也是时序电路的体现;
第三个逻辑块:
为组合逻辑,寄存器输出的状态c_state分为两路,一路送给第一个逻辑块进行下一状态的判断,另一路送给该块进行输出数据的判断;
两种状态机输出特性:
moore:在有限个门延时之后,输出会达到稳定,输出会在一个时钟周期内保持稳定,即使某关键信号在该时钟内发生变化,输出信号也不会发生变化。这是因为最后判断是用c_state,然而c_state被n_state赋值需要一个周期;分开输入和输出是该状态机的特点; mealy:因为输出受到当前输入的影响,然而输入随时可能变化,这就导致输出状态比moore快一个时钟周期,输入信号的噪声可能会出现在输出上;
注意事项:
每一种情况都要考虑,特别是case(c_state),要加default,否则可能生成锁存;
///////////////////////////////
FIFO篇
///////////////////////////////
双端口ram:
fifo是基于ram的, 设计实现一个16*8的双端口ram; 宽度8bit 深度16,addr4bit
module dp_ram(
input wr_clk,
input rd_clk,
input wr_allow,
input rd_allow,
input [ADDR_WIDTH -1:0]wr_addr,
input [ADDR_WIDTH -1:0]rd_addr,
input [RAM_WIDTH -1:0]wr_data,
output reg [RAM_WIDTH -1:0]rd_data
);
parameter RAM_WIDTH = 8;
parameter RAM_DEPTH = 16;
parameter ADDR_WIDTH = 4;
reg [RAM_WIDTH -1:0] memory [RAM_DEPTH -1 : 0];
always @ (posedge wr_clk )
begin
if(wr_allow)
memory[wr_addr] <= wr_data;
end
always @ (posedge rd_clk )
begin
if(rd_allow)
rd_data <= memory[rd_addr];
end
endmodule
注意ram的定义方式; reg [RAM_WIDTH -1:0] memory [RAM_DEPTH -1 : 0];
单端口ram
module ram(
input clk, //只有一个时钟,读写都用它
input rst_n,
input [7:0] addr,
input [15:0] w_data,
input wr_en, //1write 0read
output [15:0] r_data
);
reg[15:0] memory [255:0];
integar i;
always @ (posedge clk)
if(!rst_n)
for(i=0;i<255;i=i+1) //复位的时候给ram清零
mem[i] <= 0;
else if(wr_en)
mem[addr] <= w_data;
always @ (posedge clk)
if(!rst_n)
r_data <= 0;
else if(!wr_en)
r_data <= mem[addr];
双端口ram与单端口ram最大的区别就是:双端口可同时读写,都不耽搁; 单端口只能实现一个;
同步fifo:
- 先送入的数据也要先读出来;这是和ram不一样的地方;
- 原则:满不能写,空不能读;
- 关键:full和empty信号如何产生;
- 产生方法1:计数器:执行一次写,计数器加一;执行一次读,计数器减一;所有:cnt == depth(满了);
- 产生方法2:地址位扩展一位; 先试试第一种方法:
module syn_fifo
(
input fifo_rst,
input clk,
input read_enable,
input write_enable,
input [DATA_WIDTH -1 :0]wr_data,
output [DATA_WIDTH -1 :0]rd_data,
output reg full,
output reg empty,
output reg [ADDR_WIDTH -1 :0]cnt
);
parameter DATA_WIDTH = 8;
parameter ADDR_WIDTH = 9;
dp_ram dp_ram_1(
.wr_clk (clk),
.rd_clk (clk),
.wr_allow (wr_allow),
.rd_allow (rd_allow),
.wr_addr (wr_addr),
.rd_addr (rd_addr),
.wr_data (wr_data),
.rd_data (rd_data)
);
always@(posedge clk or posedge fifo_rst)
if(fifo_rst)
empty <= 1'b1; //上电之后肯定空的
else
empty <= (!write_enable && (cnt[8:1] == 8'd0) && ((cnt [0] ==0) ||read_enable));
//先看前面,如果地址位前八位都是0并且我还不写,进行下一个判断:如果最后一位是0,好了 空了,如果是1,说明还留了一个数据,但是此时我读请求,则为空;
//(!write_data && (cnt[8:1] == 8'b0) 当前计数器[8:1]全为0 并且此时我没有写入请求
//(cnt [0] ==0) || read_enable == 1)
// 0 0 : 此时(cnt [0] ==1) read_enable ==0 ;此时有一个数据 并且不把他读出来;
// 0 1 : 此时(cnt [0] ==1) read_enable ==1; 此时有一个数据 并且把他读出来;
// 1 0 : 此时(cnt [0] ==0) read_enable ==0 ;此时无数据 不读;
// 1 1 : 此时(cnt [0] ==0) read_enable ==1 ;此时无数据 要读;
always@(posedge clk or posedge fifo_rst)
if(fifo_rst)
full <= 1'b0; //上电之后肯定不满的
else
full <= (!read_enable && (cnt[8:1] == 8'hff) && ((cnt [0] ==1) ||write_enable));
//简而言之:我先看除了第一位地址位以外的其他位,如果当前所有地址位都为1(除了第一位),并且此时我不读,就进行下一个判断:如果最后一位也是1,不用看了,满了,如果最后一位不是1,但是此时我写入了,也是满了;
//(!read_enable && (cnt[8:1] == 8'hff) 前八位地址已满,最后一位暂时不考虑的情况下,我不读
//(cnt [0] ==1) ||write_enable == 1)
//0 0 cnt [0] ==0 write_enable == 0 地址最后一位为0,也不写入
//0 1 cnt [0] ==0 write_enable == 1 地址最后一位为0,但是写入数据
//1 0 cnt [0] ==1 write_enable == 0 地址最后一位为1,不写入
//1 1 cnt [0] ==1 write_enable == 1 地址最后一位为1,且继续写入
//读允许信号: 读请求且此时不空
wire rd_allow = (read_enable && !empty );
//写允许信号: 写请求且此时不满
wire wr_allow = (write_enable && !full );
reg [ADDR_WIDTH -1 :0]rd_addr;
reg [ADDR_WIDTH -1 :0]wr_addr;
//产生读写地址(在允许的情况下,读取地址自动加一)
always@(posedge clk or posedge fifo_rst)
if(fifo_rst)
rd_addr <= 0;
else if((rd_allow) && (!empty)) //其实不要!empty也行,前面已经判断过了 赘余了
rd_addr <= rd_addr + 1;
always@(posedge clk or posedge fifo_rst)
if(fifo_rst)
wr_addr <= 0;
else if((wr_allow) && (!full))
wr_addr <= wr_addr + 1;
//计数器
always@(posedge clk or posedge fifo_rst)
if(fifo_rst)
cnt <= 0;
else if( (!rd_allow && wr_allow) || (rd_allow && !wr_allow) ) //同时只存在一个动作
begin
//写一个数就+1 否则-1;
if(wr_allow)
cnt <= cnt + 1;
else
cnt <= cnt - 1;
end
endmodule
这是其中例化的双端口ram
module dp_ram(
input wr_clk,
input rd_clk,
input wr_allow,
input rd_allow,
input [ADDR_WIDTH -1:0]wr_addr,
input [ADDR_WIDTH -1:0]rd_addr,
input [RAM_WIDTH -1:0]wr_data,
output reg [RAM_WIDTH -1:0]rd_data
);
parameter RAM_WIDTH = 8;
parameter RAM_DEPTH = 512;
parameter ADDR_WIDTH =9;
reg [RAM_WIDTH -1:0] memory [RAM_DEPTH -1 : 0];
always @ (posedge wr_clk )
begin
if(wr_allow)
memory[wr_addr] <= wr_data;
end
always @ (posedge rd_clk )
begin
if(rd_allow)
rd_data <= memory[rd_addr];
end
endmodule
//例化的时候读写时钟都用了clk, 测试脚本:
`timescale 1ns / 1ps
module tb();
reg clk = 0;
always #5 clk = !clk;
parameter DATA_WIDTH = 8;
parameter ADDR_WIDTH = 9;
reg fifo_rst ;
//reg clk ;
reg read_enable ;
reg write_enable ;
reg [DATA_WIDTH -1 :0]wr_data ;
wire [DATA_WIDTH -1 :0]rd_data ;
wire full ;
wire empty ;
wire [ADDR_WIDTH -1 :0]cnt ;
syn_fifo syn_fifo_1(
.fifo_rst (fifo_rst),
.clk (clk),
.read_enable (read_enable),
.write_enable (write_enable),
.wr_data (wr_data),
.rd_data (rd_data),
. full (full),
. empty (empty),
. cnt (cnt)
);
initial
begin
fifo_rst <= 1; read_enable <= 0; write_enable <= 0; wr_data <= 0;
#10 fifo_rst <= 0; read_enable <= 0 ; write_enable <= 0; wr_data <= 0;
#10 read_enable <= 0; write_enable <= 1; wr_data <= 8'd2;
#10 read_enable <= 0; write_enable <= 1; wr_data <= 8'd3;
#10 read_enable <= 0; write_enable <= 1; wr_data <= 8'd4;
#10 read_enable <= 0; write_enable <= 1; wr_data <= 8'd5;
#10 read_enable <= 1; write_enable <= 0; wr_data <=0;
#10 read_enable <= 1; write_enable <= 0; wr_data <=0;
#10 read_enable <= 1; write_enable <= 0; wr_data <=0;
#10 read_enable <= 1; write_enable <= 0; wr_data <=0;
#10 read_enable <= 0; write_enable <= 0; wr_data <=0;
end
endmodule
写入2345后读出; 仿真波形:
读出数据是不是应该在前边给0呢;
第二种方法: 通过扩展地址位来判断空满; 这个方法真是简单多了:
module sync_fifo_2(
input clk,
input rst_n,
input write_enable,
input read_enable,
input [7:0] write_data,
output empty,
output full,
output reg[7:0] read_data
);
reg [7:0] mem [15:0];
wire [3:0] write_addr_a,read_addr_a;
wire [4:0] write_addr_e,read_addr_e;
assign w_addr_a = w_addr_e[3:0];
assign r_addr_a = r_addr_e[3:0];
always @ (posedge or negedge rst)
if (!rst_n)
r_addr_e <= 5'b0;
else
begin
if( empty == 0 && read_enable == 1)
begin
read_data <= mem[r_addr_a];
r_addr_e <= r_addr_e + 1;
end
always @ (posedge or negedge rst)
if (!rst_n)
w_addr_e <= 5'b0;
else
begin
if( full == 0 && write_enable == 1)
begin
mem[w_addr_a] <= write_data ;
w_addr_e <= w_addr_e + 1;
end
assign empty = ( r_addr_e == w_addr_e)? 1:0;
assign full = (r_addr_e[4] != w_addr_e[4] && r_addr_e[3:0] == w_addr_e[3:0] )? 1:0;
endmodule
前面真是费力不讨好;这种方法fifo的深度一定是2的n次方;
异步fifo:
- 读写时钟不一样;
- 使用扩展位进行空满判断;因为时钟不一样,产生的空满信号有跨时钟域问题,需要格雷码进行打一拍;
- 格雷码:每次只改变一位,这样产生亚稳态的可能就更小了;
- 要点:
写指针和读指针要分别放在不同时钟域中采样判断,易出现亚稳态;
2.为什么不用二进制编码进行空满判断?
格雷码编码的好处是每次只有1bit变化,这样有两种结果:
1)拿到目标的准确状态,符合期望;
2)拿到还没有更新的状态,但是假设现在采样读指针,最坏的情况就是把不满判断为满,这会使得写操作被禁止,但这样只会带来写入的延迟,不会有什么大的影响,写指针同理;不会对逻辑产生影响;
代码:
顶层模块:asyn_fifo: 读写part,ram,俩syn;
module asyn_fifo #(
parameter DEPTH = 256,
parameter WIDTH_A = 8,
parameter WIDTH_D = 16
)(
input w_clk,
input rst_n,
input w_req,
input [WIDTH_D-1:0] w_data,
input r_clk,
input r_req,
output [WIDTH_D-1:0] r_data,
output w_full ,
output r_empty
);
wire [WIDTH_A:0] w_addr;
wire [WIDTH_A:0] r_addr;
wire [WIDTH_A:0] r_gaddr_syn;
wire [WIDTH_A:0] w_gaddr_syn;
wire [WIDTH_A:0] w_gaddr;
wire [WIDTH_A:0] r_gaddr;
//写部分
write_part #(
.WIDTH_A(WIDTH_A)
)write_control(
.w_clk ( w_clk ),
.w_rst ( rst_n ),
.w_req ( w_req ),
.r_gaddr( r_gaddr_syn ),
.w_full ( w_full ),
.w_addr ( w_addr ),
.w_gaddr( w_gaddr )
);
//例化双端口ram
RAM #(
.DEPTH (DEPTH ),
.WIDTH_A(WIDTH_A),
.WIDTH_D(WIDTH_D)
)
RAM_inst
(
.r_clk ( r_clk ) ,
.w_clk ( w_clk ) ,
.rst_n ( rst_n ) ,
.w_addr( w_addr[WIDTH_A-1:0] ) ,
.w_data( w_data ) ,
.w_en ( w_req & (!w_full) ) ,
.r_addr( r_addr[WIDTH_A-1:0] ) ,
.r_en ( r_req & (!r_empty) ) ,
.r_data( r_data )
);
//例化了两个同步模块用来输出格雷码转换的地址
syn #(
.WIDTH_D(WIDTH_A)
)
syn_w_2_r
(
.syn_clk ( r_clk ) ,
.syn_rst ( rst_n ) ,
.data_in ( w_gaddr ) ,
.syn_data( w_gaddr_syn )
);
syn #(
.WIDTH_D(WIDTH_A)
)
syn_r_2_w
(
.syn_clk ( w_clk ) ,
.syn_rst ( rst_n ) ,
.data_in ( r_gaddr ),
.syn_data( r_gaddr_syn )
);
//读部分
read_part #(
.WIDTH_A(WIDTH_A)
)read_control(
.r_clk ( r_clk ),
.r_rst ( rst_n ),
.r_req ( r_req ),
.w_gaddr ( w_gaddr_syn ),
.r_empty ( r_empty ),
.r_addr ( r_addr ),
.r_gaddr ( r_gaddr )
);
endmodule
格雷码转换模块:格雷码与普通二进制编码不同点在于每次只变一位数;用处在后面cdc;
module bin_to_gray #(
parameter WIDTH_D = 5
)(
input [WIDTH_D-1:0] bin_c,
output [WIDTH_D-1:0] gray_c
);
//将最高一位取出
wire h_b;
assign h_b = bin_c[WIDTH_D-1];
//gray码的第n位 == 源码的第n位和第n+1位异或
//gray的第一位 == 源码的第一位
reg [WIDTH_D-2:0] gray_c_d;
integer i;
always @( * )
for( i=0;i<WIDTH_D-1;i=i+1 )
gray_c_d[i] = bin_c[i]^bin_c[i+1];
//拼出输出的gray码
assign gray_c = {h_b,gray_c_d};
endmodule
例化ram模块:前面提到的双端口ram
module RAM #(
parameter DEPTH = 256,
parameter WIDTH_A = 8,
parameter WIDTH_D = 16
)(
input r_clk,
input w_clk,
input rst_n,
input [WIDTH_A-1:0] w_addr,
input [WIDTH_D-1:0] w_data,
input w_en,
input [WIDTH_A-1:0] r_addr,
input r_en,
output reg[WIDTH_D-1:0] r_data
);
//定义一个ram
reg [WIDTH_D -1:0] mem[0:DEPTH-1];
//ram初始值全部赋零
//如果有写申请,将数据写入特定地址中
integer i;
always @( posedge w_clk )
if( !rst_n )
for(i=0;i<DEPTH;i=i+1)
mem[i] <= 'h0000;
else if( w_en )
mem[w_addr] <= w_data;
//如果有读申请,将特定地址中数据读出
always @( posedge r_clk )
if( !rst_n )
r_data <= 'h0000;
else if( r_en )
r_data <= mem[r_addr];
endmodule
读部分:
module read_part #(
parameter WIDTH_A = 8
)(
input r_clk,
input r_rst,
input r_req,
input [WIDTH_A:0] w_gaddr,
output r_empty ,
output reg[WIDTH_A:0] r_addr ,
output reg [WIDTH_A:0] r_gaddr
);
//在不空的情况下,如果有读请求,就将读地址+1
always @( posedge r_clk )
if( !r_rst )
r_addr <= 'h00;
else if( r_req&&(!r_empty) )
r_addr <= r_addr + 1'b1;
//例化gray码转换模块
wire [WIDTH_A:0] r_gaddr_w;
bin_to_gray #(
.WIDTH_D(WIDTH_A+1)
)
bin_to_gray_inst
(
.bin_c ( r_addr ),
.gray_c( r_gaddr_w )
);
//将经过gray模块转换的地址打一拍输出
always @( posedge r_clk )
if( !rst_n )
r_gaddr <= 'h0;
else
r_gaddr <= r_gaddr_w;
// 当输入的写地址(经过gray码转换)与读地址相同时,输出空标志
assign r_empty = (w_gaddr==r_gaddr_w)?1'b1:1'b0;
endmodule
同步模块:将接收到的数据打两拍输出,避免亚稳态
module syn #(
parameter WIDTH_D = 5
)
(
input syn_clk,
input syn_rst,
input [WIDTH_D:0] data_in,
output [WIDTH_D:0] syn_data
);
reg [WIDTH_D:0] syn_reg_1,syn_reg_2;
//将输入数据在当前时钟域下打两拍然后输出
always @( posedge syn_clk )
if( !syn_rst ) begin
syn_reg_1 <= 'h00;
syn_reg_2 <= 'h00;
end
else begin
syn_reg_1 <= data_in;
syn_reg_2 <= syn_reg_1;
end
assign syn_data = syn_reg_2;
endmodule
写部分:
module write_part #(
parameter WIDTH_A = 8
)(
input w_clk,
input w_rst,
input w_req,
input [WIDTH_A:0] r_gaddr,
output w_full ,
output reg[WIDTH_A:0] w_addr ,
output reg [WIDTH_A:0] w_gaddr
);
//当前有写请求并且当前不空时:写地址+1
reg [4:0] w_addr;
always @( posedge w_clk )
if( !w_rst )
w_addr <= 'h00;
else if( w_req&&(!w_full) )
w_addr <= w_addr + 1'b1;
//将输出地址输入到gray码转换模块
wire [WIDTH_A:0] w_gaddr_w;
bin_to_gray #(
.WIDTH_D(WIDTH_A+1)
)
bin_to_gray_inst
(
.bin_c ( w_addr ),
.gray_c( w_gaddr_w )
);
//将gray转换模块输出信号打一拍后输出
always @( posedge w_clk )
if( !rst_n )
w_gaddr <= 'h0;
else
w_gaddr <= w_gaddr_w;
//当gray输出写地址的前两位取反,后两位不变,和读地址相比较,相等则说明满;
assign w_full = ({~w_gaddr_w[WIDTH_A],~w_gaddr_w[WIDTH_A-1],w_gaddr_w[WIDTH_A-2:0]}==r_gaddr)?1'b1:1'b0;
endmodule
测试脚本:
`timescale 1ns/1ps
module asyn_fifo_tb;
logic w_clk ;
logic w_req ;
logic [7:0] w_data;
logic r_clk;
logic rst_n;
logic r_req;
logic [7:0] r_data;
logic w_full;
logic r_empty;
always #2 w_clk = ~w_clk;
always #8 r_clk = ~r_clk;
initial begin
w_req = 0;
w_data = 0;
r_req = 0;
w_clk = 0;
r_clk = 0;
rst_n = 0;
#30;
rst_n = 1;
#20;
r_req = 1;
w_req = 1;
forever begin
@( posedge w_clk )
if( !w_full )
w_data = w_data + 1'b1;
end
end
asyn_fifo #(
.DEPTH (256),
.WIDTH_A (8 ),
.WIDTH_D (8 )
)
asyn_fifo_inst(
.w_clk ( w_clk ),
.rst_n ( rst_n ),
.w_req ( w_req ),
.w_data ( w_data ),
.r_clk ( r_clk ),
.r_req ( r_req ),
.r_data ( r_data ),
.w_full ( w_full ),
.r_empty( r_empty )
);
endmodule
典型的跨时钟域会出现的问题
metastability: 亚稳态问题;
- 什么是亚稳态? 电路中不所期望的状态,比如说触发器只有01两种状态,但是在特定情况下会盘旋在两者之间很长时间(无法确定是0还是1),并且还会传播; sta只能检测同步电路,异步电路得使用spyglass做cdc检测;
- 什么情况会造成亚稳态? 建立保持时间违例的时候; 如图所示,当tx信号建立保持时间违例的时候,rx会出现介于0和1之间的不定态,几个周期后才趋于稳定;
虚线左右是两个时钟域;
- 如何解决单bit的跨时钟域问题? 答:在当前时钟域两级同步;在接受的时钟域打两拍;
另:功能仿真(行为级仿真)无法看到亚稳态效应,只有后仿才能看到;
reconvergence 如何解决多bit的跨时钟域问题
- 如果依旧使用单bit策略,消耗的资源太多;这里我们使用交互信号en。
当en == 1 输入值更新。
当en == 0 保持原值不变。
- 如果我们采用每个bit打两拍的方式,还存在一种可能;
简单来说就是由于电路通过逻辑电路的时间不同,导致多bit数据中间的数据到达clkb时钟域的时间不同,例如途中恰好x在clkb时钟上升沿之前,而Y在时钟上升沿之后,这就导致x领先了y一个clkb; 这样的数据势必是错误的; 解决方法:使用gray码编码,这样同时只有一位数据位发生变化,就不会出现这种情况;
data hold问题:快慢时钟问题
如果源时钟域时钟快,目的时钟域慢(x倍),就可能出现这种情况,即原时钟持续一个周期的信号,等不到目的时钟的上升沿就拉平了,导致检测不到;这样怎么解决?
也就是说拓宽源数据宽度x倍;图中是两倍,也就是
D2 = D | D1;
问题:如果目的时钟是原时钟的四倍,且传输的是低电平信号,电路怎么改? 答: 或门变与门,即有0为0,且与门改为四输入与门;
fifo handshake问题
也就是需要跨时钟域传输大量数据的时候,不可能每个都打两拍,我们可以规定一个交互信号来帮助; 比如握手信号中的req和ack;异步fifo中的地址;也即是上边说过的;
reset syn问题
异步复位时,进行异步复位,同步释放;优点;不会出现时钟违例;
输出复位信号的时候打两拍;
注意事项: sta无法解决跨时钟域问题,修复时钟树也不行;
上一个时钟域的输出最后必须打一拍送入下个时钟域,不能直接组合逻辑输出给下个时钟域;
fpga知识层次结构
层次定义: 1.仿真层:在软件上模拟真实环境,产生模拟输入,检测设计的正确性。 2.器件层; 以d触发器为核心,满足d触发器建立保持时间的各种理论和规则; 3.模块层 围绕“模块”的功能,在模块内实现多种多样的功能; 4.架构层; 使用“模块层”的模块,通过互联,相互配合等,实现FPGA的整体功能; 5.系统层 根据fpga,arm,dsp,上位机等器件的优势和劣势,将产品分配到不同器件实现,以及确认器件间的配合方式; 6.功能层 以人可理解的语言,定义出该产品的功能;
数字集成电路静态时序分析
sta核心就是确定每个触发器的建立时间和保持时间不违例; sta是对于同步电路而言的,不能出现时钟违例,但是对于异步电路,还是有些专门的区别的; 问题:触发器-逻辑电路-触发器模型? 保持时间出现违例会出现什么结果;
tcl语言学习
tcl解释器运用规则把命令分成一个个独立的单词,同时进行必要的置换; 置换分为三类:变量值换$,命令置换[]斜杠置换;
tcl语言控制流:
if: 注意:{要写在上一行;
foreach循环指令;
循环控制指令break:
循环控制指令:continue
循环控制指令:while
循环指令for
过程函数:
proc: 全局变量和局部变量
正则匹配:
- 正则表达式是一种特数的字符串模式,用来去匹配符合规则的字符串;
- 正则表达式的 \w ,用来匹配一个字母、数字、下划线;
- 正则表达式的\d 用来匹配一个数字,如字符串abc123,应该是\w\w\w\d\d\d; 简化:
-
- 零次或多次匹配;
-
- 一次或多次匹配;
- ?零次或者一次匹配; 则可以表示为 \w+\d+ or \w*\d*
- 锚位: ^符串开头,$字符串结尾 ^\d\d\d or \d\d\d$;
- 空格\s; 123空格abc空格123 == 表达式:\d+\s\w+\s\d+
- 任意字符:点. xxx空格xxx空格xxx == .+\s.+\s.+
正则匹配指令:regexp
例题: 简而言之就是把想要捕获的位置前后都用表示符表示出来,再将捕获的位置用括号圈起来; tcl文本处理; 三大类:open gets close 举例如下:
最后一个小例题;
tcl学习与应用: 第一部分: tcl在eda工具中的扩展和应用,基于synopsys工具的扩展tcl语言; (手册:Using Tcl With Synopsys Tools) 第二部分:使用tcl控制eda工具流程; 第一部分:(以下都是在dc的脚本模式下输入的)
第二部分:使用tcl语言来控制流程
primetime的库文件和时序模型; 时序弧:描述两个节点之间的延时; 分为连线延时和单元延时; 连线延时:单元的输出端口和扇出网路负载的信息 单元延时:单元的输入端口到单元的输出端口延时; 单元延迟:计算:1 transition time 1 transition time 0到1,1到0; 2 logic gate delay 就是该逻辑单元从输入端口到输出端口的延时;
建立时间和保持时间; 时序路径概念:起点,终点; 起点:可以是clockpin或者input port; 终点:可以是d触发器的输入端或者是output port; 时钟域:全局异步,局部同步; pvt:工艺,电压,温度与delay; 、 标准单元库:
非线性延迟模型: timing arc ;两个因素transition time 和out load;
threshold spec;slew derating;
sta学习 STA环境: 如何书写sdc约束文件; 环境:有以下几个重要元素:setting up clock,IO timing ,false paths,muticycle paths. 首先;时钟沿; 对于一些时钟还可以指定uncertainty,包括setup hold,如果在严苛的条件下电路仍然没有问题说明电路更加稳健; 时钟 uncertainty 包括:set up /hold/clock skew/clock delay/clock jitter;
时钟延迟分为两种; 在时钟树建立之前和建立之后LATENCY有什么区别; 生成时钟: 注意图中二分频电路的实现; 注意MASTER时钟和generated 时钟的定义和区别;
约束input & output path
output 也是类似的
DRC:design rule check;
未完。。。