数字IC基础知识总结(笔试、面试向)-持续更新

该总结生成于找工作准备期,更新做笔试题中常见的题目涉及到的知识点,同时也是对数字IC基础知识的一些总结。列入内容来自:1.笔试题知识点;2.个人觉得需要总结的一些知识。其中verilog语法部分过于庞杂,因此仅总结一些平常可能用到的但是不是特别明确的部分。同时有些知识因为间隔太过久远或疫情期间查不到比较权威的纸质资料可能总结的不正确,欢迎大家留言批评指正。由于本文持续更新,因此有需要转载的同学可以等持续更新删除后再转载,转载请务必征得本人同意且注明作者和出处

2020.05.18

  • 填充【2.5.建立时间与保持时间】相关内容
  • 增加并填充【2.6.非理想时钟】相关内容
  • 增加并填充【2.7.跨时钟域】相关内容

2020.05.19

  • 填充【6.1.功耗公式】相关内容
  • 填充【6.2.低功耗相关】相关内容

2020.05.20

  • 新增并填充【5.1.FPGA概念】相关内容
  • 填充【8.1.I2C】相关内容

2020.06.05

  • 填充UART协议
  • 填充流程相关

2020.06.06

  • 新增并填充【3.3.默认值】
  • 新增并填充【7.1.序列检测器】
  • 新增并填充【7.2.基本自动售货机】

数制相关

二进制-十进制转换

  • 二进制-> 十进制:对于整数而言,从低到高第n个bit表示2^{n-1},0110 = 2^2 \times 1 + 2^1 \times 1 = 6;对于小数而言,小数点后从高到低的第n个bit表示2^{-n},即小数点后011表示2^{-2} + 2^{-3} = 0.375
  • 十进制->二进制:对于整数,为除2取模,逆序排列,对于小数,为乘2取模,顺序排列,例子如下所示:

例子:14->二进制1110

数字 除2结果 除2取模
14 7 0
7 3 1
3 1 1
1 0 1

例子:0.6875->二进制1011

数字 乘2结果
0.6875 1.375 1
0.375 0.75 0
0.75 1.5 1
0.5 1 1

原码、反码与补码

其区别主要在于负数的表达,具体要求如下:

  • 原码:最高位为符号位,符号位为0表示整数,符号位为1表示负数
  • 反码:同样具有符号位,规则与原码相同,负数较原码非符号为均按位取反
  • 补码:为了消除+0和-0,扩展表达容量,符号位规则与原码相同,负数为原码非符号位按位取反+1

举例:以4bit码为例

十进制 原码 反码 补码
4 0100 0100 0100
-4 1100 1011 1100
-7 1111 1000 1001
-8 - - 1000

BCD码与余3码

BCD码为使用4个bit表示一个十进制位数,即123的BCD码为0x123,余3码表示BCD码基础上加3(十进制),例子如下,对于26而言:

  • BCD码为0x26即二进制为0010 0110
  • 余3码为BCD码直接加3,二进制为0010 0110 + 0011 = 0010 1001

格雷码

格雷码的优势在于相邻的数之间的二进制表达仅有一个bit发生变化,在跨时钟域中不会产生竞争冒险,格雷码-二进制转换如下所示:

  • 二进制转格雷码:保留最高位,剩余相邻位数异或。例如数据1001,结果为1101,如下:
确定方式 运算 结果
取最高位 1 1
异或 1 xor 0 1
异或 0 xor 0 0
异或 0 xor 1 1
  • 格雷码转二进制:保留最高位,从最高位往低运算,对于第n位为第n位格雷码和第n+1位的异或,例如数据1001,结果为1110,如下:
确定方式 运算 结果
取最高位 1 1
异或 0 xor 1 1
异或 0 xor 1 1
异或 1 xor 1 0

数字逻辑相关

布尔逻辑运算律

名称 运算律
结合律 (a + b) + c = a + (b + c)a(bc) = (ab)c
交换律 a + b = b + aa \cdot b = b \cdot a
分配律 a(b + c) = ab + ac
吸收律 a + ab = a
幂等律 a + a = aaa = a
德摩根律 (a + b)' = a'b'(ab)'=a' + b'
互补律 a + a' = 1a \cdot a' = 0
零一律 a + 1 = 1a \cdot 1 = aa + 0 = a a + 1 =1

