目录
- 1.IC基础
- 2.手撕代码
- 2.1 异步fifo
- 2.2 同步fifo
- 2.3 除法器(小米)
- 2.4 乘法器
- 2.5 串行加法器
- 2.6 监沿器
- 2.7 输入消抖
- 2.8 去毛刺(大疆)
- 2.9 计数器
- 2.10 无毛刺切换
- 2.11 移位寄存器
- 2.12 奇分频
- 2.13 偶分频
- 2.14 序列检查
- 2.15 脉冲展宽
- 2.16 T触发器
- 2.17 奇偶校验
- 2.18 移位与乘法
- 2.19 generate 写法
- 2.20 子模块法做大小比较
- 2.21 函数的使用
- 2.22 超前进位加法算法的实现
- 2.23 casex 的使用
- 2.24 全减器
- 2.25 脉冲同步(大疆)
- 2.26 读状态转移表
- 2.27 rom的实现
- 2.28 有符号数的加减(VIVO)
- 2.29 信号发生器
- 2.30 自动贩卖机(经典)
- 2.31 格雷码计数器
- 2.32 DMUX
- 2.33 握手
- 3.电路知识
- 4.刷刷刷刷
- 4.1 晶体管用于放大作用,工作在什么区?
- 4.2 CMOS管用于开关作用,工作在什么区?
- 4.3 设计一个序列发生器,以CLK为控制信号,输出序列为0010110111
- 4.4 共阳数码管(如图示)的显示译码器,当输入A3A2A1A0为0101时,输出为多少
- 4.5 5个具有计数功能的T触发器链接,输入脉冲频率为256KHZ, 则此计数器最高位触发器的输出脉冲频率为多少
- 4.6 什么是BCD码计数器?在4个触发器构成的 8421BCD码计数器中,具有几个无关状态?
- 4.7 若传感器的输出分辨率为1mV,ADC的电源为5V,基准电压为2.5V,为保证ADC的采样精度,ADC的位数至少为多少?
- 4.8 分析下图所示电路,设各触发器初始状态为0,算出Q2n+1的方程
- 4.9 测量高压电路电流时,应将电流表接到接近哪一端?
- 4.10 对一个异步fifo,列出你能想到的所有测试点?
- 4.11 只使用(2选1MUX)完成异或门逻辑,最少需要多少个MUX
- 4.12 时序检查中对于异步复位电路的时序分析分别是?
- 4.13 关于于网表仿真描述正确的是?
- 4.14 N位触发器构成的扭环形计数器,其无关状态数有几个?
- 4.15 什么结构化描述?行为描述?数据流描述?
- 4.16 为实现D触发器转换成T触发器,图示的虚线框内应是什么电路?
- 4.17 有一个FIFO设计,输入时钟100Mhz,输出时钟80Mhz,输入数据模式是固定的,其中1000个时钟中有800个时钟传输连续数据,另外200个空闲,请问为了避免FIFO下溢/上溢,最小深度是多少
- 4.18 在四变量卡诺图中,逻辑上不相邻的一组最小项为:( )
- 4.19 3个D触发器构成的电路图如下,Q2端的输出是什么?
- 4.20 一个8进3出的优先编码器,如果1、3、4、5输入端为有效电平,其二进制输出为?
- 4.21 Verilog 里的取模运算:Verilog 中 10%(-3) 的结果是多少?
- 4.22 Verilog有符号数的计算
- 4.23 三目运算有x和z态怎么判断
- 4.24 rom和ram的计算
- 4.25 NAND flash和NOR flash
- 4.26 JK触发器状态
- 4.27 fifo深度计算
- 4.28 浮点数的无损计算(大疆)
- 4.29 FIFO对前后级的握手信号如何产生?
- 4.30 为什么异步fifo可以进行跨时钟域处理?
- 4.31 解释input delay和 output delay的含义
- 4.32 如果芯片已经生产出来,发现setup time或者hold time有违例,怎么办?能补救吗?
- 4.33 FIFO会不会存在假空假满的情况呢?
- 4.34 时序路径的终点和起点?
- 4.34 FPGA 不同bank之间有什么不同?
- 4.35 如果有一段突发数据,需要将其转为稳定的数据流,怎么做?
- 4.36 除了人肉看波形外,还有什么验证正确性的办法?
- 4.37 AHB仲裁是怎么工作的?sel信号怎么产生?
- 4.38 Ready信号有延迟怎么办?
- 4.39 fifo的空满怎么判断?
- 4.40 多个乘法运算会出现时序问题?怎么解决?
- 4.41 什么是伪路径?
- 4.42 一个always块中能否用同时用阻塞和非阻塞赋值?
- 4.43 在rtl设计中哪项工作需要手工进行门级设计?
- 4.44 STA是在哪个级进行的?
- 4.45 CMOS管的原理?PMOS的衬底连接的是什么?
- 4.46 CRC的计算?
- 4.47 AHB特性?
- 4.48 Verilog与其他编程语言有哪几种接口机制?
- 4.49 UPF
- 4.50 PPA
1.IC基础
1.1 锁存器触发器结构
①锁存器
我们定义:
①Q = 1,且Q’ = 0 为锁存器的1状态
②Q = 0,且Q’ = 1 为锁存器的0状态
③Q为现在的状态,Q*为下一个状态,Q’为Q反
其真值表及功能如下:
②触发器
触发器与锁存器的不同在于,它除了置1置0输人端以外,又加了一个触发信号输入,只有当触发信号到来时,触发器才能按照输人的置1、置0信号置成相应的状态,并保持下去,我们将这个触发信号称为时钟信号,记作CLK。
下面讲讲触发器的几种类型:
-
SR触发器:Q* = S + R’Q
-
JK触发器:Q* = JQ’ + K’Q
-
T触发器:Q* = TQ’ + T’Q
-
D触发器:Q* = D
1.2 建立保持时间
- 建立时间:指输入信号应当先于时钟信号clk到达的时间
在D触发器中,为了保证触发器有效的翻转,在clk改变前FF1中的Q1的状态必须稳定的建立起来,使得Q1 = D;而这个先来的时间就叫做建立时间;至少 tsu = 2 td 。
- td为触发器的电路延迟时间;
- 保持时间是指时钟信号clk到达以后,输入信号需要保持不变的时间;至少 th = 2 td
- 传输延迟时间是指从clk动作沿开始,直到输出新状态稳定建立做需要的时间;至少 tpd = 5td
1.3 STA
-
STA——静态时序分析
-
对建立时间进行分析:
①从触发器DFF1到DFF2,Tco为触发器内部延时,Tcmb为组合逻辑延时,在D2处还有需要满足建立时间Tset
②这里有两条路劲,一个是数据路径data_path,一个则是时钟路径timing_path
③为满足建立时间,data_path需要比timing_path跑得更快因此有:
Tco + Tcmb + Tset < clk2 - clk1 = T
④这是在没有时钟偏移Tskew下的不等式,而考虑悲观的Tskew,则data_path需要更前的时间到来,有:
Tco + Tcmb + Tset < T - Tskew
⑤在上面的式子中,Tco 和Tset 以及Tskew都是不能再改变的了,因此能改变的只剩下了Tcmb 和T ,这也是为什么T越大,越能满足建立时间的原因
⑥我们把式子移一下 Tcmb < T - Tskew - Tco - Tset ,综合工具做的逻辑优化就是优化这个Tcmb ,使得Tcmb 满足式子
- 对保持时间进行分析
①和建立时间不一样的是,在采集数据时,是以同一时钟触发来采数据的,因此路径上的延时只有Tco 以及Tcmb
②那么对于保持时间来说,路径上的延时是越迟越好的,因此有:
Tco + Tcmb > Thold
③当存在时钟偏移Tskew时,考虑悲观的情况,时钟偏移让我的延时减少,有:
Tco + Tcmb - Tskew > Thold ,换一下位就是:
Tcmb > Thold - Tco + Tskew - 时钟树综合:目的就是为了优化Tskew,使得距离较远的两个DFF之间的Tskew做到很小
- cell延时:信号从低电平(10%)到高电平(90%)转换所要的时间
- wire延时:金属连线的延时跟单位长度的寄生电容和寄生电阻成正比,布局布线后,要是一根连线很长,延时很大,可以通过插入多个buff来降低单位长度的寄生电容和寄生电阻,从而减小延时
- 时序违例怎么办?时序违例可以分为两种情况,分别为建立时间违例和保持时间违例
①对于建立时间违例,可以采用以下方法:
1)降低频率,但是频率一般不允许有太大的改动
2)工艺升级,cell延时会变小
3)从组合逻辑延时下手,可以采用流水线(pipline)、优化电路设计
4)调整Tskew,使得Tskew对建立时间有利
5)mos管阈值越小,其延时越小,因此可以在关键路径采用阈值小的单元
②对于保持时间违例,可以采用以下方法:
1)插buffer,使得组合逻辑延时增大
2)调整Tskew,使得其对保持时间是有利的
1.4 CDC
- CDC(clock domain crossing) :跨时钟域
- 同步时钟域是指时钟频率和相位具有一定关系的时钟域,并非一定只有频率和相位相同的时钟才是同步时钟域
- 异步时钟域的两个时钟则没有任何关系
假设数据由clk1传向clk2
①单bit传输时,同步时钟域因为频率和相位关系都是已知的,可以推导,所以不需要采用额外的硬件电路就可以解决CDC问题,只需要源数据在clk1端保持足够长时间即可。
让其保持足够长时间有两个好处:即便出现亚稳态,也可以在两个clk2时钟周期后数据变得稳定下来,从而采到正确的结果;还可以防止低频采高频时,因为频率跟不上而导致数据丢失。
②单bit传输时,异步时钟域的传输就必须使用额外的电路模块(同步器)来保证数据正确的传输。最基本的同步器是双锁存结构的电平同步器,其余的同步器都是由其衍生而来。该同步器的基本原理,也是让数据至少在clk2的时钟下保存两个周期,消除亚稳态。当然同步器能解决异步时钟域的同步问题,自然也可以拿来解决同步时钟域的问题,毕竟同步时钟域更简单一些。
③实际的电路设计中,不用管那么多细节,不管是同步时钟域还是异步时钟域,只要是不同的时钟之间传数据,就加上同步器的结构,这当然是一种偷懒的解决办法。脉冲同步器就是这么一种万能的结构,对于单bit跨时钟域传输而言,使用脉冲同步器就够了,不需要区分时钟有没有关系,也不需要区分是高频采低频还是低频采高频,毕竟也很少有人能掌握这么全的细节
④对于多bit传输,不能采用单bit传输的方法。原因在于,单bit传输时,不能确定该数据到底经过1个clk2时钟周期之后有效还是两个clk2时钟周期之后才有效。所以对多个bit各自采用单bit的同步机制,会导致输出一些错误的中间状态。对于多bit传输,可以使用握手信号或者异步fifo
⑤异步fifo这里不做多少,手撕代码的时候自然见分晓
⑥握手:保持寄存器+握手信号,也就是先异步暂存,后同步写入。所谓握手,就是通信双方使用了专门控制信号进行状态指示,这些控制信号是双向的。(在边缘检测的综合项目中有用到,用于数据对齐)
⑦DMUX方法
多路选择器+触发器实现,MUX相当于触发器的使能信号,当两个时钟域同步时,打开使能接收数据
1.5 亚稳态怎么解决
- 首先什么是亚稳态呢?
指触发器无法在某个规定时间段内达到一个可确认的状态,这种无用的输出电平可以沿信号通道上的各个触发器级联式传播下去 - 亚稳态产生的原因?
①跨时钟信号
②时钟偏移超过容限
③组合延时太长
以上所造成的根本的问题就是触发器的建立保持时间不满足 - 解决办法?
①首先就是做好CDC,具体可以看上面的CDC章节
②使用采样更快的触发器
③降低采样的频率
1.6 低功耗
-
基于CMOS管的低功耗分析
-
首先功耗来源于两个大部分:动态和静态
-
动态:又分为开关功耗和短路功耗
-
开关功耗:我们知道电容是会被电源充放电的,所以在翻转的过程中会有负载电容充电的功耗,叫做开关功耗
-
短路功耗:在翻转的过程中,存在一段很短的时间t,使得CMOS正负导通,形成电流,叫做短路功耗,这也就是为什么沿变得快的时钟可以降低功耗的道理
-
那么动态功耗的公式为:
其中:
CL ------电路总负载电容
Vdd ------工作电压
Ptran------工作电路所占比例
F------工作时钟频率
ttran------上下MOS管同时导通时间
Ipeak------短路电流 -
而静态功耗则是MOS管上的漏电功耗,相对于动态功耗来说差了几个数量级
-
降低功耗的方法:
①clock gating 门控时钟 ,在时钟端加一个使能
②power gating ,控制电源的关断
③再非关键的路径用HVT,高阈值电压,使得漏电流更小(这个可以在时序要求比较宽松的时候用)
④DVFS,动态电压频率调节
1.7 竞争冒险
- 竞争: 两个输入信号同时向相反逻辑电平跳变。通俗的说,就是两个输入,一个从1变0,同时另一个从0变1。
- 竞争-冒险:由于竞争在电路输出端可能产生尖峰脉冲的现象。
- 判断准则:只要输出端的逻辑函数在一定条件下能简化成Y=A+A’或Y=A·A’则可判定存在竞争-冒险现象
注意:有竞争不一定产生尖峰脉冲,只有在存在不同步的跳变时,某输入先跳变而另一输入还未跳变时产生尖峰脉冲。
- 消除竞争冒险的方法
①滤波电容:因为尖峰脉冲很窄,用很小的电容就能削弱尖峰到Vth一下,但由于电容会增加电压波形的上升时间和下降时间,会破坏原有波形。
②引入选通脉冲:利用选通脉冲,在电路达到稳定后,P的高电平期间的输出信号不会出现尖峰。(就是再加一个区别信号来防止尖峰出现)
③修改逻辑设计:防止Y=A+A’或Y=A·A’的出现(并不是万能的,办法有限,但效果很好)
1.8 毛刺
- 毛刺就是上面我们说的竞争冒险所产生的尖峰脉冲
- FPGA器件内部,信号通过连线和逻辑单元时,都有一定的延时,延时的大小与连线的长短和逻辑单元的数目有关,同时还受器件的制造工艺、工作电压、温度等条件的影响
- 信号的高低电平转换也需要一定的过渡时间,由于存在这两方面因素,多路信号的电平值发生变化时,在信号变化的瞬间,组合逻辑的输出有先后顺序,并不是同时变化,往往会出现一些不正确的尖峰信号,这些尖峰信号称为"毛刺"
- 具体的去毛刺电路可以看手撕代码2.8的题目
1.9 IC设计流程
1. 项目需求
包括:
①工艺、面积、封装
②频率、功耗
③功能、接口
2. 前端设计
- RTL代码设计:寄存器级描述
- 功能验证:也叫动态仿真,如用Modelsim / VCS对设计的模块进行验证
- 逻辑综合:也叫DC,作用是得到综合后的门级网表Netlist
- STA:静态时序分析,若满足约束,得到最终的Netlist
- 形式验证:也叫静态验证,保证在逻辑综合过程中没有改变原先RTL电路功能
- DFT:可测性设计,测试芯片制作有无缺陷,一般是在电路中插入扫描链
3.后端设计
- 布局布线
- 提取延迟信息
- 再次STA
- 版图物理验证
- 流片
1.10 补码、原码、反码
- 原码 :最高位符号位,0代表正数,1代表负数,非符号位为该数字绝对值的二进制
- 反码:正数的反码与原码一致,负数的反码是对原码按位取反,而最高位(符号位)不变
- 补码:正数的补码与原码一致,负数的补码是该数的反码加1
举个例子:
①数字 5
原码:00000101
反码:00000101
补码:00000101
②数字 -5
原码:10000101
反码:11111010
补码:11111011
注意:符号位是不用变的
1.11 格雷码、独热码
-
格雷码:在一组数的编码中,若任意两个相邻的代码只有一位二进制数不同,则称这种编码为格雷码
如2位的格雷码:00 01 11 10
-
独热码:独热编码即 One-Hot 编码,又称一位有效编码,其方法是使用N位状态寄存器来对N个状态进行编码,每个状态都由他独立的寄存器位,并且在任意时候,其中只有一位有效
如3状态的状态机采用的独热码:S0=3'b001 S1=3'b010 S2=3'b100
-
格雷码的优缺:格雷码属于可靠性编码,是一种错误最小化的编码方式;比如十进制的3转换到4时二进制码的每一位都要变,使数字电路产生很大的尖峰电流脉冲。而格雷码则没有这一缺点,它在任意两个相邻的数之间转换时,只有一个数位发生变化;但是格雷码编码使用最少的触发器,消耗较多的组合逻辑
-
独热码的优缺:独热码编码的最大优势在于状态比较时仅仅需要比较一个位,从而一定程度上简化了译码逻辑;虽然在需要表示同样的状态数时,独热编码占用较多的位,也就是消耗较多的触发器,但这些额外触发器占用的面积可与译码电路省下来的面积相抵消,也就是说组合逻辑用的少了,但是触发器用的多了
-
格雷码与二进制码的转化:
①二进制转格雷码:二进制码整体右移一位,然后与二进制码本身进行异或,得到该二进制码对应的格雷码
assign gray = (bin >>1) ^ bin;
②格雷码转二进制:最高位相同,二进制码的最高位与格雷码的次高位相异或
assign bin[N-1] = gray[N-1]; //最高位相同
genvar i;
generate
for(i = N-2; i >= 0; i = i - 1) begin: gray2bin
assign bin[i] = bin[i + 1] ^ gray[i];
end
endgenerate
1.12 fifo深度
- 一切fifo的深度问题都归咎于一点:最坏条件下所需要存储的数据量
- 相当于一边发数据,一边收数据,其中有速度差(发 > 收),为了使得数据不丢失则需要引入缓冲器对还没来得及收的数据进行缓存
- 那么缓存器需要存几个数据就是其深度
- 就相当于一个水池一边进水一边放水,进水速度快的时候,需要多高的池子才能保证水不会溢出
- 具体的题目可以看4.17 和 4.27
1.13 二进制小数转换
- 10进制数101.25,转换为2进制数为?
101转成二进制就不用多说了,短除法,度数从下往上读,看下图:非小数部分则为1100101
而小数部分则是 x2 取整处理,然后小数部分继续x2
因此答案是1100101.01
- 上面的题反过来呢?怎么用二进制小数化成10进制?
-1次方、-2次方的形式
如:
1.14 操作符优先级
第一级:
第二级:
第三级:
第四级:
第五级:
第六级:
第七级:
第八级:
之后逐级降低优先级:
第十五级:
第十六级:
1.15 multicycle(多周期路径)
- 两级寄存器之间有复杂的组合逻辑,导致此处的组合逻辑延迟可能超过一个时钟周期
- 按照两级寄存器的时序分析原理,这里一定会报时序错误
- 但是有时候我们采集此信号未必需要在发射沿的下一个时钟周期采样,我们可能需要经过多个时钟周期采集数据,这就是multicycle
- 在多周期设置中,Setup的周期是X,设置Hold周期为X-1,以保证hold check维持在同一时钟,否则极容易slack violated
1.16 parameter 、define 、local parame
- parameter和local parameter定义的变量在module内有效
- parameter可以进行参数传递
- 而local parameter不能进行参数传递
- define定义的变量,写在模块名称上面,在整个设计工程都有效,可以跨模块的定义
2.手撕代码
2.1 异步fifo
- 异步fifo怎么设计?
(1)双口 RAM 存储数据------------------------------二维数组或者用ip例化
(2)同步读数据指针到写时钟域--------------------转格雷码再打两拍
(3)同步写数据指针到读时钟域--------------------转格雷码再打两拍
(4)处理写指针和满信号的逻辑--------------------根据读写地址判断
(5)处理读指针和空信号的逻辑--------------------根据读写地址判断 - 先讲一下我们需要例化的ram模块
①读端:时钟、使能、地址、数据 经典四要素
②写端:时钟、使能、地址、数据 经典四要素
③再ram模块中生成存储的语句:reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
④意思是生成WIDTH宽度数据的存储单元DEPTH个
⑤打个比方reg [7:0] RAM_MEM [0:15]; 表示生成8位宽度数据的存储单元16个,而16就是我们所说的深度
⑥这个模块的作用就是给数据和地址就能进行读和写的操作
module dual_port_RAM #(parameter DEPTH = 16,
parameter WIDTH = 8)
(
input wclk
,input wenc
,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽
,input [WIDTH-1:0] wdata
,input rclk
,input renc
,input [$clog2(DEPTH)-1:0] raddr
,output reg [WIDTH-1:0] rdata
);
reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
always @(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata;
end
always @(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr];
end
endmodule
-
接着就是异步fifo模块
-
在其中例化了存储单元ram模块之后,我们还要在内部定义地址,而地址分为:
①给到ram的地址waddr、raddr ,位宽是ADDR_WIDTH-1
②二进制计数地址waddr_bin、raddr_bin,位宽拓展一位,为ADDR_WIDTH,方便用来判断空满
③由二进制计数地址转化的格雷码地址waddr_gray、raddr_gray,用来进行跨时钟处理
④有格雷码地址打两拍的地址waddr_gray_ff1、raddr_gray_ff1,用来进行跨时钟处理
⑤由打完两拍的格雷码地址再转化而成的同步后的二进制地址waddr_gray2bin、raddr_gray2bin,用来进行fifo空满的判断
-
由fifo的空满决定读写使能
①读空判断:读地址和写地址一样时,则为读空
②写满判断:因为扩展了一位,所以最高位为1时表示读写地址刚好相差一圈,这时就是写满的时候;见下表,假设深度为8,那么扩展后是4位;从data1再次写到data1时,则为写满,从data2再次写到data2时,也为写满,以此类推;很明显写满的时候,最高位相反,其他位一样,也就是上面说的正好相差一圈,用代码表示就是:
assign wfull = (waddr == {~raddr[3],raddr[2:0]});
-
总结:fifo的设计要点就是 存数据的ram + 格雷码打两拍跨时钟 + fifo的空满判断
-
代码:
`timescale 1ns/1ns
/***************************************RAM*****************************************/
module dual_port_RAM #(parameter DEPTH = 16,
parameter WIDTH = 8)(
input wclk
,input wenc
,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
,input [WIDTH-1:0] wdata //数据写入
,input rclk
,input renc
,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
,output reg [WIDTH-1:0] rdata //数据输出
);
reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
always @(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata;
end
always @(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr];
end
endmodule
/***************************************AFIFO*****************************************/
module asyn_fifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input wclk ,
input rclk ,
input wrstn ,
input rrstn ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output wire wfull ,
output wire rempty ,
output wire [WIDTH-1:0] rdata
);
parameter ADDR_WIDTH = $clog2(DEPTH);
//internal sig
//二进制扩展移位的读写地址
reg [ADDR_WIDTH:0] waddr_bin;
reg [ADDR_WIDTH:0] raddr_bin;
//二进制打1拍 与 后面跨时钟后生成的二进制对齐
reg [ADDR_WIDTH:0] waddr_bin_ff0;
reg [ADDR_WIDTH:0] raddr_bin_ff0;
//格雷码扩展一位的读写地址
reg [ADDR_WIDTH:0] waddr_gray;
reg [ADDR_WIDTH:0] raddr_gray;
//格雷码打两拍的读写地址
reg [ADDR_WIDTH:0] waddr_gray_ff0;
reg [ADDR_WIDTH:0] waddr_gray_ff1;
reg [ADDR_WIDTH:0] raddr_gray_ff0;
reg [ADDR_WIDTH:0] raddr_gray_ff1;
//打完拍后再转二进制进行空满判断
wire [ADDR_WIDTH:0] waddr_gray2bin;
wire [ADDR_WIDTH:0] raddr_gray2bin;
//读写使能
wire wen ;
wire ren ;
//最后给到ram的地址 保持真实位宽地址
wire [ADDR_WIDTH-1:0] waddr;
wire [ADDR_WIDTH-1:0] raddr;
//-----------------------------------
//功能语句
//-----------------------------------
//例化ram作为存储
dual_port_RAM #(.DEPTH(DEPTH),
.WIDTH(WIDTH)
)
dual_port_RAM
(
.wclk (wclk),
.wenc (wen),
.waddr(waddr[ADDR_WIDTH-1:0]),
.wdata(wdata),
.rclk (rclk),
.renc (ren),
.raddr(raddr[ADDR_WIDTH-1:0]),
.rdata(rdata)
);
//二进制打3拍
always @(posedge wclk or negedge wrstn) begin
if(~wrstn) begin
waddr_bin_ff0 <= 'd0;
end
else begin
waddr_bin_ff0 <= waddr_bin;
end
end
always @(posedge rclk or negedge rrstn) begin
if(~rrstn) begin
raddr_bin_ff0 <= 'd0;
end
else begin
raddr_bin_ff0 <= raddr_bin;
end
end
//读写使能
assign wen = winc & !wfull;
assign ren = rinc & !rempty;
//真是位宽地址 给到ram
assign waddr = waddr_bin[ADDR_WIDTH-1:0];
assign raddr = raddr_bin[ADDR_WIDTH-1:0];
//地址++ 再收到读写使能后 进行地址++
always @(posedge wclk or negedge wrstn) begin
if(~wrstn) begin
waddr_bin <= 'd0;
end
else if(wen)begin
waddr_bin <= waddr_bin + 1'd1;
end
end
always @(posedge rclk or negedge rrstn) begin
if(~rrstn) begin
raddr_bin <= 'd0;
end
else if(ren)begin
raddr_bin <= raddr_bin + 1'd1;
end
end
//二进制转格雷码
always @(posedge wclk or negedge wrstn) begin
if(~wrstn) begin
waddr_gray <= 'd0;
end
else begin
waddr_gray <= waddr_bin ^ (waddr_bin>>1);
end
end
always @(posedge rclk or negedge rrstn) begin
if(~rrstn) begin
raddr_gray <= 'd0;
end
else begin
raddr_gray <= raddr_bin ^ (raddr_bin>>1);
end
end
//交叉时钟下打两拍得到ff0 ff1
always @(posedge rclk or negedge rrstn) begin
if(~rrstn) begin
waddr_gray_ff0 <= 'd0;
waddr_gray_ff1 <= 'd0;
end
else begin
waddr_gray_ff0 <= waddr_gray;
waddr_gray_ff1 <= waddr_gray_ff0;
end
end
always @(posedge wclk or negedge wrstn) begin
if(~wrstn) begin
raddr_gray_ff0 <= 'd0;
raddr_gray_ff1 <= 'd0;
end
else begin
raddr_gray_ff0 <= raddr_gray;
raddr_gray_ff1 <= raddr_gray_ff0;
end
end
//再转二进制码
assign waddr_gray2bin[ADDR_WIDTH] = waddr_gray_ff1[ADDR_WIDTH]; //最高位相同
genvar i;
generate
for(i = ADDR_WIDTH-1; i >= 0; i = i - 1) begin: gray2bin_w
assign waddr_gray2bin[i] = waddr_gray2bin[i + 1] ^ waddr_gray_ff1[i];
end
endgenerate
assign raddr_gray2bin[ADDR_WIDTH] = raddr_gray_ff1[ADDR_WIDTH]; //最高位相同
genvar j;
generate
for(j = ADDR_WIDTH-1; j >= 0; j = j - 1) begin: gray2bin_r
assign raddr_gray2bin[j] = raddr_gray2bin[j + 1] ^ raddr_gray_ff1[j];
end
endgenerate
//读空的判断
assign rempty = (raddr_bin_ff0 == waddr_gray2bin);
//写满的判断
assign wfull = (waddr_bin_ff0 == {~raddr_gray2bin[ADDR_WIDTH],raddr_gray2bin[ADDR_WIDTH-1:0]});
endmodule
2.2 同步fifo
- 同步fifo比异步fifo简单,不需要进行跨时钟的处理,直接存数据的ram + fifo的空满判断即可
- 空满判断需要我们自己来定义地址,详细可见上面的异步fifo,在掌握了异步fifo后同步的就是小菜一碟
- 代码:
`timescale 1ns/1ns
/**********************************RAM************************************/
module dual_port_RAM #(parameter DEPTH = 16,
parameter WIDTH = 8)(
input wclk
,input wenc
,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
,input [WIDTH-1:0] wdata //数据写入
,input rclk
,input renc
,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
,output reg [WIDTH-1:0] rdata //数据输出
);
reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
always @(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata;
end
always @(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr];
end
endmodule
/**********************************SFIFO************************************/
module sfifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input clk ,
input rst_n ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output reg wfull ,
output reg rempty ,
output wire [WIDTH-1:0] rdata
);
parameter addr_width = $clog2(DEPTH);
reg [addr_width:0] waddr_pd;
reg [addr_width:0] raddr_pd;
wire wen;
wire ren;
assign wen = (winc & (!wfull));
assign ren = (rinc & (!rempty));
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
waddr_pd <= 0;
else if(wen)
waddr_pd <= waddr_pd + 1'b1;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
raddr_pd <= 0;
else if(ren)
raddr_pd <= raddr_pd + 1'b1;
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wfull <= 'd0;
rempty <= 'd0;
end
else begin
wfull <= (waddr_pd == {~raddr_pd[addr_width],raddr_pd[addr_width-1:0]});
rempty <= (raddr_pd == waddr_pd);
end
end
dual_port_RAM u1
(
.wclk(clk),
.wenc(wen),
.waddr(waddr_pd[addr_width-1:0]),
.wdata(wdata),
.rclk(clk),
.renc(ren),
.raddr(raddr_pd[addr_width-1:0]),
.rdata(rdata)
);
endmodule
2.3 除法器(小米)
5位的A和3位的B,计算C = A / B
- 这种方法要自己慢慢推算二进制除法
- 很巧妙,就记住步骤吧
- temp_a = temp_a-temp_b+1; 在末尾+1表示商为1,不加则是为0
- temp_a 要延展的位数为c的宽度,因为c就是商且在temp_a 的低16
module chu
(
input wire[15:0] a,
input wire [ 7:0] b,
output wire [15:0] c
);
reg [15:0] a_reg;
reg [7:0] b_reg;
reg [31:0] temp_a;
reg [31:0] temp_b;
integer i;
always@(*)begin
a_reg =a;
b_reg= b;
end
always@(*)begin
temp_a ={16'h0,a_reg} ;
temp_b={b_reg ,16'h0 } ;
for(i =0;i<16;i=i+1)begin
temp_a = temp_a <<1;
if (temp_a >= temp_b)begin
temp_a = temp_a-temp_b+1;
end
else begin
temp_a = temp_a;
end
end
end
assign c = temp_a[15:0];
endmodule
2.4 乘法器
实现4bit无符号数流水线乘法器设计
- 思路:将乘法转化为加法
- 用拼接的方法求得LV1 ~ LV4
- 再用两级加法进行求和
`timescale 1ns/1ns
module multi_pipe#(
parameter size = 4
)(
input clk ,
input rst_n ,
input [size-1:0] mul_a ,
input [size-1:0] mul_b ,
output reg [size*2-1:0] mul_out
);
wire [7:0] lv1;
wire [7:0] lv2;
wire [7:0] lv3;
wire [7:0] lv4;
reg [7:0] add1;
reg [7:0] add2;
assign lv1 = mul_b[0]?{4'b0,mul_a }:8'b0;
assign lv2 = mul_b[1]?{3'b0,mul_a,1'b0}:8'b0;
assign lv3 = mul_b[2]?{2'b0,mul_a,2'b0}:8'b0;
assign lv4 = mul_b[3]?{1'b0,mul_a,3'b0}:8'b0;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
begin
add1 <= 0;
add2 <= 0;
mul_out <= 0;
end
else
begin
add1 <= lv1 + lv2;
add2 <= lv3 + lv4;
mul_out <= add1 + add2;
end
end
endmodule
- 当位数更多的时候,考虑使用generate产生LV信号
genvar i;
generate
for(i = 0; i < size; i = i + 1)begin : loop
assign temp[i] = mul_b[i] ? mul_a << i : 'd0;
end
endgenerate
2.5 串行加法器
- 由全加器例化4个模块得来
- 代码略 不难
2.6 监沿器
- 监沿器用来鉴定上升沿或下降沿
- 具体实现:前一刻时钟与现时刻时钟比较
- 信号经打一拍处理后存为前一刻信号,即比原信号慢了一个时钟的信号,因此可以根据两者的比较检测出上升沿或者下降沿。当现时刻为1,前时刻为0时,说明从上个时钟的0,变为这个时钟的1,判断出上升沿,同理可以判断出下降沿。具体代码如下:
module identify_tool
(
input clk,
input rst_n,
input signal,
output wire pose,
output wire nege
);
//内部信号
reg signal_before;
//前一刻信号值寄存
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
signal_before <= 0;
end
else begin
signal_before <= signal ;
end
end
//上升下降判断
assign nege = signal==0 && signal_before==1;
assign pose = signal==1 && signal_before==0;
endmodule
2.7 输入消抖
- 按键按下的时候会有10ms左右的抖动,因此按键按下的时候需要做输入消抖处理
- 处理的方法:检测到按下----计数----到达10ms----进入稳定状态------检测到松开----计数-----到达10ms-----进入稳定状态
module prevent_jitter
(
input clk,
input rst_n,
input key_in,
output reg key_out
);
reg key_in0;
reg [19:0] cnt;
wire change;
parameter jitter=20'd1000000;
// ff0
always@(posedge clk)
if(!rst_n)
key_in0<=0;
else
key_in0<=key_in;
// up and down
assign change=(key_in & !key_in0)|(!key_in & key_in0);
// cnt
always@(posedge clk)
if(!rst_n)
cnt<=0;
else if(change) cnt<=0;
else cnt<=cnt+1;
// key_out
always@(posedge clk)
if(!rst_n)
key_out<=1;
else if(cnt==jitter-1)
key_out<=key_in;
endmodule
2.8 去毛刺(大疆)
设计一个电路,使用时序逻辑对一个单bit信号进行毛刺滤除操作,高电平或者低电平宽带小于4个时钟周期的为毛刺
- 目的:将高低电平时间小于4T的信号滤除
- 思路:边沿检测加上计数器,上升或者下降沿一旦检测到,计数器清零并开始计数,若是计数到3则为有效信号并停止计数,否则是无效信号
module filter
(
input clk,
input rst_n,
input in,
output out
);
reg in_ff0;
reg in_ff1;
wire pose;
wire nege;
reg [1:0] cnt;
reg out;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
begin
in_ff0 <= 0;
in_ff1 <= 0;
end
else
begin
in_ff0 <= in;
in_ff1 <= in_ff0;
end
end
assign pose = ~in_ff1 && in_ff0;
assign nege = in_ff1 && ~in_ff0;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 0;
else if (pose || nege)
cnt <= 0;
else
cnt <= cnt + 1;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
out <= 0;
else if(cnt == 'd3)
out <= in_ff1;
end
endmodule
- dve仿真:
2.9 计数器
写一个十六进制计数器模块,计数器输出信号递增每次到达0,给出指示信号zero,当置位信号set 有效时,将当前输出置为输入的数值set_num
- 16进制计数
- 判断zero的产生
- 注意zero和number数据对齐,这里先缓存计数值,再用计数值判断并输出zero和number
- 代码:
`timescale 1ns/1ns
module count_module(
input clk,
input rst_n,
input set,
input [3:0] set_num,
output reg [3:0]number,
output reg zero
);
reg [3:0] number_cnt;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
number_cnt <= 0;
else if(set)
number_cnt <= set_num;
else
number_cnt <= number_cnt + 1;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
number <= 0;
else
number <= number_cnt;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
zero <= 0;
else if(number_cnt == 0)
zero <= 1;
else
zero <= 0;
end
endmodule
2.10 无毛刺切换
- 时钟切换的组合电路
assign outclk = (clk1 & select) | (~select & clk0);
①当sel为1时,输出clk1;当sel为0时,输出clk0
②这个电路中存在一个致命的问题,就是切换时存在毛刺,时序如下图:
- 为了解决这个问题,在时钟端引入一个下降沿D触发器
插入一个下降沿触发的D触发器,可以确保在切换时钟源时,即使时钟正处在高电平,也不会影响输出变化
上图中,当sel发生翻转,out_clk不会立马变化,而是等到原时钟下降沿来临后,再等到次时钟下降沿才开始实现整个切换
module glitch (
input clk0,
input clk1,
input select,
input rst_n,
output clkout
);
reg out1;
reg out0;
always @(negedge clk1 or negedge rst_n)begin
if(rst_n == 1'b0)begin
out1 <= 0;
end
else begin
out1 <= ~out0 & select;
end
end
always @(negedge clk0 or negedge rst_n)begin
if(rst_n == 1'b0)begin
out0 <= 0;
end
else begin
out0 <= ~select & ~out1;
end
end
assign clkout = (out1 & clk1) | (out0 & clk0);
endmodule
2.11 移位寄存器
- 移位寄存器经常用在并串转换中,可以参考2.14的序列检查
2.12 奇分频
-
奇数分频比偶数分频要复杂一点,奇数分频的翻转点在0.5个时钟处,上升沿并不能检测到此处,因此我们需要用到上升沿计数以及下降沿计数;
-
一个时钟,然后两个计数器分别采样上升沿和下降沿,然后利用偶数分频的办法,得到两个中间时钟,这两个时钟周期为2N+1,N个周期的高电平,N+1个周期的低电平,然后利用两个中间时钟进行相或操作得到奇数分频
-
做法:对于一个2N+1分频,用两个计数器分别采样上升沿和下降沿个数,计数到2N+1(代码中是2N,因为计算机从0开始数),用两个计数器产生两个时钟,在N+1和2N+1计数处翻转,得到的两个时钟相或,得出2N+1分频时钟
module div_5
(
input clk,
input rst_n,
output wire clk_div
);
//参数
parameter N = 2'd2;
//内部信号
reg [2:0] cnt_pose;
reg [2:0] cnt_nege;
reg clk_pose;
reg clk_nege;
//功能块
always@(posedge clk or negedge rst_n)begin // 对上升沿计数
if(!rst_n)
cnt_pose <= 0;
else if(cnt_pose == 2*N)
cnt_pose <= 0;
else
cnt_pose <= cnt_pose + 1;
end
always@(posedge clk or negedge rst_n)begin // 中间时钟clk_pose
if(!rst_n)
clk_pose<=0;
else if(cnt_pose == N||cnt_pose == 2*N)
clk_pose <= ~clk_pose;
else
clk_pose <= clk_pose;
end
always@(negedge clk or negedge rst_n)begin // 对下降沿计数
if(!rst_n)
cnt_nege <= 0;
else if(cnt_nege == 2*N)
cnt_nege <= 0;
else
cnt_nege <= cnt_nege + 1;
end
always@(negedge clk or negedge rst_n)begin // 中间时钟clk_nege
if(!rst_n)
clk_nege <= 0;
else if(cnt_nege == N||cnt_nege == 2*N)
clk_nege <= ~clk_nege;
else
clk_nege <= clk_nege;
end
assign clk_div = clk_pose|clk_nege; // 2N+1分频时钟输出
endmodule
2.13 偶分频
- 偶数分频,是最简单的分频。举个例子,以100分频为例,我们需要用100个原始的时钟周期T,作为我们分频后新的时钟周期T1,也就是T1 =
100T;那么时钟的占空比都是50%,所以很明显,在50T的时候翻转时钟就行; - 利用计数器可以对原始时钟的周期个数的进行计数,在计数到50T,也就是49的时候,新时钟进行反转,得到100分频时钟;
- 进而得到规律:实现N分频(N为偶数),只需要在计数到N/2-1时翻转新的时钟信号即可;
- 下面是一个10分频的代码,因此计数到4时时钟翻转:
module div_10
(
input clk,
input rst_n,
output reg clk_div
);
//参数
parameter N = 4'd10;
//内部信号
reg [3:0] cnt;
//功能块
always@(posedge clk or negedge rst_n)begin
if (!rst_n)begin
cnt <= 0;
end
else if(cnt == N - 1 ) begin
cnt <= 0;
end
else begin
cnt <= cnt + 1;
end
end
always@(posedge clk or negedge rst_n)begin
if (!rst_n) begin
clk_div <= 0;
end
else if (cnt <= (N/2)-1) begin
clk_div <= 1;
end
else begin
clk_div <= 0;
end
end
endmodule
2.14 序列检查
设计一个序列检测器,将码流中的“10010”序列检测出来:
①三个输入,clk,rst_n,x,其中x是一位的输入,由x传输的多个数据构成码流
②输出z,在检测到完整的10010序列时,z拉高
用移位寄存器的方法比状态机的方法代码要轻简很多,先说一下原理:
- 先定义一个5位的变量用来移位——shift = 5’b00000;
- 然后每输入一位x,移位放至shift中——shift <= {shift[3:0], x};
module check_num_shift
(
input clk,
input rst_n,
input x,
output wire z
);
reg [4:0] shift;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
shift <= 0;
else
shift <= {shift[3:0],x};
end
assign z = (shift == 5'b10010)?1:0;
endmodule
2.15 脉冲展宽
对1bit的脉冲信号进行展宽,转为32bit位宽,并产生有效信号
- 首先题目什么意思呢?就是说给你1T周期宽度的脉冲信号,要你做成32T的宽度,并带上有效信号输出
- 思路:监沿器鉴定脉冲下降沿,输出有效信号;用计数器将1T延迟到32T
- 代码:
module 1_to_32(
input clk,
input rst_n,
input pulse_in,
output reg pulse_out,
output wire flag
);
//-------------------------------------------------------
reg pulse_in_ff0,pulse_in_ff1;
reg [4:0] cnt;
//-------------------------------------------------------
//监沿(下降沿)
always @(posedge clk or negedge rst_n)begin
if(!rst_n) begin
pulse_in_ff0<=1'b0;
pulse_in_ff1<=1'b0;
end
else begin
pulse_in_ff0 <= pulse_in;
pulse_in_ff1 <= pulse_in_ff0;
end
end
//前一拍为0,现在为1,则为上升沿
assign flag=(~pulse_in_ff1)&pulse_in_ff0;
//-------------------------------------------------------
//计数延长32个时钟
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
pulse_out<=1'b0;
else if(flag)
pulse_out<=1'b1;
else if(cnt==5'd31)
pulse_out<=1'b0;
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt<=5'b0;
else if(pulse_out==1'b1)begin
if(cnt==5'd31)
cnt<=5'd0;
else
cnt<=cnt+1'b1;
end
end
//-------------------------------------------------------
endmodule
2.16 T触发器
- T触发器的公式是Q* = TQ’ + T’Q ,相当于一个异或运算
- 所以采用两级tff即可
`timescale 1ns/1ns
module Tff_2 (
input wire data, clk, rst,
output wire q
);
//*************code***********//
reg data_tff0;
reg data_tff1;
always@(posedge clk or negedge rst)begin
if(!rst)begin
data_tff0 <= 0;
data_tff1 <= 0;
end
else begin
data_tff0 <= data^data_tff0;
data_tff1 <= data_tff0^data_tff1;
end
end
assign q = data_tff1;
//*************code***********//
endmodule
2.17 奇偶校验
对输入的32位数据进行奇偶校验,根据sel输出校验结果,0输出奇校验,1输出偶校验
- 首先你要明白奇偶校验的原理:通过在编码中增加一位校验位来使编码中1的个数为奇数或者偶数
- 那么我们怎么求奇偶校验位呢? 答案是异或
举个栗子,码流101 ,奇校验位是1,偶校验位是0 ,数据直接异或的结果是0,刚好是偶校验,那么这时奇校验取反就行 - 代码如下:
`timescale 1ns/1ns
module odd_sel(
input [31:0] bus,
input sel,
output check
);
//*************code***********//
wire ji;
wire ou;
wire check;
assign ou = ^bus;
assign ji = ~ou;
assign check = (sel) ? ou :ji;
//*************code***********//
endmodule
2.18 移位与乘法
已知d为一个8位数,在每个时钟周期分别输出该数乘1/3/7/8,并输出一个信号表示此时刻输入d有效
- 关键在于什么时候读进来数据
- 什么时候进行什么样的计算
- 那么引入一个计数器就可以完美解决,图中一共4周期一个循环,第一个周期上升沿取得数据,那么数据在第二个周期有效,第2-4周期可以直接用第一周期取到的进行计算
- 左移位可以实现x2
- 右移位可以实现÷2
- 移位不消耗乘法除法器的那么多资源
- 代码:
`timescale 1ns/1ns
module multi_sel(
input [7:0]d ,
input clk,
input rst,
output reg input_grant,
output reg [10:0]out
);
//*************code***********//
reg [1:0] cnt;
reg [7:0] d_reg;
always@(posedge clk or negedge rst)begin
if(!rst)
cnt <= 0;
else
cnt <= cnt + 1;
end
always@(posedge clk or negedge rst)begin
if(!rst)begin
d_reg <= 0;
out <= 0;
input_grant <= 0;
end
else
case(cnt)
0:
begin
d_reg <= d;
out <= d;
input_grant <= 1;
end
1:
begin
out <= (d_reg<<2) - d_reg;
input_grant <= 0;
end
2:
begin
out <= (d_reg<<3) - d_reg;
input_grant <= 0;
end
3:
begin
out <= d_reg<<3;
input_grant <= 0;
end
endcase
end
//*************code***********//
endmodule
2.19 generate 写法
用generata…for语句编写代码,替代下面语句,
assign data_out [0] = data_in [7];
assign data_out [1] = data_in [6];
assign data_out [2] = data_in [5];
assign data_out [3] = data_in [4];
assign data_out [4] = data_in [3];
assign data_out [5] = data_in [2];
assign data_out [6] = data_in [1];
assign data_out [7] = data_in [0];
- 先定义生成块的变量genvar i ,声明的此变量i只用于生成块的循环计算,在电路里面并不存在
- 使用generate for endgenerate 格式
- 在begin end 声明中可以加上名称
`timescale 1ns/1ns
module gen_for_module(
input [7:0] data_in,
output [7:0] data_out
);
genvar i;
generate
for(i = 0; i < 8; i = i + 1)
begin : bit_gen
assign data_out[i] = data_in[7 - i];
end
endgenerate
endmodule
2.20 子模块法做大小比较
编写一个子模块,将输入两个8bit位宽的变量a,b,并输出ab之中较小的数;并在主模块中例化,实现输出三个8bit输入信号的最小值的功能
- 先写子模块
- 再在top上例化3个子模块进行比较即可
`timescale 1ns/1ns
module main_mod(
input clk,
input rst_n,
input [7:0]a,
input [7:0]b,
input [7:0]c,
output [7:0]d
);
wire [7:0] d;
wire [7:0] ab_min;
wire [7:0] ac_min;
wire [7:0] min;
son u1
(
.clk (clk),
.rst_n (rst_n),
.a (a),
.b (b),
.c (ab_min)
);
son u2
(
.clk (clk),
.rst_n (rst_n),
.a (a),
.b (c),
.c (ac_min)
);
son u3
(
.clk (clk),
.rst_n (rst_n),
.a (ac_min),
.b (ab_min),
.c (min)
);
assign d = min;
endmodule
module son
(
input clk,
input rst_n,
input [7:0] a,
input [7:0] b,
output reg [7:0] c
);
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
c <= 0;
else if(a < b)
c <= a;
else
c <= b;
end
endmodule
2.21 函数的使用
用函数实现一个4bit数据大小端转换的功能。实现对两个不同的输入分别转换并输出。
程序的接口信号图如下:
- 所谓的大小端转换就是高低位互换
- [N]----[0]
- [N-1]----[1]
- [1]----[N-1]
- [0]----[N]
- …
`timescale 1ns/1ns
module function_mod(
input clk,
input rst_n,
input [3:0]a,
input [3:0]b,
output [3:0]c,
output [3:0]d
);
assign c = data_rev(a);
assign d = data_rev(b);
function [3:0] data_rev;
input [3:0] data_in;
begin
data_rev[0] = data_in[3];
data_rev[1] = data_in[2];
data_rev[2] = data_in[1];
data_rev[3] = data_in[0];
end
endfunction
endmodule
2.22 超前进位加法算法的实现
- 超前进位加法器的思想是并行计算进位Ck,用来缩短进位路径
- Ck可以直接由加数得到
- 具体算法如下:
①中间变量P(传播信号)G(生成信号)
②输出计算:
③本题是4位的数据,因此N=3 即[3:0]
`timescale 1ns/1ns
module lca_4(
input [3:0] A_in ,
input [3:0] B_in ,
input C_1 ,
output wire CO ,
output wire [3:0] S
);
wire [3:0] g;
wire [3:0] p;
wire [3:0] s;
wire [3:0] c;
assign g[3] = A_in[3]*B_in[3];
assign g[2] = A_in[2]*B_in[2];
assign g[1] = A_in[1]*B_in[1];
assign g[0] = A_in[0]*B_in[0];
assign p[3] = A_in[3]^B_in[3];
assign p[2] = A_in[2]^B_in[2];
assign p[1] = A_in[1]^B_in[1];
assign p[0] = A_in[0]^B_in[0];
assign c[3] = g[2]+c[2]*p[2];
assign c[2] = g[1]+c[1]*p[1];
assign c[1] = g[0]+c[0]*p[0];
assign c[0] = C_1;
assign s[3] = p[3]^c[3];
assign s[2] = p[2]^c[2];
assign s[1] = p[1]^c[1];
assign s[0] = p[0]^c[0];
assign S = s;
assign CO = c[3];
endmodule
2.23 casex 的使用
根据该功能表,用Verilog实现该优先编码器
- 使用casex语句识别x
- 本身难度不打
`timescale 1ns/1ns
module encoder_83(
input [7:0] I ,
input EI ,
output reg [2:0] Y ,
output reg GS ,
output reg EO
);
always@(*)begin
if(~EI)begin
Y = 0;
GS = 0;
EO = 0;
end
else
casex(I)
8'b00000000:begin
Y = 0;
GS = 0;
EO = 1;
end
8'b1xxxxxxx:begin
Y = 3'b111;
GS = 1;
EO = 0;
end
8'b01xxxxxx:begin
Y = 3'b110;
GS = 1;
EO = 0;
end
8'b001xxxxx:begin
Y = 3'b101;
GS = 1;
EO = 0;
end
8'b0001xxxx:begin
Y = 3'b100;
GS = 1;
EO = 0;
end
8'b00001xxx:begin
Y = 3'b011;
GS = 1;
EO = 0;
end
8'b000001xx:begin
Y = 3'b010;
GS = 1;
EO = 0;
end
8'b0000001x:begin
Y = 3'b001;
GS = 1;
EO = 0;
end
8'b00000001:begin
Y = 3'b000;
GS = 1;
EO = 0;
end
default:begin
Y = 3'b000;
GS = 0;
EO = 0;
end
endcase
end
endmodule
2.24 全减器
module decoder1(
input A ,
input B ,
input Ci ,
output reg D ,
output reg Co
);
wire [2:0] x;
assign x = {A,B,Ci};
always@(*)begin
case(x)
0: begin
D = 0;
Co = 0;
end
1: begin
D = 1;
Co = 1;
end
2: begin
D = 1;
Co = 1;
end
3: begin
D = 0;
Co = 1;
end
4: begin
D = 1;
Co = 0;
end
5: begin
D = 0;
Co = 0;
end
6: begin
D = 0;
Co = 0;
end
7: begin
D = 1;
Co = 1;
end
endcase
end
endmodule
2.25 脉冲同步(大疆)
signal_a是clka(300M)时钟域的一个单时钟脉冲信号,如何将其同步到时钟域clkb(100M)中,并产生出signal_b同步脉冲信号。请用Verilog代码描述,并画出对应的时序波形图说明图
- 像这种单bit信号的跨时钟,我们立马想到打两拍处理,但是这里明显不行
- 因为clka快时钟域发送的信号signal_a,慢时钟域的时钟clkb根本采集不到
- 思路:在clka时钟域将singal_a脉冲展宽为3个周期,再在clkb时钟域进行边沿检测
module pulse_syn
(
input clka,
input rst_n_a,
input clkb,
input rst_n_b,
input signal_a,
output signal_b
);
reg [1:0] cnt;
reg signal_a_exp;
reg signal_a_ff0;
reg signal_a_ff1;
reg signal_a_ff2;
reg signal_b;
//expand 3T for signal_a
always@(posedge clka or negedge rst_n_a)begin
if(!rst_n_a)
signal_a_exp <= 0;
else if(cnt == 'd2)
signal_a_exp <= 0;
else if(signal_a)
signal_a_exp <= 1;
end
always@(posedge clka or negedge rst_n_a)begin
if(!rst_n_a)
cnt <= 0;
else if(signal_a_exp)
cnt <= cnt + 1;
else
cnt <= 0;
end
//在b时钟域下打拍
always@(posedge clkb or negedge rst_n_b)begin
if(!rst_n_b)
begin
signal_a_ff0 <= 0;
signal_a_ff1 <= 0;
signal_a_ff2 <= 0;
end
else
begin
signal_a_ff0 <= signal_a_exp;
signal_a_ff1 <= signal_a_ff0;
signal_a_ff2 <= signal_a_ff1;
end
end
always@(posedge clkb or negedge rst_n_b)begin
if(!rst_n_b)
signal_b <= 0;
else if(~signal_a_ff1 && signal_a_ff2)
signal_b <= 1;
else
signal_b <= 0;
end
endmodule
- 在vcs上的仿真如下:
- tb文件编写如下
`timescale 1 ns / 1 ns
module pulse_syn_tb();
reg clka;
reg rst_n_a;
reg clkb;
reg rst_n_b;
reg signal_a;
wire signal_b;
parameter CYCLE = 15;
parameter RST_TIME = 3 ;
//clka and rst_n_a
initial begin
clka = 0;
forever
#(CYCLE/3)
clka=~clka;
end
initial begin
rst_n_a = 1;
#2;
rst_n_a = 0;
#(CYCLE*RST_TIME);
rst_n_a = 1;
end
//clkb and rst_n_b
initial begin
clkb = 0;
forever
#(CYCLE)
clkb=~clkb;
end
initial begin
rst_n_b = 1;
#2;
rst_n_b = 0;
#(CYCLE*RST_TIME);
rst_n_b = 1;
end
initial begin
signal_a = 0;
#95
signal_a = 1;
#10
signal_a = 0;
#200
signal_a = 1;
#10
signal_a = 0;
end
initial begin
$vcdpluson;
end
initial begin
# 800 $finish;
end
pulse_syn u1
(
.clka (clka),
.rst_n_a (rst_n_a),
.clkb (clkb),
.rst_n_b (rst_n_b),
.signal_a (signal_a),
.signal_b (signal_b)
);
endmodule
2.26 读状态转移表
实现下图的状态转换并输出
- 状态一共有两位,而转态跳转由A来决定
- 当A = 0, 00 跳 01 ,01 跳10 ,以此类推
- 输出Y只有在11状态才输出1,其他为0
`timescale 1ns/1ns
module seq_circuit(
input A ,
input clk ,
input rst_n,
output wire Y
);
reg [1:0] curr_state;
reg [1:0] next_state;
always @ (posedge clk or negedge rst_n)
begin
if( !rst_n ) begin
curr_state <= 2'b00;
next_state <= 2'b00;
end
else begin
curr_state <= next_state;
end
end
always @ (*)
begin
case(curr_state)
2'b00 : next_state = (A == 1'b1) ? 2'b11 : 2'b01;
2'b01 : next_state = (A == 1'b1) ? 2'b00 : 2'b10;
2'b10 : next_state = (A == 1'b1) ? 2'b01 : 2'b11;
2'b11 : next_state = (A == 1'b1) ? 2'b10 : 2'b00;
default : next_state = 2'b00;
endcase
end
assign Y = (curr_state == 2'b11) ? 1 : 0;
endmodule
2.27 rom的实现
实现一个深度为8,位宽为4bit的ROM,数据初始化为0,2,4,6,8,10,12,14。可以通过输入地址addr,输出相应的数据data
- 首先要生成存储的空间 8个4bit的空间 reg [3:0] men [7:0];
- 然后根据地址给输出就行
`timescale 1ns/1ns
module rom(
input clk,
input rst_n,
input [7:0]addr,
output [3:0]data
);
reg [3:0] data;
reg [3:0] men [7:0];
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
begin
men[0] <= 4'd0;
men[1] <= 4'd2;
men[2] <= 4'd4;
men[3] <= 4'd6;
men[4] <= 4'd8;
men[5] <= 4'd10;
men[6] <= 4'd12;
men[7] <= 4'd14;
data <= 0;
end
else
begin
data <= men[addr];
end
end
endmodule
2.28 有符号数的加减(VIVO)
请描述如下代码,实现加法;
C = A + B;
A是21bit无符号数;
B是18位有符号数;
保证正确得到一个不溢出的有符号数C;
- 明确一点:只要参与运算的式子里,有一个数是无符号的,整个式子就会按照无符号计算
- 所以,需要对A做强制转换,而C的位数最高到22bit,符号位再加1bit所以是23bit
module unsign_sign
(
input wire [20:0]A,
input wire signed [17:0]B,
output wire signed [22:0]C
);
assign C =$signed({1'b0,A})+ B;
endmodule
2.29 信号发生器
编写一个信号发生器模块,根据波形选择信号wave_choise发出相应的波形:wave_choice=0时,发出方波信号;
wave_choice=1时,发出锯齿波信号;
wave_choice=2时,发出三角波信号;
mamodule bo
(
input clk,
input rst_n,
input [1:0] wave_choise,
output reg [4:0]wave
);
reg [4:0] fang;
reg [4:0] juchi;
reg [4:0] sanjiao;
reg [4:0] cnt;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 0;
else if(wave_choise != 3)
cnt <= cnt + 1;
else
cnt <= 0;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
fang <= 0;
else if(cnt < 15)
fang <= 20;
else
fang <= 0;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
juchi <= 0;
else if(juchi == 31)
juchi <= 0;
else
juchi <= juchi + 1;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
sanjiao <= 0;
else if(cnt < 16)
sanjiao <= sanjiao + 1;
else
sanjiao <= sanjiao - 1;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
wave <= 0;
else if(wave_choise == 0)
wave <= fang;
else if(wave_choise == 1)
wave <= juchi;
else if(wave_choise == 2)
wave <= sanjiao;
else
wave <= 0;
end
endmodule
dve仿真结果:
2.30 自动贩卖机(经典)
设计一个自动贩售机,输入货币有三种,为0.5/1/2元,饮料价格是1.5元,要求进行找零
d1 0.5元
d2 1元
d3 2元
out1 饮料
out2 零钱
时序如下:
- 这里注意out1和out是在不同的时钟上升沿触发的,并不是同一时钟,我在做这题的时候没仔细看被坑了
`timescale 1ns/1ns
module seller1(
input wire clk ,
input wire rst ,
input wire d1 ,
input wire d2 ,
input wire d3 ,
output reg out1,
output reg [1:0]out2
);
parameter idle = 0;
parameter mao5 = 1;
parameter yuan1 = 2;
parameter yuan2 = 4;
parameter yuan3 = 6;
parameter yuan2mao5 = 5;
parameter yuan1mao5 = 3;
reg [2:0] cstate;
reg [2:0] nstate;
always@(posedge clk or negedge rst)begin
if(!rst)
cstate <= idle;
else
cstate <= nstate;
end
always@(*)begin
case(cstate)
idle:
begin
if({d1,d2,d3} == 3'b100)
nstate = mao5;
else if({d1,d2,d3} == 3'b010)
nstate = yuan1;
else if({d1,d2,d3} == 3'b001)
nstate = yuan2;
else if({d1,d2,d3} == 3'b000)
nstate = nstate;
end
mao5:
begin
if({d1,d2,d3} == 3'b100)
nstate = yuan1;
else if({d1,d2,d3} == 3'b010)
nstate = yuan1mao5;
else if({d1,d2,d3} == 3'b001)
nstate = yuan2mao5;
else if({d1,d2,d3} == 3'b000)
nstate = nstate;
end
yuan1:
begin
if({d1,d2,d3} == 3'b100)
nstate = yuan1mao5;
else if({d1,d2,d3} == 3'b010)
nstate = yuan2;
else if({d1,d2,d3} == 3'b001)
nstate = yuan3;
else if({d1,d2,d3} == 3'b000)
nstate = nstate;
end
yuan2:
begin
nstate = idle;
end
yuan2mao5:
begin
nstate = idle;
end
yuan1mao5:
begin
nstate = idle;
end
yuan3:
begin
nstate = idle;
end
default:
nstate = idle;
endcase
end
always@(*)begin
if(!rst)
begin
out1 = 0;
out2 <= 0;
end
else if(cstate == yuan1mao5)
begin
out1 = 1;
out2 <= 0;
end
else if(cstate == yuan2)
begin
out1 = 1;
out2 <= 1;
end
else if(cstate == yuan2mao5)
begin
out1 = 1;
out2 <= 2;
end
else if(cstate == yuan3)
begin
out1 = 1;
out2 <= 3;
end
else
begin
out1 = 0;
out2 <= 0;
end
end
endmodule
2.31 格雷码计数器
实现一个4位的格雷码计数器
- 思路:将格雷码转成二进制再加1,再转成格雷码输出
- 注意:加是以转换后的二进制加1 bin_cnt <= bin + 1’b1;
`timescale 1ns/1ns
module gray_counter(
input clk,
input rst_n,
output reg [3:0] gray_out
);
reg [3:0] bin;
wire [3:0] gray;
reg [3:0] bin_cnt;
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0) begin
bin <= 4'b0;
end
else begin
bin[3] = gray[3];
bin[2] = gray[2]^bin[3];
bin[1] = gray[1]^bin[2];
bin[0] = gray[0]^bin[1];
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n) begin
bin_cnt <= 4'b0;
end
else begin
bin_cnt <= bin + 1'b1;
end
end
assign gray = (bin_cnt >> 1) ^ bin_cnt;
always @(posedge clk or negedge rst_n)begin
if(rst_n == 1'b0) begin
gray_out <= 4'b0;
end
else begin
gray_out <= gray;
end
end
endmodule
2.32 DMUX
将A时钟下的多bit数据同步到B时钟下,已知data_in端数据变化频率很低,相邻两个数据间的变化,至少间隔10个B时钟周期
- 数据变化的很慢,也就是说传输的速率不高,而且还间隔10个B时钟周期才变化,这说明我们有足够的时间来把A时钟的使能信号同步到B时钟来,数据也不会因为采漏而丢失
- 思路:输入的数据先通过B时钟打一拍寄存起来,再等待A的使能同步完成,进行数据同步
`timescale 1ns/1ns
module mux(
input clk_a ,
input clk_b ,
input arstn ,
input brstn ,
input [3:0] data_in ,
input data_en ,
output reg [3:0] dataout
);
reg [3:0] data_buff;
reg data_en_ff0;
reg data_en_ff1;
always@(posedge clk_b or negedge brstn)begin
if(!brstn)begin
data_en_ff0 <= 0;
data_en_ff1 <= 0;
end
else begin
data_en_ff0 <= data_en;
data_en_ff1 <= data_en_ff0;
end
end
always@(posedge clk_b or negedge brstn)begin
if(!brstn)begin
data_buff <= 0;
end
else
data_buff <= data_in;
end
always@(posedge clk_b or negedge brstn)begin
if(!brstn)begin
dataout <= 0;
end
else if(data_en_ff1)
dataout <= data_buff;
end
endmodule
2.33 握手
数据发送模块循环发送0-7,在每个数据传输完成之后,间隔5个时钟,发送下一个数据。请在两个模块之间添加必要的握手信号,保证数据传输不丢失
- 握手可以用在跨时钟传输上
- 握手信号就是加入一些指示信号,在两个模块间确认数据已经被接受之后再进行下一个数据的传输
- 思路:
①对于发送模块:每五个时钟发一个req和data,收到返回的ack命令后,req拉低等待5个时钟重新发送,data+1也等待再次发送
②而对于接收模块:成功收到数据后输出ack信号
③当然不同时钟需要进行cdc处理
module data_driver
(
input clk_a,
input rst_n,
input data_ack,
output reg [3:0]data,
output reg data_req
);
reg data_ack_ff0;
reg data_ack_ff1;
reg data_ack_ff2;
reg [9:0] cnt;
wire pose;
//==========同步并检测ack上升沿==================
always @ (posedge clk_a or negedge rst_n)begin
if (!rst_n)
begin
data_ack_ff0 <= 0;
data_ack_ff1 <= 0;
data_ack_ff2 <= 0;
end
else
begin
data_ack_ff0 <= data_ack;
data_ack_ff1 <= data_ack_ff0;
data_ack_ff2 <= data_ack_ff1;
end
end
assign pose = ~data_ack_ff2 & data_ack_ff1;
//==============DATA赋值==========================
always @ (posedge clk_a or negedge rst_n)begin
if (!rst_n)
begin
data <= 0;
end
else if(pose)
begin
data <= data+1; //上升沿赋值
end
else begin
data <= data;
end
end
//=============cnt================================
always @ (posedge clk_a or negedge rst_n)begin
if (!rst_n)
cnt <= 0;
else if (pose)
cnt <= 0;
else if (data_req)
cnt <= cnt;
else
cnt <= cnt+1;
end
always @ (posedge clk_a or negedge rst_n)begin
if (!rst_n)
data_req <= 0;
else if (cnt == 3'd4) //cnt计数5个时钟,发送下一个数据
data_req <= 1'b1;
else if (pose)
data_req <= 1'b0;
else
data_req <= data_req;
end
endmodule
module data_receiver
(
input clk_b,
input rst_n,
input [3:0] data,
input data_req,
output reg data_ack
);
reg [3:0] data_in_reg;
reg data_req_ff0;
reg data_req_ff1;
reg data_req_ff2;
always @ (posedge clk_b or negedge rst_n)begin
if (!rst_n)
begin
data_req_ff0 <= 0;
data_req_ff1 <= 0;
data_req_ff2 <= 0;
end
else
begin
data_req_ff0 <= data_req;
data_req_ff1 <= data_req_ff0;
data_req_ff2 <= data_req_ff1;
end
end
assign pose = data_req_ff1 && ~data_req_ff2;
always @ (posedge clk_b or negedge rst_n)begin
if (!rst_n)
data_ack <= 0;
else if (data_req_ff0)
data_ack <= 1;
else data_ack <=0 ;
end
always @ (posedge clk_b or negedge rst_n)begin
if (!rst_n)
data_in_reg <= 0;
else if (pose)
data_in_reg <= data;
else
data_in_reg <= data_in_reg ;
end
endmodule
3.电路知识
3.1 最小最大项及卡诺图
-
N个乘积项的表达(与项),如两变量AB的最小项为A’B’、A’B,、AB’、AB
最小项的性质: ①在输入变量任一取值下,有且仅有一个最小项的值为1; ②全体最小项之和为1 ; ③任何两个最小项之积为0 ; ④两个相邻的最小项之和可以合并,消去一对因子,只留下公共因子;(卡诺图重要原理) 相邻:仅一个因子不同的最小项,如:A'BC'与A'BC;
-
N个项和的表达(或项),如两变量AB的最大项为A’+B’、A’+B,、A+B’、A+B
最大项的性质: ①在输入变量任一取值下,有且仅有一个最大项的值为0 ; ②全体最大项之积为0 ; ③任何两个最大项之和为1 ; ④两个相邻的最大项之积可以合并,消去一对因子,只留下公共因子。 相邻:仅一个因子不同的最大项,如:A'+B+C和A'+B+C
-
卡诺图:用来进行化简
①将函数化为最小项之和的形式 ②画出卡诺图(00、01、11、10 格雷码),在最小项位置填1 ③把一圈起来! 圈1原则: 覆盖所有的1 圈圈数目做到最少 圈圈里面1尽量多
如上图,找公用的项
横着的圈是 AB’
竖着的圈是BC’
结果就是AB’ + BC’
3.2 MOS管原理及功耗
总的就是说:在DS两极加电压,DS不导通;在GS加电压,DS间形成沟道使得DS导通
其也可以分为三个区:
①当Vgs < Vgs(th) 时,也就是小于启动电压时,这部分区域成为截至区
②当Vgs > Vgs(th) 时,如上图所示,虚线左边成为可变电阻区,其等效电阻的大小与Vgs有关
③虚线右边是恒流区,此时电流基本上由Vgs决定
- 功耗
①静态功耗:是指电路输出没有状态转换时的功耗
静态时,CMOS电路的电流非常小,使得静态功耗非常低。CMOS反相器在静态时,P、N管只有一个导通。由于没有Vdd到GND的直流通路,所以CMOS的静态功耗应该等于零。但实际上,由于扩散区和衬底的PN结上存在反向漏电流,所以会产生静态功耗。
②CMOS电路在输出发生状态转换时的功耗称为动态功耗
主要由两部分组成。一部分是电路输出状态转换瞬间MOS管的导通功耗。当输出电压由高到低或由低到高变化过程中,在短时间内,NMOS管和PMOS管均导通,从而导致有较大的电流从电源经导通的NMOS管和PMOS管流入地。
动态功耗的另一部分是因为CMOS管的负载通常是电容性的,当输出由高电平到低电平,或者由低电平到高电平转换时,会对电容进行充、放电,这一过程将增加电路的损耗。
3.3 TTL管原理
①截止区:条件Vbe= 0, ib = 0, ic = 0, c一e间“断开”
②放大区 :条件Vce> 0.7, ib>0, ic 随ib成正比变化, △ic=β△ib
③饱和区 :条件Vce< 0.7, ib >0, Vce 很低,△ic随△ib增加变缓,趋于“饱和”
3.4 触发器种类
前面1.1已经说过了,这里不在阐述
3.5 多谐振荡电路
- 多谐振荡电路是一种自激振荡电路
- 在接通电源以后,不需要外加触发信号,便能自动地产生矩形脉冲
- 由于矩形波中含有丰富的高次谐波分量,习惯上又将矩形波振荡电路称为多谐振荡电路
①对称式多谐振荡器:两个反相器与两个电容耦合起来的正反馈振荡电路。
②非对称式多谐振荡器:在对称式的基础上简化。
③施密特多谐振荡器:用施密特触发电路的反相输出经RC积分接回输入。
④环形振荡器:利用延迟负反馈产生振荡。
⑤石英晶体多谐振荡器:石英晶体 + 对称式(接入石英晶体稳频)
3.6 单稳态电路
单稳态电路中的工作特性具有下的显著特点:
- 第一,它有稳态和暂稳态两个不同的工作状态
- 第二,在外界触发脉冲作用下,能从稳态翻转到暂稳态,在暂稳态维持一段时间以后,再自动返回稳态
- 第三,暂稳态维持时间的长短取决于电路本身的参数,与触发脉冲的宽幅度无关
例子: 声控灯,灭的时候是稳态、而亮则是暂稳态
原理:
①当vi = 0,vi2 = vdd,所以vo = 0
②当vi接脉冲时,在cd和rd之间会产生窄脉冲vd,当vd = vth后,发生如下正反馈
③迅速使得vo1 = vi2 = 0
④由于电容电压不会发生迅速的跳变,因此进入暂稳态
⑤当vdd给电容充电完毕后,又进入稳态
- 单稳态有两种类型的电路:
①微分型:可以用窄脉冲触发、输出脉冲下降沿较差
②积分型:抗干扰能力强、输出边沿比较差
3.7 施密特触发电路
①输入信号在上升和下降过程中,电路状态转换的输入电平不同
②电路状态转换时有正反馈过程,使输出波形边沿变陡
③用于波形的变换
④用于监幅
⑤用于脉冲整形
原理:
①当vi = 0 ,vo = vol = 0 ,这是va也是0
②而vi从0升到vth,CMOS管G1导通,引起如下正反馈
③此时vo迅速上高到vdd,而此时vi的值就是我们所说的vt+
④同理vi从vdd下降到vt-,vo从voh变成vol,也是正反馈过程
3.8 逻辑计算
一张表足矣
- 补一个 A+A’B=A+B
推导:a+a’b=a(1+b)+a’b=a+ab+a’b=a+(a+a’)b=a+b
3.9 线与
- 线与说白了就是物理上连接、电器上选择连接
- 线与在ttl管中是通过两个OC与非门(集电极开路门)来实现的
- 线与在mos管中是通过两个OD与非门(漏级开路门)来实现的
- 他们大致的图都如下所示,只有当Y1Y2都是1,Y才输出1
那么为什么需要用OD/OC门来做线与呢?可以看看OD门的特点:
①可以利用外部电路的驱动能力,减少IC内部的驱动,或者驱动比芯片电源电压高的负载
②可以将多个开漏输出的Pin,连接到一条线上,这也就是一些总线的占用原理
③可以利用改变上拉电源的电压,改变传输电平;如加上上拉电阻就可以提供TTL/CMOS电平输出
④正常的CMOS输出级是上、下两个管子,把上面的管子去掉就是OPEN-DRAIN(OD)
⑤缺点:OD门提供了灵活的输出方式,但也带来了上升沿的延时
3.10 Bi-CMOS微电子技术
- Bi-CMOS技术是一种将CMOS器件和双极型器件集成在同一芯片上的技术
- 双极型(ttl)器件速度高,驱动能力强,模拟精度高,但是功耗大,集成度低,无法在超大规模集成电路中实现
- CMOS器件功耗低,集成度高,抗干扰能力强,但是速度低、驱动能力差
- Bi-CMOS技术综合了双极型器件高跨导和强负载驱动能力及CMOS器件高集成度和低功耗的优点,取长补短,发挥各自优点,是高速、高集成度、高性能超大规模集成电路又一可取的技术路线。目前,在某些专用集成电路和高速SRAM产品中已经使用了Bi-CMOS工艺技术
- Bi-CMOS技术可能不会成为主流的微电子工艺技术,但是在高性能数字与模拟集成电路领域,这种技术将是一种强有力的解决方案之一
3.11 cache
- 高速缓冲存储器 :一种特殊的存储器子系统
- 其中复制了频繁使用的数据以利于快速访问
- 存储器的高速缓冲存储器存储了频繁访问的 RAM 位置的内容及这些数据项的存储地址
- 当处理器引用存储器中的某地址时,高速缓冲存储器便检查是否存有该地址,如果存有该地址,则将数据返回处理器;如果没有保存该地址,则进行常规的存储器访问
- 主存和Cache主要有三种地址映射方式,分别为全相联映射、直接相联映射和组相联映射
4.刷刷刷刷
4.1 晶体管用于放大作用,工作在什么区?
放大区(也叫线性区) :条件Vce> 0.7, ib>0, ic 随ib成正比变化, △ic=β△ib
0.7的Von是硅三极管,锗三极管是0.3
4.2 CMOS管用于开关作用,工作在什么区?
单个MOS管时,看下图;
输入为低电平时,MOS管处于截止区,输出高电平1;
输入为高电平时,MOS管导通(个人觉得时工作在可变电阻区),输出低电平0
当为CMOS管时,看下图;
无论输入是高电平还是低电平,两个MOS管总是一个截至一个导通,即为所谓的互补关系;
而CMOS中的C就是互补的意思;
4.3 设计一个序列发生器,以CLK为控制信号,输出序列为0010110111
module check_num_shift
(
input clk,
input x,
output wire is_it
);
reg [9:0] shift;
always@(posedge clk)begin
shift <= {shift[8:0],x};
end
assign is_it= (shift == 10'b0010110111)?1:0;
endmodule
4.4 共阳数码管(如图示)的显示译码器,当输入A3A2A1A0为0101时,输出为多少
输入为0101即要显示数字5,因此afgcd亮灯,置0,其他置1 ,所以输出是0100100
4.5 5个具有计数功能的T触发器链接,输入脉冲频率为256KHZ, 则此计数器最高位触发器的输出脉冲频率为多少
每输入一个时钟脉冲,触发器状态便翻转一次,实现一次分频,因此5个就是除以 2^5,最后为8KHZ
4.6 什么是BCD码计数器?在4个触发器构成的 8421BCD码计数器中,具有几个无关状态?
BCD码就是用4位二进制数来表示1位十进制数中的0~9这10个数码,因此BCD码计数器就是0-9的计数器,4位触发器共有16种状态,所以多出了6种无关状态
4.7 若传感器的输出分辨率为1mV,ADC的电源为5V,基准电压为2.5V,为保证ADC的采样精度,ADC的位数至少为多少?
传感器的输出电压为模拟信号,它作为ADC的输入信号,其分辨率为1mV,为保证ADC采样精度,n位ADC可以分辨的最小模拟电压就是1mV;分辨率的公式如下,Vref是基准电压2.5
所以算的n最小为12位
4.8 分析下图所示电路,设各触发器初始状态为0,算出Q2n+1的方程
4.9 测量高压电路电流时,应将电流表接到接近哪一端?
处于零电位一端,电流表上承受的共模电压小。
4.10 对一个异步fifo,列出你能想到的所有测试点?
对着信号一个一个列出来感觉不会有什么问题
- 写端口时序行为与描述一致,检查数据在wr被采样时刻正确写入
- 读端口时序行为与描述一致,检查数据在rd被采样时刻正确读出
- 空信号能正确生成
- 满信号能正确生成
- 写满读空之后是否有做读写保护防止数据覆盖
- 写满读空之后是否有做读写保护防止空满信号错乱
- 是否能被正常复位,解复位后各输出信号初始状态(复位值)是否正常
- 格雷码转换逻辑的正确性
4.11 只使用(2选1MUX)完成异或门逻辑,最少需要多少个MUX
Y = A’B +AB’,即:A=0时,Y = B;A=1时,F = Y’
4.12 时序检查中对于异步复位电路的时序分析分别是?
- recovery time:恢复时间
撤销复位时,恢复到解复位状态的电平必须在时钟有效沿来临之前的一段时间到来,才能保证时钟能有效恢复到解复位状态,此段时间为recovery time。
- removal time :移除时间
复位时,在时钟有效沿来临之后复位信号还需要保持的时间为移除时间removal time
4.13 关于于网表仿真描述正确的是?
A.网表仿真不能发现实现约束的问题
B.仿真速度比RTL仿真速度更快
C.网表仿真可以发现电路设计中的异步问题
D.为了保证芯片正常工作,即使在时间和资源紧张情况下,也需要将所有的RTL仿真用例都进行网表仿真并且确保通过
网表仿真通过网表反标sdf进行仿真,仿真速度较RTL仿真慢,由于sdf通过sdc约束和单元逻辑延时和线网延时而来,可以发现约束问题。设计大的话,网表仿真太耗时,常用采用形式验证手段来保证门级网表在功能上与RTL设计保持一致,配合静态时序分析工具保证门级网表的时序
4.14 N位触发器构成的扭环形计数器,其无关状态数有几个?
扭环形计数器,亦称约翰逊计数器,每次状态变化时仅有一个触发器发生翻转,译码不存在竞争冒险,在n(n≥3)位计数器中,可使用2n个状态,有2^n-2n个状态未使用
Verilog代码:右移,[0]位取反补高位
module johnson_cnt(
input clk,
input rst_n,
output reg [3:0] out
);
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
out <= 4'b0;
end
else begin
out <= {~out[0], out[3:1]} ;
end
end
endmodule
4.15 什么结构化描述?行为描述?数据流描述?
数据流描述:采用assign 连续赋值语录。
行为描述:使用always 语句或initial 语句块中的过程赋值语录
结构化描述:实例化已有的功能模块或原语
4.16 为实现D触发器转换成T触发器,图示的虚线框内应是什么电路?
4.17 有一个FIFO设计,输入时钟100Mhz,输出时钟80Mhz,输入数据模式是固定的,其中1000个时钟中有800个时钟传输连续数据,另外200个空闲,请问为了避免FIFO下溢/上溢,最小深度是多少
- 首先一边是100M发数据,一边是80M收数据
- 那么肯定需要一个缓冲器来存来不及收的数据
- 这个缓冲器的深度计算的思路:最坏的情况下需要存储的数量
- 那么对于上述的情况,最坏就是两个800连着发,也就是一次发1600个数据
- 那么题目就变成了发1600个数据,100M发80M收需要多深的fifo
- 我们把100M看成一次发100个,80M看成1次收80个
- 那么1600个需要发16次,而我收16次只收了80*16 = 1280个
- 因此还需对其余的 320 个进行缓冲
- 所以fifo的最小深度应该是320
趁热打铁继续做4.27!!
4.18 在四变量卡诺图中,逻辑上不相邻的一组最小项为:( )
A m1与m3
B m4与m6
C m5与m13
D m2与m8
首先要知道什么是相邻:仅一个因子不同的最小项,如:A’BC’与A’BC
我们把四变量列出来
0000 m0
0001
0010
0011
0100
0101
0110
0111
1000
1001
1010
1011
1100
1101
1110
1111 m15
所以最后检验选项 选D
4.19 3个D触发器构成的电路图如下,Q2端的输出是什么?
4.20 一个8进3出的优先编码器,如果1、3、4、5输入端为有效电平,其二进制输出为?
优先编码器首先输出最高顺序的输入,根据优先编码器的优先级别,5的级别最高,则其二进制输出为101
4.21 Verilog 里的取模运算:Verilog 中 10%(-3) 的结果是多少?
Verilog 里的取模运算,即求余数运算,先把各自符号位去掉运算,然后取第一个运算数的符号位,答案为1。
4.22 Verilog有符号数的计算
在下列Verilog代码中,a=11, b=10,则z的运算结果为:
input [3:0] a;
input [3:0] b;
output signed [7:0] z;
wire signed [3:0] c;
assign c = a[3:0] * b[3:0];
assign z = c;
- 在运算中,只要是有无符号数参与,就按无符号数运算;
(1)输入的 a 和 b 都是无符号数,所以进行无符号数的乘法,11 和 10 也在输入的表示范围内,执行 11*10 =110;
(2)c 只有 4 bit ,所以直接截取低 4 位为 1110,也就是14;
(4)将其赋值给 z 后,由于 z 是有符号数,且 c 的最高位符号位为 1,所以进行符号位的扩展,得到 1111_1110,该数表示 -2。
- 为什么表示 -2?
(1)该数目前是补码表示,去掉最高位的符号位为 111_1110;
(2)取反,000_0001;
(3)加 1,得到 000_0010,该数表示2;
(4)符号位为 1,表示负数,所以是 -2;
4.23 三目运算有x和z态怎么判断
现有表达式Y = C ? A : B;如果C = x或z,A=1010,B=1100时,Y为多少?
- 如果条件为x或z,结果将按位操作的值表示:0与0得0,1与1得1,其余情况为x
- 所以输出1xx0
4.24 rom和ram的计算
①用2048x12的ROM芯片,最多能实现()个输入 ()个 输出的组合逻辑的数?
- 首先你要明白2048*12是什么意思
- 2048也就是深度,12是数据线
- 而rom是只读的,输出就是12根线的输出
- 因此输入就是11个,因为2^11是2048,一个地址对应一个深度,2048个深度就需要11根地址线输入
②SRAM共12根地址线,32根数据线, 如果要实现2的20次方bytes的Memory,需要多少块这样的SRAM?
- 一个byte就是8位数据
- 32根线,一根只能传一个数据,也就是4byte
- 那么sram一共能存4* 2^12 个byte
- 用2^20除以上面的数得出结果:64片
4.25 NAND flash和NOR flash
- Flash又分为NAND flash和NOR flash二种
- NOR的特点是芯片内执行,这样应用程序可以直接在flash 闪存内运行,不必再把代码读到系统RAM中
- NOR的传输效率很高,在1~4MB的小容量时具有很高的成本效益,但是很低的写入和擦除速度大大影响了它的性能
- NAND内部采用非线性宏单元模式,为固态大容量内存的实现提供了廉价有效的解决方案
- NAND存储器具有容量较大,改写速度快等优点,适用于大量数据的存储,因而在业界得到了越来越广泛的应用,如数码相机、MP3记忆卡、U盘等
1、NOR的读速度比NAND稍快一些
2、NAND的写入速度比NOR快很多
3、NAND的4ms擦除速度远比NOR的5s快
4、NAND的擦除单元更小,相应的擦除电路更少
4.26 JK触发器状态
如下电路实现的逻辑功能为多少进制计数器?
- 首先要知道JK触发器的公式Q* = JQ’ + K’Q ,其中Q*是下一个状态
- Q2的J端是相与后作为J引入JK触发器
- 注意Q1的触发是Q0的上升沿,其他都是时钟触发
最后得到状态表
4.27 fifo深度计算
ModuleA、ModuleB用的是同个Clock, Clock领率80MHz。
ModuleA和ModuleB同时启动,ModuleA产生burst数据给ModuleB,一共产生8次burst;burst rate : 1280 Mbit/s,burst持续时间1us;burst内部速率均匀,burst周期5us,余下的4us内没有数据
ModuleB收到启动信号后,需要花10us做初始化,所以先把moduleA的数据缓存在moduleB内部的同步fifo中,同步fifo位宽32bits,初始化结束后moduleB以640Mbit/s的均匀速率从fifo中读取数据
问:最小的fifo深度?
- fifo深度问题的核心就是最坏的情况下需要存储的数据
- 因为B需要初始化10us,因此这个时候A可能已经传了10us数据了,也就是传了2次,那么最大的情况就是初始化的10us传了两次数据+第11us也传了一次数据,也就是3次数据,而我读数据再第11us读了一次
- 因此这时候需要存储的数据为 1280Mbit/s x 3us - 640 Mbit/s x 1us = 3200bit
- 因此需要3200/32 = 100 个深度
4.28 浮点数的无损计算(大疆)
对12.918做无损定点化,需要的最小位宽是多少?位宽为11时量化误差是多少?
- 整数部分不用说都知道,4位就能表示
- 小数怎么量化呢?
①当小数取1位精度,也就是x.1 或者x.0 相当于把1分成了两份,.1占一份,.0占一份,所以精度就是0.5
②当小数取2位精度,就有4种组合,每种占0.25,精度就是0.25,以此类推
③所以小数位数越多,表示的精度越高,若小数点后有n位,则其表示的最大精度为1/(2^n) - 量化误差是什么呢?
①而0.918用8bit的数怎么表示呢,就是用0.912* 2^8 = 235.008 ,就是说用8位宽度表示0.918时,对应10进制的235
②反过来,将235进行8位的量化,238/(2^8) = 0.91796875
③量化的误差就是0.918 - 0.91796875 = 0.00003125 - 无损的要求:量化误差小于精度的一半,就可以认为是无损的
①对于8位的精度,1/(2^8)= 0.00390625
②刚才算出来的量化误差为0.00003125,明显小于精度的一半,因此是无损的 - 所以需要8+4 = 12位来无损量化12.918
- 当位数是11时:
①0.918 * 2^7 = 117.504 取117
②117/(2^7)= 0.9140625
③量化误差 = 0.918- 0.9140625 = 0.0039375
4.29 FIFO对前后级的握手信号如何产生?
- 只要FIFO没满,就可以一直收数据,即对上级的应答信号ready一直拉高
- FIFO不为空,就可以一直向下级发送数据,即数据发送信号valid一直拉高
4.30 为什么异步fifo可以进行跨时钟域处理?
- 异步fifo中使用了存储器RAM能将两个时钟域的时序路径隔开。
- 在异步fifo的读写控制中,引入了格雷码同步;由于格雷码相邻两个码之间只有一位发生变化,因此在指针跨时钟域传递时如果发生亚稳态,指针要么是变化后的地址,要么是与同步前的指针保持一致,因此这不会引起fifo的功能紊乱,只是影响了其读写效率
4.31 解释input delay和 output delay的含义
- 两者都是对外部器件与芯片之间时序路径的约束
- input delay是指从上游器件的时钟边沿到输入端口之间的延迟,包括触发器的输出延迟Tco、PCB的走线延迟Tpcb;
- input delay =Tco + Tpcb
- output delay 是指从端口输出数据到下游器件时钟沿之间的延迟,包括pcb走线延迟与下游器件第一级寄存器的建立时间。
- output delay = Tpcb + Tsu
4.32 如果芯片已经生产出来,发现setup time或者hold time有违例,怎么办?能补救吗?
- 由于建立时间的时序路径与时钟周期有关,因此若是出现setup违例可通过降低时钟频率来增大时序裕量
- 而对于hold违例,首先在需要保证再经过处理后setup不能为例,因此首先先降频保证满足建立时间,然后对芯片进行升温,增大器件延迟比如Tcq,从而增大保持时间时序裕量
4.33 FIFO会不会存在假空假满的情况呢?
- 会,异步FIFO会存在虚空虚满的情况
- 判空信号是在读时钟域中产生的,需要将写时钟的写指针转换成格雷码并同步到读时钟域来,从而导致被同步过来的写地址是慢于或者等于真实的写地址的值
- 此时读地址的格雷码的值是真实的值,所以,此时判断出来的空状态是一个假空状态,同理,可得到写时钟域判断出来的满状态是一个虚满状态
- 这不影响异步FIFO的功能使用, 只是会较低其工作效率
4.34 时序路径的终点和起点?
- 时序路径的起点有触发器和输入端口
- 终点有触发器和输出端口
- 起点和终点进行排列组合,共有4中时序路径的类型
4.34 FPGA 不同bank之间有什么不同?
- FPGA的端口被划分为多个bank,每个bank的独立供电,相同bank中的IO电气标准相同
- 每个bank的标准接口电压由VCCO决定,不同bank的接口电压不同,只有相同接口电压VCCO的IO才能直接相连
4.35 如果有一段突发数据,需要将其转为稳定的数据流,怎么做?
- 可以先用存储将这部分数据缓存下来,等待全部数据缓存完成后再以一个稳定的速率将数据从存储器中读出
- 在同一时钟域可以使用同步fifo对数据进行缓存,在读使能有效时将数据读出
- 在不同时钟域,可以采用异步fifo
4.36 除了人肉看波形外,还有什么验证正确性的办法?
- 可能和验证更相关了,可以将dut中需要检查的节点添加打印信息,在 scoreboard内添加对比文件信息,比较两者的一致性,输出两者的比较结果
4.37 AHB仲裁是怎么工作的?sel信号怎么产生?
- 仲裁过程:
①设备向仲裁模块通过发送HBuSREQx信号请求访问总线
②仲裁器置位该设备的HGRATx信号,表示申请已成功,并在当前传输完成时获得总线访问权
③设备获得地址、数据总线 - 授予总线是有优先级的,仲裁器通过HGRANT信号表示总线中哪个master的优先级最高,并且在当前HREADY为高(数据读写完成)时将总线授予优先权最高的master
- 存在一些仲裁的算法如固定优先级仲裁,循环式优先级仲裁,随机性仲裁和竞争优先级仲裁
- 当设备得到总线权力后,那么就可以由HADDR根据译码电路来给出不同的sel来选择从机
4.38 Ready信号有延迟怎么办?
- VALID/READY 机制其实有3种情况
①VALID 信号先到达,也就是Ready迟来,这时候VALID 是一直拉高的,直到本次传输完成;题目中的Ready信号有延迟就会导致数据收的慢一点,如果对功能产生影响,则需要在Ready信号所在模块进行优化,具体问题要具体分析,分析为什么延迟了,改怎么优化
②READY 信号先到达,会等待VALID,直到传输结束,之后只要VALID没有置起,READY 可以自由发挥
③同时到达,会在下一时钟沿完成握手
4.39 fifo的空满怎么判断?
- 可以通过二进制的地址进行判断
①读空判断:读地址和写地址一样时,则为读空
②写满判断:因为扩展了一位,所以最高位为1时表示读写地址刚好相差一圈,这时就是写满的时候;见下表,假设深度为8,那么扩展后是4位;从data1再次写到data1时,则为写满,从data2再次写到data2时,也为写满,以此类推;很明显写满的时候,最高位相反,其他位一样,也就是上面说的正好相差一圈,用代码表示就是:
assign wfull = (waddr == {~raddr[3],raddr[2:0]});
- 也可以通过格雷码的地址进行判断
①读空判断:读地址和写地址一样时,则为读空
②写满判断:因为扩展了一位,所以最高位为1时表示读写地址刚好相差一圈,这时就是写满的时候;而格雷码相差一圈的表达式为:
assign wfull = (waddr == {~raddr[3:2],raddr[1:0]});
前两位相反,后面的位相同
4.40 多个乘法运算会出现时序问题?怎么解决?
- 在FPGA中乘法运算常常是通过DSP处理的处理
- 多个乘法运算需要大量的时间,在一个时钟周期内常常是算不完的,所以会出现时序问题
- 可以通过增加流水线的方式解决这个问题,将计算的中间结果寄存一拍,在进行后续计算
4.41 什么是伪路径?
- 伪路径就是存在,但是不起作用的路径,不需要对它进行时序分析
- 排除伪路径可以移除无效的时序路径
- 跳过路径优化,可以节省时间和资源
4.42 一个always块中能否用同时用阻塞和非阻塞赋值?
- 不能的
4.43 在rtl设计中哪项工作需要手工进行门级设计?
- ECO-------------工程变更(Engineering Change Order)
- 在设计后期,根据静态时序分析和后仿真中所暴露出来的问题,对电路和标准单元布局进行小范围调整
4.44 STA是在哪个级进行的?
- 门级
- 检验门级电路的最大最小延迟,保证在指定的频率下,能够满足保持时间的要求
4.45 CMOS管的原理?PMOS的衬底连接的是什么?
- 首先我们要了解什么是N\P型半导体
硅中参磷:N型半导体
硅中参硼:P型半导体
PN结
-
以N型半导体为例,在gs之间接入正向电压,沟道会有负电荷被吸引进来从来形成N沟道
-
生成N沟道则为NMOS,符号箭头指向栅极,反之则为PMOS
- P上N下,上正下负,因此PMOS的衬底连接的是VDD
4.46 CRC的计算?
- 数据通信中最常用的一种查错校验码
- 假设crc的生成多项式如下
- 那么其二进制序列为10001001,第7、3、0位为1
- 在数据后面添加二进制序列位数-1个0
- 用新的数据除以10001001,得到的余数为crc码
- 将crc码接在原数据的后面送出
- 接收端收到数据后,除以10001001,余数为0,则数据没有错误
以上是正常的crc检验的流程,但verilog代码中一般通过异或来实现:
- 在0、3、7处进行异或得到下一位
crc_reg[0] <= send[当前位]^crc_reg[6];
crc_reg[1] <= crc_reg[0];
crc_reg[2] <= crc_reg[1];
crc_reg[3] <= send[当前位]^crc_reg[6]^crc_reg[2];
crc_reg[4] <= crc_reg[3];
crc_reg[5] <= crc_reg[4];
crc_reg[6] <= crc_reg[5];
4.47 AHB特性?
- A.split transaction-----------在HRESP信号中体现,将当前传输的优先级调低,在slave准备好之后,通过SPLIT信号通知Arbitration,而后再将优先级调高
- B.burst transfer -------------突发传输
- C.non-tristate implementation—非三态,也就是无三态
4.48 Verilog与其他编程语言有哪几种接口机制?
- PLI(Programming Language Interface )是一种Verilog代码调用C/C++函数的机制。它能让Verilog像调用一些系统调用(如$ display/$ stop/$ random)一样调用用户编写的C/C++函数
- 直接编程接口(DPI,Direct Programming Interface),它能更加简单地连接C、C++或其他非Verilog编程语言;DPI经常被用来调用C代码读取激励、包含一个参考模型或扩展SV的功能。
4.49 UPF
- UPF(unified Power Format),是广泛用于低功耗设计和验证的标准功耗格式,由类似TCL的命令组成,用于描述低功耗意图;这样整个flow的低功耗意图都来自一个文件,降低设计风险。便于多个flow的低功耗验证
- UPF描述了芯片电源管理的供电网络:power distribution architecture、power strategy 、usage of special cell
4.50 PPA
- Performance—性能
- Power—功耗
- Area—面积