卡诺图

卡诺图的思路为可视化的将电路逻辑转为最小乘积项,再通过合并最小乘积项进行化简,如这一个例子:
Y = AB' + A'C + BC + C'D
对应的卡诺图为:

AB\CD 00 01 11 10
00 1 1 1
01 1 1 1
11 1 1 1
10 1 1 1 1

表中为1表示有这一个乘积项,例如在AB=01、CD=00有一个1,即表示逻辑表达式含有项A'BC'D'。随后绘制框,绘制框的过程即为将最小项进行组合化简的过程:同时包括0和1的乘积项可消去,如下图所示:

框越大,可消去的项越多,根据以上卡诺图,化简结果为:
Y = A + B + CD'
除了化简外,卡诺图还可用于判定竞争冒险,当存在相切的框时,存在竞争冒险

竞争冒险

组合竞争冒险即信号在实际电路中的传输存在延迟(Delay),所以由于信号到达同一元件的时间并不一致,到达的时间可能存在延迟。导致组合逻辑电路在某些时刻的结果看上去可能是错误的。竞争冒险的检查方法有:

  • 代数法:当逻辑表达式中出现A+A'或A'A时,会产生竞争冒险
  • 卡诺图法:当存在相切的框时,存在竞争冒险

解决方法有以下几种:

  • 增加冗余项:在卡诺图中增加一个冗余的框,同时与相切的两个框相交即可
  • 增加选通信号:当输出稳定后才输出数据
  • 改用时序逻辑:同步时序逻辑中D触发器对竞争冒险不敏感,添加D触发器可解决竞争冒险问题
  • 改用格雷码:格雷码仅有1bit发生变化
  • 输出端增加滤波电容

同步复位与异步复位

同步复位的优点:

  • 一般能够确保电路是百分之百同步的。
  • 确保复位只发生在有效时钟沿,可以作为过滤掉毛刺的手段。

同步复位的缺点:

  • 复位信号的有效时长必须大于时钟周期,才能真正被系统识别并完成复位。同时还要考虑如:时钟偏移、组合逻辑路径延时、复位延时等因素。
  • 由于大多数的厂商目标库内的触发器都只有异步复位端口,采用同步复位的话,就会耗费较多的逻辑资源。

异步复位优点:

  • 异步复位信号识别方便,而且可以很方便的使用全局复位。
  • 由于大多数的厂商目标库内的触发器都有异步复位端口,可以节约逻辑资源。

异步复位缺点:

  • 复位信号容易受到毛刺的影响。
  • 复位结束时刻恰在亚稳态窗口内时,无法决定现在的复位状态是1还是0,会导致亚稳态。

(以上优缺点内容来自知乎用户Kevin Zhang的回答,如有侵权可联系本人删除)

异步复位电路需要注意以下两点:

  • 输入端需滤除毛刺并做抗干扰处理(使用专用的异步复位IO),放置干扰
  • 需要同步释放,防止亚稳态的出现

异步复位同步释放的代码表达如下所示:

// rst_n_out为处理后的复位信号
always @ (posedge clk or negedge rst_n) begin
    if(~rst_n) begin
        rst_n_buff <= 1'b0;
        rst_n_out  <= 1'b0;
    end else begin
        rst_n_buff <= 1'b1;
        rst_n_out  <= rst_n_buff;
    end
end

建立时间与保持时间

建立时间和保持时间的定义如下所示:

  • 建立时间:输入数据在时钟采样沿前必须有效的时间(时钟沿之前必须稳定的时间)
  • 保持时间:输入数据在时钟采样沿后必须保持稳定的时间(时钟沿后必须稳定的时间)

违反建立时间和保持时间可能产生亚稳态,对于一个一般的时序逻辑模型如下所示:

为了满足后一个寄存器的建立时间要求,需要满足:
T_{clk} > T_{c-q} + T_{comb} + T_{steup}
为了满足后一个寄存器的保持时间要求,需要满足:
T_{c-q} + T_{comb} > T_{hold}
由以上公式,可以推断出理想状态下的:

  • 最小时钟周期:T_{c-q} + T_{comb} + T_{steup}
  • 组合逻辑延迟范围:T_{hold} - T_{c-q} < T_{comb} < T_{clk} - T_{c-q} - T_{setup}

非理想时钟

非理想时钟因素包括时钟偏差(skew)和时钟抖动(jitter):

  • clock skew(时钟偏差):集成电路中一个时钟翻转到达时间在空间上的差别(时钟沿到达不同寄存器时钟端口的时间差距)
  • clock jitter(时钟抖动):芯片的某一个给定点(寄存器时钟端口)上时钟周期发生暂时的变化(每个时钟周期之间具有的差异)

偏差和抖动对电路性能均有影响,对于时钟偏差而言:
T_{clk} + \delta > T_{c-q} + T_{comb} + T_{steup} \\ T_{c-q} + T_{comb} > T_{hold} + \delta
这里的\delta即为skew,表示后级寄存器相对于前一级寄存器的时钟偏差,亦可理解为“时钟的从前级到后级的传播延迟”,其值可以为正也可以为负(时钟流向与数据流向反向)。对于抖动而言:
T_{clk} - 2t_j > T_{c-q} + T_{comb} + T_{steup} \\ T_{c-q} + T_{comb} > T_{hold} + 2t_j
其中t_j为时钟抖动,表示抖动的最大值(绝对值)。当同时考虑时钟skew和jitter时,有:
T_{clk} + \delta -2t_j > T_{c-q} + T_{comb} + T_{steup} \\ T_{c-q} + T_{comb} > T_{hold} + \delta + 2t_j
产生非理想时钟的原因可能包括:

  • 时钟信号产生:偏上时钟发生器可能产生时钟jitter
  • 器件制造:由于工艺偏差不同路径上的缓冲器参数不同,引起时钟skew
  • 互联偏差:互联线的形态参数偏差,引起时钟skew
  • 环境:温度和电源,温度变化可以引起时钟skew,用反馈电路校准温度补偿可以缓解;供电一般产生jitter,在时钟驱动器周围加去耦电容缓解

跨时钟域

在跨时钟域过程中,可能会产生以下问题:

  • 数据丢失:常出现在高频时钟域到低频时钟域中,由于采样时钟过慢导致数据丢失
  • 数据重复:常出现在低频时钟域到高频时钟域中,由于采样时钟过快导致数据重复
  • 亚稳态:数据建立时间和保持时间不符合

解决方法有:

  • 同步器:多个(一般为两个)接收域寄存器串联,不能解决亚稳态问题,仅能缓解。同时需要注意连接到同步器上的输入必须是无组合逻辑的寄存器输出信号,否则可能产生毛刺问题。多bit信号一般不用同步器,可能产生毛刺传递错误的数据。
  • 握手信号:通过多次握手
  • 异步FIFO:用于多bit数据的跨时钟域传输(使用格雷码传递地址指针,仅有1bit发生跳变不会产生毛刺)

根据时钟频率区别,在跨时钟域传输时还需要以下操作:

  • 从低频时钟域到高频时钟域:在高频时钟域需要进行间隔采样,否则会产生数据重复
  • 从高频时钟域到低频时钟域:在高频时钟域需要进行数据保持,否则会产生数据丢失

另外,还有一种脉冲同步器需要注意,这种脉冲同步器的方法为在发送时钟域中,每接收到一次脉冲,传递信号的电平发生一次跳变,传递信号通过同步器传递到接收时钟域后使用边沿检测还源出脉冲。

线性反馈移位寄存器

线性反馈移位寄存器用于生成伪随机码,其结构如下所示:

其中Gx为0表示没有这一条连接,为1表示有这一条连接,每一种连接对应一种多项式,如下所示:
Y = G_n x^n + G_{n-1}x^{n-1} + ... + G_{2} x^2 + G_1 x + G_0
当该多项式为本原多项式(不可再因式分解)时,可生成覆盖2^n-1个数据。

Verilog语法

位宽推断

verilog的位宽推断的基本准则为:中间值的位宽=整个表达式中所有操作数(包括结果,即等号左边的数字),例如:

a[7:0] = b[9:0] + c[4:0]

该表达式b+c的结果为10bit,赋值给a的时候舍去最高2bit,再例如:

a[7:0] = b[7:0] + c[7:0]

该表达式b+c的结果为8bit,因此很容易产生数据溢出的问题(255+255->254)

宏定义

宏定义的方式为使用``define A B进行,声明一个宏定义A,内容是B,使用时需要使用\A表示这是一个宏定义。宏定义的使用可以理解为“字符串呼唤”,即在代码中使用`A从字符串角度等效于B,如下例子:

`define SUM A+B+C
assign a = `SUM;   // 使用宏定义
assign a = A+B+C;  // 宏定义等效

注意宏定义不需要使用;结尾,否则;也会被替换,如下:

`define SUM A+B+C;
assign a = `SUM;   // 使用宏定义
assign a = A+B+C;; // 宏定义等效

默认值

对于定义了但是没有赋值过的数据,不同类型具有不同默认值:

  • wire:高阻态z
  • reg:不定值x

还有一种情况是没有指定变量(net)的类型,默认类型为wire

运算符优先级

优先级 运算符类型 运算符
0(最高) 连接运算符 {}、{{}}
1 一元运算符 !、~、&、|、^
2 算数运算符 *、/、%、+、-
3 移位运算符 <<、>>
4 关系运算符 >、<、<=、>=
5 相等运算符 ==、!=、===、!==
6 按位运算符 &、~、|
7 逻辑运算符 &&、||
8 条件运算符 ?:

数字信号处理相关

香农公式

香农公式描述了信噪比-带宽-信道容量之间的关系,公式如下所示:
C = B \times log_2(1 + \frac{S}{N})
其中,\frac{S}{N}为信噪比,B为带宽,C为信道容量

FPGA相关

FPGA基本知识有:

  • FPGA的中文全称为现场可编程逻辑门阵列

  • FPGA基于查找表实现多种逻辑,具有丰富的时序逻辑资源

  • FPGA布线为分段式布线,延迟具有不可预测性

  • FPGA的连接具有易失性(使用SRAM编程),掉电丢失,需要片外存储固件,存储固件一般使用FLASH或EEPROM

SOC设计常识

功耗公式

数字集成电路的功耗主要由两个来源:

  • 动态功耗:在运行时才产生的功耗
  • 静态功耗:只要上电就会产生的功耗

动态功耗的来源主要有以下几种:

  • 开关功耗:当门的输出发生改变时,理解为输出端等效电容的充电(0->1)或放电(1->0),这个过程产生动态功耗
  • 短路功耗:当逻辑门输出发生跳变时,电源到地会短暂的产生通路(上拉网络和下拉网络均导通),此时有电流从电源流动到地,产生动态功耗

动态功耗中开关功耗的公式如下所示:
P_{switch} = \alpha C_LV_D^2f
其中,C_L为等效负载电容,V_D为电源电压,f为频率,\alpha为翻转率,即平均时间内单个节点在单位时钟周期之内翻转的概率。短路功耗的公式如下:
P_{dp} = t_{sc}V_DI_{peak}f
其中t_{sc}为单位时间内两个期间同时导通的时间,V_D为电源电压,I_{peak}为短路电流,f为频率。除了动态功耗外,还存在静态功耗,静态功耗的来源是各种漏电,公式为:
P = V_DI_{peak}
其中V_D为电源电压,I_{peak}为等效漏电电流

低功耗相关

低功耗技术分为以下几种:

  • 工艺级低功耗技术:包括降低电源电压(降低动态功耗,但导致阈值降低,增大静态功耗),降低晶体管尺寸,多层金属布线(避免大范围连线,减少开关电容,但会增加耦合寄生电容)等
  • 版图级低功耗技术:优化时钟树(占功耗40%左右),多电源电压(MSMV关键路径电压高提高性能,其他路径电压低降低功耗),路径平衡技术(使输入信号尽量同时到达,减少不必要的翻转)
  • 门级低功耗技术:门尺寸优化(减小器件尺寸降低功耗,会导致性能下降),门级多阈值电压
  • RTL级低功耗技术:减少不必要的跳变(增加使能信号),门控时钟
  • 系统级低功耗技术:动态电源电压管理(DVS,根据负载调整电源电压),动态阈值调节(DTS),多电压域(关闭不需要的区域的电源,降低静态功耗)

这里需要着重介绍的是门控时钟,门控时钟的基本结构如下所示:

该电路消除了直接使用AND门控制产生的毛刺问题,即仅当CLK为低时,控制信号才可以发生变化,因此不会产生毛刺问题(本来CLK为高时直接被控制信号关断)

DFT相关

设计流程

步骤 流程 工具
1 前端设计 VCS(仿真器)、Verdi(波形查看)、nLint(代码检查)、Incisive(仿真器)、NCsim(仿真器)
2 逻辑综合:将RTL代码综合为门级网表 Design Compiler、Genus
3 形式验证:通过数学等价性的理论性方法验证两个设计的等价性,不需要测试向量 Formality
4 Floorplan:布局,放置macro cell,建立电源网络 ICC(IC Compiler)、Innovus
5 Placement:放置stand cell ICC(IC Compiler)、Innovus
6 Clock Tree Synthesis:时钟网络综合 ICC(IC Compiler)、Innovus
7 Routing:布线 ICC(IC Compiler)、Innovus
8 静态时序分析(STA) StarRC+PT
9 物理验证:LVS、DRC和ERC(电气规则检查) calibre、Hercules、Diva/dracula

常见硬件编程

序列检测器

module sequence_detection #(
    parameter SEQ = 5'b10010,
    parameter SEQ_WIDTH = 5
) (
    input clk,    // Clock
    input rst_n,  // Asynchronous reset active low

    input val,
    input din,

    output reg dout
);
    
reg [SEQ_WIDTH - 1:0] buf_din;
wire [SEQ_WIDTH - 1:0] next_buf_din = {buf_din[SEQ_WIDTH - 2:0],din};
always @ (posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        buf_din <= {SEQ_WIDTH{1'b0}};
    end else if (val) begin
        buf_din <= next_buf_din;
    end
end 

always @ (posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        dout <= 1'b0;
    end else if (val && next_buf_din == SEQ) begin
        dout <= 1'b1;
    end else begin
        dout <= 1'b0;
    end
end

endmodule

基本自动售货机
只有一种商品,售价为10分,可投入硬币5分或10分,考虑找零

module store (
    input clk,    // Clock
    input rst_n,  // Asynchronous reset active low

    input corn_val,
    input corn_type,

    output reg return_corn,
    output reg return_commodity
    
);

localparam NOW_0 = 1'b0;
localparam NOW_5 = 1'b1;

reg mode,next_mode;
always @ (posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        mode <= NOW_0;
    end else begin
        mode <= next_mode;
    end
end

wire is_5 = corn_val && !corn_type;
wire is_10 = corn_val && corn_type;
always @ (*) begin
    case (mode)
        NOW_0:begin
            if (is_5) begin
                next_mode = NOW_5;
            end else begin
                next_mode = NOW_0;
            end
        end
        NOW_5:begin
            if (corn_val) begin
                next_mode = NOW_0;
            end else begin
                next_mode = NOW_5;
            end
        end
        default : next_mode = NOW_0;
    endcase
end

always @ (posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        return_corn <= 1'b0;
    end else if (mode == NOW_5 && is_10) begin
        return_corn <= 1'b1;
    end else begin
        return_corn <= 1'b0;
    end
end

always @ (posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        return_commodity <= 1'b0;
    end else if (mode == NOW_5 && corn_val) begin
        return_commodity <= 1'b1;
    end else if (mode == NOW_0 && is_10) begin
        return_commodity <= 1'b1;
    end else begin
        return_commodity <= 1'b0;
    end
end

endmodule

扩展自动售货机

有两种商品A和商品B,A商品售价5分,B商品售价10分,可投入5分或10分的硬币,考虑找零

module store_plus (
    input clk,    // Clock
    input rst_n,  // Asynchronous reset active low

    input want_val,
    input want_type,

    input corn_val,
    input corn_type,

    output reg return_corn,
    output reg return_store 
);

localparam INIT = 2'd00;
localparam CORN = 2'd01;
localparam RETU = 2'd10;

reg [1:0] mode,next_mode;
always @ (posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        mode <= INIT;
    end else begin
        mode <= next_mode;
    end
end

reg is_corn_finish;
always @ (*) begin
    case (mode)
        INIT:begin
            if (want_val) begin
                next_mode = CORN;
            end else begin
                next_mode = INIT;
            end
        end
        CORN:begin
            if (is_corn_finish) begin
                next_mode = RETU;
            end else begin
                next_mode = CORN;
            end
        end
        RETU:next_mode = INIT;
        default : next_mode = INIT;
    endcase
end

reg store_type;
always @ (posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        store_type <= 1'b0;
    end else if(want_val)begin
        store_type <= want_type;
    end
end

reg [1:0]corn_num,next_corn_num;
always @ (posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        corn_num <= 2'd0;
    end else if (mode == INIT) begin
        corn_num <= 2'd0;
    end else if (mode == CORN && corn_val) begin
        corn_num <= next_corn_num;
    end
end
always @ (*) begin
    if (corn_type == 1'b0) begin
        next_corn_num = corn_num + 1'd1;
    end else begin
        next_corn_num = corn_num + 2'd2;
    end
end

always @ (*) begin
    if (store_type == 1'b0 && next_corn_num >= 2'd1) begin
        is_corn_finish = 1'b1;
    end else if (store_type == 1'b1 && next_corn_num >= 2'd2) begin
        is_corn_finish = 1'b1;
    end else begin
        is_corn_finish = 1'b0;
    end
end

always @ (posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        return_store <= 1'b0;
    end else if (next_mode == RETU) begin
        return_store <= 1'b1;
    end else begin
        return_store <= 1'b0;
    end
end
always @ (posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        return_corn <= 1'b0;
    end else if (next_mode == RETU && !store_type && next_corn_num > 2'd1) begin
        return_corn <= 1'b1;
    end else if (next_mode == RETU && store_type && next_corn_num > 2'd2) begin
        return_corn <= 1'b1;
    end else begin
        return_corn <= 1'b0;
    end
end

endmodule

50%占空比分频器

module frequence_divider #(
    parameter CWIDTH = 8
) (
    input clk,    // Clock
    input rst_n,  // Asynchronous reset active low
    
    input cfg_val,
    input [CWIDTH - 1:0]cfg_data,

    output reg clock_div

);

reg [CWIDTH - 1:0] cfg;
always @ (posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        cfg <= {CWIDTH{1'b0}};
    end else if (cfg_val) begin
        cfg <= cfg_data;
    end
end

reg [CWIDTH - 1:0] count_pos;
always @ (posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        count_pos <= {CWIDTH{1'b0}};
    end else if (count_pos >= cfg) begin
        count_pos <= {CWIDTH{1'b0}};
    end else begin
        count_pos <= count_pos + 1'b1;
    end
end

reg [CWIDTH - 1:0] count_neg;
always @ (negedge clk or negedge rst_n) begin
    if (~rst_n) begin
        count_neg <= {CWIDTH{1'b0}};
    end else begin
        count_neg <= count_pos;
    end
end

wire [CWIDTH - 1:0] judge = {1'b0,cfg_data[CWIDTH - 1:1]} + 1'b1;

always @ (*) begin
    if (cfg == {CWIDTH{1'b0}}) begin
        clock_div = clk;
    end else if (cfg[0] == 'b1) begin
        clock_div = (count_pos < judge)?1'b1:1'b0;
    end else begin
        clock_div = (count_pos < judge && count_neg < judge)?1'b1:1'b0;
    end
end

endmodule

格雷码计数器

第一种写法,节约寄存器,时序比较慢

module gray_count #(
    parameter WIDTH = 4
) (
    input clk,    // Clock
    input rst_n,  // Asynchronous reset active low

    output reg [WIDTH - 1:0] dout_gray
);

one methed
wire [WIDTH - 1:0] count_bin,next_count_bin,next_count_gray;

genvar i;
generate
    for (i = 0; i < WIDTH; i = i + 1) begin:gray
        if (i == WIDTH - 1) begin
            assign count_bin[i] = dout_gray[i];
            assign next_count_gray[i] = next_count_bin[i];
        end else begin
            assign count_bin[i] = dout_gray[i] ^ count_bin[i + 1];
            assign next_count_gray[i] = next_count_bin[i] ^ next_count_bin[i + 1];
        end
    end
endgenerate
assign next_count_bin = count_bin + 1'b1;

always @ (posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        dout_gray <= {WIDTH{1'b0}};
    end else begin
        dout_gray <= next_count_gray;
    end
end
endmodule

第二种写法,多用一套寄存器,时序较好

module gray_count #(
    parameter WIDTH = 4
) (
    input clk,    // Clock
    input rst_n,  // Asynchronous reset active low

    output reg [WIDTH - 1:0] dout_gray
);

reg [WIDTH - 1:0]count_bin;
wire [WIDTH - 1:0] next_count_bin = count_bin + 1'b1;
always @ (posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        count_bin <= {WIDTH{1'b0}};
    end else begin
        count_bin <= next_count_bin;
    end
end

wire [WIDTH - 1:0] next_count_gray;
genvar i;
generate
    for (i = 0; i < WIDTH; i = i + 1) begin:gray
        if (i == WIDTH - 1) begin
            assign next_count_gray[i] = next_count_bin[i];
        end else begin
            assign next_count_gray[i] = next_count_bin[i] ^ next_count_bin[i + 1];
        end
    end
endgenerate

always @ (posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        dout_gray <= {WIDTH{1'b0}};
    end else begin
        dout_gray <= next_count_gray;
    end
end
endmodule

常见协议

I2C

I2C是由 Phiilps提出的一种串行协议,具有两根信号线,分别为:

  • SCL:时钟信号线,由主机产生
  • SDA:数据信号线,为双向信号线(inout类型),用弱上拉电阻上拉到1

静默时,SCL和SDA均为1,传输开始和结束如下所示:

  • 开始信号:SCL为高时SDA拉低
  • 结束信号:SCL为高时SDA拉高

具体如下图所示:

其中S为SCL高且SDA拉低,表示传输开始;P为SCL高且SDA拉高,表示传输结束。数据部分输入与输出共享信号线SDA,数据的传输需要满足以下要求:

  • 当SCL为高时,SDA不发生变化(SCL为高时采样);当SCL为低时数据可以发生变化
  • 一拍数据传输8bit(带ACK为9bit),数据从高位传输,即先发的为MSB,最后发送的为LSB,每次传输发送的数据拍数任意

如下图所示:

整体传输如下图所示:

主机发送开始信号即可开始传输数据,发送8bit后有一个ACK信号需要传输,即主机发送第9个脉冲,同时释放总线,若从机在第9个脉冲为高前将其数据拉低并保持到第9个脉冲结束,则表示为ACK,从机响应主机传输,可继续传输;否则为NACK,应当结束传输。

UART

UART为一个全双工的双线协议,每一根线负责一个方向,分为TX和RX,每根线上一次传输如下:

  • 起始帧:信号线拉低一个时钟周期
  • 数据帧:根据约定可以发送多个bit的数据,低位先发送,每个bit占据一个时钟周期
  • 奇偶校验(可选):在数据bit后添加一个bit,使数据整体1的数量为奇数或者偶数,占据一个时钟周期
  • 结束帧:信号线拉高一个时钟周期

由于线上没有相关时钟,因此时钟靠通信双方进行约定。当通信双方时钟不同时,会出现数据乱码问题。时钟的约定靠波特率完成,在UART中波特率一般与比特率等效,即每秒钟发送的bit数量。对于一个下图的传输,需要1bit起始位,7bit数据位,1bit校验位,1bit停止位,一次传输一共需要10bit,以波特率为9600为例,1s可以发送9600bit,因此一共可以进行960次传输,此时时钟频率被约定为1/9600。

根据上图,起始状态为高,当需要发送数据时,将该信号拉低,即第一个bit为起始帧。随后发送数据,低位优先,发送约定好的数据bit位数后,发送奇偶检验位,最后拉高,表示一次数据发送完成。

SPI

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342