精华内容
下载资源
问答
  • 并行输入串行输出电路结构
    千次阅读
    2020-02-13 23:04:33

    前言

    本文节选自《FPGA之道》,来一起学习下作者对于并行与串行的讲解。

    Verilog的并行语句

    在Verilog的基本程序框架一节中,我们了解到,module模块实现中的语句部分里面的语句都是并行的。那么到底Verilog里面有哪些并行语句可以供我们使用呢?请看如下描述:
    module <module_name>(<port_list>);
    <Verilog连续赋值语句>;
    <Verilog程序块语句>;
    <Verilog实例化语句>;
    <Verilog生成语句>;
    <Verilog函数调用语句>;
    <Verilog模块说明语句>;
    endmodule;
    以上这些并行语句,没有哪一类是不可或缺的,但是一个module中怎么着也得至少有一条,否则虽然从语法上来讲没什么问题,但是这个module就不具有任何功能了。一般来说,只要有模块实例化语句、程序段语句和连续赋值语句这三类语句就足够描述FPGA的功能了,这也正是我们在【Verilog的基本程序框架】一节中所介绍的。下面详细介绍一下这些并行语句。

    Verilog连续赋值语句

    Verilog中共有两种连续赋值语句,即普通连续赋值语句和条件连续赋值语句。它们都只能给线网类型的变量赋值,前面的章节已经对于这两种语句有过介绍,这里简要总结如下:

    普通连续赋值语句

    该语句的显式语法为:
    assign <wire_name> = ;
    也可以有隐式的写法,即在声明线网变量时同时赋值,例如:
    wire a;
    assign a = 1’b1;
    可以简写成:
    wire a = 1’b1;

    条件连续赋值语句

    该语句的显式语法为:
    assign = <1-bit_select> ? <input_for_1> : <input_for_0>;
    也可以有隐式的写法,即在声明线网变量时同时赋值,例如:
    wire a, b, sel, c;
    assign c = sel ? a : b;
    可以简写成:
    wire c = sel ? a : b;

    Verilog程序块语句

    Verilog中共包含两种程序块语句——initial与always,它们的本质区别是initial程序块仅在程序的最开始执行一次,而always程序块会不断地、循环地得到执行。因此,initial程序块主要负责模块的初始化功能,而always程序块才主要负责模块的功能描述。下面,我们将针对always程序块,进行详细讲解。always的语法结构如下:

    always@(<sensitive_list>)
    begin : <lable>
       	<statements>;
    end
    

    其中,是可选的,它是程序块的标号,主要起到提高代码可读性的作用。注意,如果always中只包含一条子语句,那么begin-end关键字可省略,不过必须紧跟在begin关键字的后面,因此有的程序块不能省略begin-end关键字,其它含有begin-end关键字语法的语句也是类似。
    之前已经介绍过,按照<sensitive_list>的形式来分,always共有三个“纯种”的基本类型:纯组合always、纯同步时序逻辑always、具有异步复位的同步时序逻辑always。不过如果从always程序块的结构入手,我们的可以将always的语法划分可以更细一些,即:纯组合always、纯时序always、具有同步复位的always、具有异步复位的always以及具有混合复位的always。可见,基于结构的划分比基于<sensitive_list>的划分更细致一些、面更广一些。基于结构的划分其实就是基于时钟描述结构的划分,因此,首先来介绍一下在always中表示时钟事件的方法。

    沿事件

    时钟描述实际上就是基于时钟信号跳变沿的描述。如果在有一个变量a,那么表示a变化的时候会触发always的执行,但是如果要描述时序逻辑,我们显然需要更精确的描述方法,于是Verilog提供了沿事件。沿事件的描述有两种语法:
    posedge ,
    negedge ,
    分别对应敏感变量的上升沿事件或者下降沿事件。我们可以利用这两句语法来方便的描述时钟等事件。
    切记!在一个always的敏感量表中,只能出现一个时钟信号的一种边沿事件。这是由寄存器的结构决定的,因为一个寄存器只有一个时钟端口,并且只敏感这个端口的某一个边沿,因此凡是不尊重这个事实的代码都是不可综合的。不过,异步复位信号有时候也做边沿事件放入到敏感量表中,但是请注意,异步复位其实是电平敏感事件,之所以作为边沿事件放入到敏感量表中,很大程度上是为了方便仿真。

    下面根据时钟描述结构的不同,分别介绍五种基本always代码结构如下:

    纯组合always

    纯组合always语法如下:
    always@(<sensitive_list>)begin
    ;
    end
    参考例子如下:
    // b must be register data types
    always@(a)begin
    b = not a;
    end
    上述例子描述了一个非门的结构,关于纯组合always程序块,有三点需要注意:
    一、纯组合always程序块中的语句强烈推荐只使用阻塞赋值符号,而时序always程序块中推荐只使用非阻塞赋值符号,否则会带来非常多的隐患。
    二、虽然从字面上理解,always是在变量a出现变化的情况下才触发执行,但是不可自作聪明将上例写成:
    // It is wrong!!
    always@(posedge a or negedge a)begin
    b = not a;
    end
    注意,只有时序逻辑才能用posedge和negedge关键字,虽然从代码事件解释来看上述两例好像功能相似,但是若出现沿事件关键字,则编译器会将程序块综合为时序逻辑,而这个世界上目前还没有既能够敏感一个信号上升沿又能够敏感这个信号下降沿的触发器,所以综合会报错。
    三、若<sensitive_list>中有多个变量,则可以用逗号“,”或者关键字or分隔开来。如果<sensitive_list>中的变量确实太多,Verilog给大家提供了一个偷懒的方法,那就是使用匹配符号“”,此时编译器将会完成<sensitive_list>中的元素推断。例如:
    always@(
    )

    always@*

    纯时序always

    纯时序always的语法如下:
    always@(<edge_type> clk) // only clk in the sensitive list
    begin
    ;
    end
    参考例子如下:
    // a must be register data types
    always@(posedge clk)
    begin
    a <= b;
    end

    具有同步复位的always

    具有同步复位的always的语法如下:
    always@(<edge_type> clk)
    begin
    if(rst) //or if(!rst)
    begin
    ;
    end
    else
    begin
    ;
    end
    end
    参考例子如下:
    //a must be register data types
    always@(negedge clk)
    if(!rst)
    a <= 1’b0;
    else
    a <= b & c;

    具有异步复位的always

    具有异步复位的always语法如下:
    always@(<edge_type> clk, <edge_type> aRst)
    begin
    if(aRst) //or if(!aRst)
    begin
    ;
    end
    else
    begin
    ;
    end
    end
    参考例子如下:
    //n must be register data types
    always @(posedge clk or negedge aRst)
    if (!aRst)
    n <= 8’b0;
    else
    n <= m;
    注意,在Verilog中必须通过敏感量表中的一个沿事件再加上代码中的电平判断来实现一个电平敏感的异步复位。以下写法虽然道理上也说的通,但是却是Verilog不支持的:
    always @(posedge clk or aRst) //will cause compile error
    if (!aRst)
    n <= 8’b0;
    else
    n <= m;
    这是由于always敏感量表中一旦出现了沿事件,就不允许再出现描述组合逻辑的信号事件了。

    具有混合复位的always

    具有混合复位的always的语法如下:
    always@(<edge_type> clk, <edge_type> aRst)
    begin
    if(aRst) //or if(!aRst)
    begin
    ;
    end
    else
    begin
    if(rst) //or if(!rst)
    begin
    ;
    end
    else
    begin
    ;
    end
    end
    end
    也可以写成如下形式:
    always@(<edge_type> clk, <edge_type> aRst)
    begin
    if(aRst) //or if(!aRst)
    begin
    ;
    end
    else if(rst) //or else if(!rst)
    begin
    ;
    end
    else
    begin
    ;
    end
    end
    参考例子如下:
    //a must be register data types
    always@(posedge clk, negedge aRst)
    if(!aRst)
    a <= 4’h0;
    else
    if(rst) //or if(!rst)
    a <= 4’hF;
    else
    a <= b;

    Verilog实例化语句

    实例化语句是非常重要的一种语句,有了它,我们才可以化繁为简、聚简成繁!在之前的Verilog基本程序框架小节中我们对实例化语句进行了一些简单了解,而在这里我们将详细介绍一下Verilog中的实例化语句。Verilog语言中支持两种模块实例化方式——单独实例化与数组实例化,分别介绍如下:

    单独实例化

    单独实例化的语法如下:
    <module_name> <instance_name> (.<port_name_0> (),
    .<port_name_1> (),

    .<port_name_N> ());
    其中,<module_name>是一个已经完成模块的名字,<instance_name>是我们给它实例化对象起的一个名字,这两者之间的对应关系很像C++中类和对象之间的关系。<port_name_X>对应被实例化模块中的具体端口名称,其后的为与端口连接的父模块内部的变量。例如:
    wire a, b, c;
    myAnd insAnd (.in0 (a), .in1(b), .out©);
    注意,实例化的时候,实例的输出端口只能连接线网类型的变量,而输入端口可以连接线网或者寄存器类型的变量。

    数组实例化

    有些情况下,我们可能需要同时实例化一个模块多次,这个时候如果使用单独实例化语句会使代码显得非常的臃肿,也不利于阅读和修改,于是Verilog提供了数组实例化语句,语法如下:
    <module_name> <instance_name> <instance_array_range>
    (.<port_name_0> (variable0),
    .<port_name_1> (variable1),

    .<port_name_N> (variableN));
    可以看出,相比于单独实例化语句,它主要多了一个<instance_array_range>参数,利用这个参数,我们就可以控制实例的数量。例如:
    wire [3:0] a, b, c;
    myAnd insAnd[3:0] (.in0 (a), .in1(b), .out©);
    上述数组实例化语句的功能相当于
    myAnd insAnd3 (.in0 (a[3]), .in1(b[3]), .out(c[3]));
    myAnd insAnd2 (.in0 (a[2]), .in1(b[2]), .out(c[2]));
    myAnd insAnd1 (.in0 (a[1]), .in1(b[1]), .out(c[1]));
    myAnd insAnd0 (.in0 (a[0]), .in1(b[0]), .out(c[0]));
    有些时候,众多实例中的有些端口是需要共用信号的,例如使能信号,此时可以写成这样:
    wire en;
    wire [3:0] a, b, c;
    myEnAnd insEnAnd[3:0] (.in0 (a), .in1(b), .inEn (en), .out©);
    此时的数组实例化语句的功能相当于
    myAnd insAnd3 (.in0 (a[3]), .in1(b[3]), .inEn (en), .out(c[3]));
    myAnd insAnd2 (.in0 (a[2]), .in1(b[2]), .inEn (en), .out(c[2]));
    myAnd insAnd1 (.in0 (a[1]), .in1(b[1]), .inEn (en), .out(c[1]));
    myAnd insAnd0 (.in0 (a[0]), .in1(b[0]), .inEn (en), .out(c[0]));
    注意,数组实例化时,对输入的变量位宽是有一定要求的:
    一、等于所有实例对应端口的位宽之和。例如对于上例的变量a来说,它的位宽等于4个实例中in0端口的位宽和:1bit*4 = 4bits。这样变量的位宽将会被均分到各个实例的对应端口上;
    二、等于模块对应端口的位宽。例如对于上例的变量en来说,它的位宽就等于模块只能够inEn端口的位宽,为1bit,此时该变量就会被连接至所有的实例对应的端口上。
    对于其他情况的位宽输入Verilog的数组实例化语句都是不支持的,请不要在这个地方进行错误的发明创造。

    实例参数重载

    参数重载也是实例化语句的一个重要组成部分。在Verilog基本程序框架中,我们提到过,为了增强模块的重用性,Verilog会在模块中定义一些参数,从而通过再例化的时候对参数进行重载来适应不同的需求。按照例化时对参数的重新赋值方式,我们可以把参数重载分为内部重载与外部重载,分别介绍如下:

    内部重载
    内部重载使用”#(.<parameter_name>(new_value), …)”语法,例如:
    cellAnd #(.WIDTH(4)) m (a,b,c);
    这是我们在【Verilog基本程序模板】小节中给出的例子。

    外部重载
    相比于在模块实例化的时候来修改参数的值,外部重载允许在编译的时候再修改参数的值。外部重载需要用到defparam关键字,举例如下:
    cellAnd m (a,b,c);
    defparam m.WIDTH = 4;
    不过在使用defparam的时候需谨慎,因为有些综合工具或者它们的早期版本并不支持该语法。

    端口赋值形式

    实例化时实例端口的赋值形式有多种,当然,最常用,最典型也是最推荐的就是映射赋值,不过除此以外,端口赋值还有多种形式,列举如下供大家了解:
    一、映射赋值。例如:
    wire a, b, c;
    myAnd insAnd (.in0 (a), .in1(b), .out©);

    二、位置赋值。例如:
    wire a, b, c;
    myAnd insAnd (a, b, c);

    三、部分赋值。这是由于有些模块在使用时并不是所有端口都需要的,若上例中的端口b是可以不使用的,那么按照映射赋值可以写成:
    myAnd insAnd (.in0 (a), .out©);
    而按照位置赋值必须写成:
    myAnd insAnd (a, , c);
    注意其中的多余的那个逗号,是用来占位用的,有了它,后面的c变量来能正确对应到out端口。

    四、常数赋值。例如:
    wire a, c;
    myAnd insAnd (.in0 (a), .in1(1’b1), .out©);
    注意,常数只能用于实例的输入端口。

    五、表达式赋值。例如:
    wire a, b, c, d;
    myAnd insAnd (.in0 (a&d), .in1(~b), .out©);
    不过不建议这样做,因为不太符合规范。

    Verilog生成语句

    Verilog中的生成语句主要使用generate语法关键字,按照形式主要分为循环生成与条件生成,分别介绍如下:

    循环生成

    循环生成的主要目的是简化我们的代码书写,利用循环生成语句我们可以将之前需要写很多条比较相似的语句才能实现的功能用很简短的循环生成语句来代替。基本语法如下:
    genvar ;
    generate
    for (=0; < ; =+1)
    begin:

    input [3:0] a,b;
    output [3:0] c,d;
    
    generate
    genvar i;
    	for (i=0; i < 4; i=i+1) 
    	begin : genExample
    		myAnd insAnd (.a(a[i]), .b(b[i]), .c(c[i]));
    		assign d[i] = a[i];
    	end
    endgenerate
    

    注意,利用循环生成语句生成的实例名称不能像数组例化那样用方括号表示,否则会报错。那么,你可能会疑惑上例中实例的名字,其实,上述实例化展开来类似:

    myAnd genExample(0).insAnd (.a(a[0]), .b(b[0]), .c(c[0]));
    myAnd genExample(1).insAnd (.a(a[1]), .b(b[1]), .c(c[1]));
    myAnd genExample(2).insAnd (.a(a[2]), .b(b[2]), .c(c[2]));
    myAnd genExample(3).insAnd (.a(a[3]), .b(b[3]), .c(c[3]));
    

    这也是为什么循环生成语句必须要有个名字。从上例我们还可以看出,当循环语句用作实例化时,所表述的功能跟数组实例化语句其实是类似的。
    最后,循环生成语句是支持嵌套的,例如二重循环生成语法如下:

    	genvar <var1>, <var2>;
    generate
           for (<var1>=0; <var1> < <limit>; <var1>=<var1>+1) 
           begin: <label_1>
              for (<var2>=0; <var2> < <limit>; <var2>=<var2>+1) 
              begin: <label_2>
                 <code>
              end
           end
    endgenerate	
    

    条件生成

    条件生成的目的是为了左右编译器的行为,类似于C语言中的条件选择宏定义,根据一些初始参数来决定载入哪部分代码来进行编译。Verilog中共提供了两种条件生成语句,一种是generate-if语句,一种是generate-case语句,两者的功能几乎相同,只是书写形式不一样而已,分别介绍如下:

    generate-if语句

    该语句的语法如下:

    generate
    	if (<condition>) begin: <label_1>
    		<code>;
    	end else if (<condition>) begin: <label_2>
    		<code>;
    	end else begin: <label_3>
    		<code>;
    	end
    endgenerate
    

    关于该语法有三点注意:
    1、必须是常量比较,例如一些参数,这样编译器才可以在编译前确定需要使用的代码;
    2、if语句的内容中,begin-end只有在有多条语句时才是必须的;
    3、每一个条件分支的名称是可选的,这点不像循环生成语句那么严格。
    关于generate-if语句,举例如下:

    wire c, d0, d1, d2;
    parameter sel = 1;
    
    generate
    	if (sel == 0)
    		assign c = d0;
    	else if (sel == 1)
    		assign c = d1;
    	else
    		assign c = d2;
    endgenerate
    

    该例子表示编译器会根据参数sel的值,来确定到底是让d0~d2中哪个变量和c连通。但是注意,一旦连通,那么要想更改必须修改参数后重新编译,如果需要动态选择,可以写成如下这样,但是资源上却都需要一个多路复用器来实现。
    assign c = (sel == 0) ? d0 : (sel == 1) ? d1 : d2;

    generate-case语句

    该语句的语法如下:

    	generate
    		case (<constant_expression>)
    			<value>: begin: <label_1>
    				<code>
    				end
    			<value>: begin: <label_2>
    <code>
    				end
    			……
    			default: begin: <label_N>
    <code>
    end
          		endcase
       	endgenerate
    

    关于该语法也有三点注意,和generate-if类似:
    1、<constant_expression>必须是常量比较,例如一些参数,这样编译器才可以在编译前确定需要使用的代码;
    2、case语句的内容中,begin-end只有在有多条语句时才是必须的;
    3、每一个条件分支的名称是可选的,这点不像循环生成语句那么严格。
    关于generate-case语句,举例如下:

    wire c, d0, d1, d2;
    parameter sel = 1;
    
    generate
    	case (sel)
    		0 :
    			assign c = d0;
    		1: 
    			assign c = d1;
    		default: 
    			assign c = d2;
    	endcase
    endgenerate
    

    该例所描述的功能和generate-if小节的例子是一摸一样的。

    Verilog函数调用语句

    函数的定义可以放在模块实现部分中的声明部分,使用function语法定义如下:

    function  [<lower>:<upper>] <output_name> ;
    input <name>;
    <other inputs>
    <variable declarations>
    begin
    <statements>
    end
    endfunction
    

    关于函数的定义有以下几点说明:
    1、<output_name>既是输出的变量名也是函数调用名,它的位宽由function关键字后面的范围指定;
    2、中,只能够声明寄存器类型的变量;
    3、函数体中语句只能使用阻塞赋值符号;
    4、函数调用的时候只能使用位置赋值,因此需要严格按照input的顺序罗列变量;
    5、函数调用可以用在并行语句中也可以用在串行语句中;
    6、函数中可以调用别的函数;
    7、函数支持递归,不过此情况一般用于仿真;
    8、函数不能调用任务,这主要是由于任务中可以有定时相关语句,而函数中不能够有。

    举例如下:
    示例一:几种常用函数调用方法;

    module tft(input clk, a, b, c, output reg d,e); 
    function  andFunc ;
    input a;
    input b;
    begin
    andFunc = a & b;
    end
    endfunction
    
    always@(posedge clk)
    begin
    	e <= andFunc(a, b); // called by no-blocking assignment
    end
    
    always@*
    begin
    		d = andFunc(a, b); // called by blocking assignment
    end
    
    assign c = andFunc(a, b); // called by continuous assignment
    endmodule
    

    示例二:函数调用函数

    module(input a,b, output c); 
    function  bufFunc ;
    		input a;
    		begin
    			bufFunc = a;
    		end
    endfunction
    function  andFunc ;
    		input a;
    		input b;
    		reg t;
    		begin
    			andFunc = bufFunc(a) & b;
    		end
    endfunction
    assign c = andFunc(a,b);
    endmodule
    

    示例三:函数递归

    function [3:0] addFunc ;
    	input [3:0] a;
    	reg [3:0] t;
    	begin
    		if(a == 4'b0)
    			addFunc = 1'b0;
    		else begin
    			t = a - 1'b1;
    			addFunc = a + addFunc(t);
    		end
    	end
    endfunction
    

    最后,需要提醒大家注意的是,函数的抽象级别比较高,它的编程思路更像是软件而不是硬件,因此一般多用于仿真时使用,具体设计FPGA时,如果需要重复使用某一个功能,完全可以通过模块实例化的方式来实现。

    Verilog模块说明语句

    模块说明语句的关键字是specify,它主要用来说明模块的一些时延信息。它的语法如下:
    specify
    <specparam_declarations> //一些参数定义
    <timing_constraint_checks> //设置一些时序检查选项
    <simple_pin-to-pin_path_delay> //设置模块中组合逻辑管脚到管脚的时间延迟
    <edge-sensitive_pin-to-pin_path_delay> //设置模块中时序逻辑时钟相关的时间延迟
    <state-dependent_pin-to-pin_path_delay> //条件延迟语句,类似条件生成语句
    endspecify
    一个简单的例子如下:
    specify
      specparam d_to_q =9;
      specparam clk_to_q =11;
      (d=>q) = d_to_q;
      (clk=>q) = clk_to_q;
    endspecify
    一般来说,各个FPGA厂商一般会针对自己的根据硬件相关的一些原语编写specify,这样我们才能够对我们的设计进行时序仿真或者时序分析,因此基本上我们不需要在自己设计的模块中编写specify。所以本小节仅对模块说明语句进行一些简单介绍,让大家对specify有个概念,做个了解即可。

    Verilog的串行语句

    串行语句的执行思路是顺序执行的,一般高级编程语言中的语句执行方式都是顺序执行的,例如c语言,由此可见,顺序执行的语句更容易帮助我们来表达我们的设计思想,尤其是使描述时序逻辑变得容易。所以,虽然FPGA的设计思路都是并行的,module中仅支持并行语句的调用,但是为了方便设计者表达自己的思想,尤其是表达时序逻辑的思想,Verilog中的一些并行语句中的子语句体允许是顺序执行的,例如always。那么到底Verilog语言里面有哪些串行语句可以供我们使用呢?以always为例描述如下:

    always@(...)
    begin
    		<Verilog阻塞赋值语句>;
    		<Verilog非阻塞赋值语句>;
    		<Verilog条件语句>;
    		<Verilog循环语句>;
    		<Verilog等待语句>;
    		<Verilog函数调用语句>;
    		<Verilog任务调用语句>;
    end
    

    Verilog阻塞赋值语句

    使用阻塞赋值操作符对变量进行赋值的语句叫阻塞赋值语句。一般来说,如果你认为你描述的这个变量在FPGA硬件中对应连线,那么你就应该使用阻塞赋值语句。使用阻塞赋值符号的赋值语句,一定要等到赋值行为结束之后才会开始执行下一条程序,因此阻塞赋值语句的书写顺序改变会引起综合或者仿真的问题。举例如下:
    always@(c, d) begin
    b = c & d;
    a = ~ b;
    end
    若赋值语句顺序颠倒会引起仿真的问题。

    Verilog非阻塞赋值语句

    使用非阻塞赋值操作符对变量进行赋值的语句叫非阻塞赋值语句。一般来说,如果你认为你描述的这个变量在FPGA硬件中对应寄存器等存储记忆单元,那么你就应该使用非阻塞赋值语句。使用非阻塞赋值符号的赋值语句,在赋值行为未完成之前就会开始执行下一条程序,也正是因为如此,所以非阻塞赋值语句的书写顺序是无所谓的。举例如下:
    always@(posedge clk) begin
    b <= c & d;
    a <= ~ b;
    end
    赋值语句顺序颠倒无所谓。
    如果无视变量对应的硬件结构而乱用赋值符号的话,会造成非常大的隐患。

    Verilog条件语句

    条件语句是一种典型的串行语句。Verilog中有两类条件语句——带优先级条件语句和无优先级条件语句。其中优先级条件语中的各个条件分支是具有优先级的,且分支优先级按照书写顺序从高至低,代表为if条件语句;而无优先级条件语句中,各个分支间的地位是等同的,代表为case条件语句。除了if和case语句外,Verilog还支持casex和casez两种衍生的无优先级条件语句,分别介绍如下:

    if条件语句

    if条件语句的完全语法如下:

    if (<condition>) begin
    <statement>;
    end
    else if (<condition>) begin
    <statement>;
    end
    else begin
    <statement>;
    end
    

    其中的 else if和else分支都不是必须的,可以根据具体情况来确定。以求A、B、C三者中的最大值为例描述如下:

    if (A >= B and A >= C)
    	max = A;
    else if (B >= C)
    	max<= B;
    else
    	max <= C;
    

    为什么说if条件语句是具有优先级的条件语句呢?需要从两个方面来说:
    第一,从语句的功能描述来分析。如果要描述上述求最大值的例子,我们可以这样翻译代码:首先,判断数A是不是大于等于B和C,如果成立,则最大值是A,结束判断;否则说明A不是最大值,那么这时候只需判断数B是不是大于等于C,如果成立,则最大值是B,判断结束;否则,由于之前已经得出A、B两数都不是最大值,那么最大值只能是C了。由此可见,每一个分支的判断都是建立在写在它之前的所有分支的基础上的。
    第二,从硬件实现上来说。上述求最大值的例子,对应到门级电路上,肯定是从A到max之间的路径最短,即所经过的逻辑门最少,而从B到max之间的路径次之,从C到max之间的路径最长。关于门级实现可以参考如下示意图:
    在这里插入图片描述
    由此可见,基于优先级条件语句的特点,如果我们知道A、B、C三个数中最大值的概率是B大于C大于A,那么我们应该把对B的判断放在第一个分支,然后C放在第二个分支,而A放在最后一个分支。这样,今后的仿真效率会更高,且对于具体的FPGA实现,也能保证最短路径得到最充分的利用,这样芯片即使工作在比较恶劣的环境下,也能保证故障率达到最低。

    case条件语句

    case条件语句的完全语法如下:

    case (<expression>)
    	<constant-value1> : 
    begin
    			<statements>;
    		end
    	<constant-value2> : 
    begin
    			<statements>;
    		end
    	<other branchs>	
    	default :
    begin
    			<statements>;
    		end
    endcase
    其中,<constant-value>的值必须互相不同,以四选一多路选择器为例描述如下:
    case (sel)
    2’b00 : data = channel0; 
    2’b01 : data = channel1; 
    2’b10 : data = channel2;
    2’b11 : data = channel3;
    default : data = channel0;
    endcase
    

    上述例子中的分支已经覆盖完全,但是还是有一个default分支,这虽然有些画蛇添足,但确是一个编程的好习惯,请大家注意!
    为什么说case条件语句是无优先级的条件语句呢?也需要从两方面来说:
    第一,从语句的功能描述来分析。如果要描述上述多路选择器的例子,我们可以这样翻译代码:如果sel等于2’b00,那么选择第一路输出;如果sel等于2’b01,那么选择第二路输出;如果sel等于2’b10,那么选择第三路输出;如果sel等于2’b11那么选择第四路输出。可见这四个分支的判断之间没有任何相互关系,也互不为前提。
    第二,从硬件实现上来说。上述多路复用器的例子,对应到门级电路上,无论是channel0~3中的任何一个,到data的路径都是等长的。关于门级实现可以参考如下示意图:
    在这里插入图片描述
    由此可见,在使用无优先级条件语句时,分支的顺序是无关的,不会影响电路的最终实现。

    if与case的对比

    为了进一步说明优先级条件语句与非优先级条件语句之间的区别,我们用if条件语句重写上节中四选一多路选择器的例子如下;

    	if(sel == 2'b00)
    		data = channel0;
    	else if(sel == 2'b01)
    		data = channel1;
    else if (sel == 2'b10)
    		data = channel2;
    	else
    		data = channel3;
    

    关于其门级实现可参考如下电路图:
    在这里插入图片描述
    可见,此时,channel0~3到data的路径长度就不一致了,最短的为一个两输入复用器延迟,最大的为3个两输入复用器延迟。当然,由于上图并不是最简形式,所以此处我们没必要深究它与【case条件语句】小节中的例子到底孰优孰劣,但是请注意,由于目前的编译器都会对我们的代码有一定优化作用,因此有时候if和case也可能会综合成为一样的电路。

    case语句中的判断表达式有可能出现的情况比较多,但是分支却有可能没有那么多,因此下面介绍一些case的变形写法,能够更加方便我们去描述电路。

    case语句的一些变形

    首先,利用特殊的“或”符号——“,”来简化代码,例如,要构建一个三输入的多路复用器,可以描述如下(当然,这并不是最优描述形式):

    case (sel)
    2’b00 : data = channel0; 
    2’b01 : data = channel1; 
    2’b10 ,
    2’b11 : data = channel2;
    default : data = channel0;
    endcase
    

    其次,case的常量和表达式还可以互换位置,例如

    reg sel;
    case (2'b00)
    sel : data = channel0; 
    default : data = channel1;
    endcase
    

    case、casex与casez

    在Verilog语法中,case的比较是十分高效的,但它的匹配成功要求所有位上的逻辑值必须精确相等。于是,Verilog又提供了casex与casez两种语法结构作为补充,它们和case的语法结构相同,只不过分别以casex和casez开头而已。这样,在比较的时候就可以引入不关心位,从而能够达到简化代码的效果。在【本篇->编程语法->Verilog基本语法->Verilog数据类型->Verilog四值逻辑系统】小节,我们介绍了Verilog中的四种逻辑形式:0、1、X、Z,那么,对于casex来说,它会将X、Z视为“不关心位”;而对于casez来说,它会将Z视为“不关心位”。
    在Verilog中,我们可以用“?”来表示“不关心位”,讨论如下:

    条件表达式中有“不关心位”

    举例说明如下:
    reg a;
    case (a)
    1’b0 : statement1;
    1’b1 : statement2;
    1’bx : statement3;
    1’bz : statement4;
    endcase
    上例中,若a = 1’b0或1’b1,那么statement1或statement2将会执行;若我们令a = ?,那么statement4将会执行,因为语法认为“?”等于Z状态。

    reg a;
    casez (a)
    1’b0 : statement1;
    1’b1 : statement2;
    1’bx : statement3;
    1’bz : statement4;
    endcase
    上例中,若a = 1’b0、1’b1或1’bx,那么statement1、statement2或statement3将会执行;但是若a = ?或者1’bz,那么statement1会执行,因为此时casez将这两种值视为无关状态,会直接执行第一条语句,所以statement4永远得不到执行。

    reg a;
    casex (a)
    1’b0 : statement1;
    1’b1 : statement2;
    1’bx : statement3;
    1’bz : statement4;
    endcase
    上例中,若a = 1’b0或1’b1,那么statement1或statement2将会执行;但是若a = ?或者1’bx、1’bz,那么statement1会执行,因为此时casex将这三种值视为无关状态,会直接执行第一条语句,所以statement3、statement4永远得不到执行。

    常数项中有“不关心位”

    举例如下:
    case (sel)
    3’b1?? : data0 = channel0;
    3’b01? : data0 = channel1;
    default : data0 = channel2;
    endcase
    由于case要求精确的匹配,所以无论当sel是什么情况,都只能执行default语句,因此data0只能取到channel2的值。

    casex (sel)
    	3'b1z? : data1 = channel0; 
    	3'b01x : data1 = channel1; 
    	default : data1 = channel2;
    

    endcase
    由于casex将?、X、Z均视为“不关心位”,因此,sel从3’b1003’b111都能匹配3’b1z?,而sel从3’b0103’b011都能匹配3’b01x,而3’b000、3’b001什么都不匹配,因此data1可以取到channel0、channel1和channel2的所有值。

    casez (sel)
    	3'b1z? : data2 = channel0; 
    	3'b01x : data2 = channel1; 
    	default : data2 = channel2;
    

    endcase
    由于casez将?、Z均视为“不关心位”,因此,sel从3’b1003’b111都能匹配3’b1z?,而sel从3’b0103’b011却不能匹配3’b01x,再加上3’b000、3’b001也什么都不匹配,因此data2可以取到channel0和channel2的值,却没有办法通过匹配获得channel1的值。
    上述几个例子如果写在一个module中,我们可以通过其综合后的电路来更加形象的理解:

    要想用case来实现上例casex实现的优先级译码功能,最优的情况下可以写成这样:
    case (sel)
    3’b001 : data0 = channel2;
    3’b010,
    3’b011 : data0 = channel1;
    default : data0 = channel0;
    endcase

    最后,需要说明的一点是,casex和casez中,可以通过使用不关心位来实现代码的简化或一些特殊的逻辑功能,例如优先级译码器。但是在其他情况下请避免使用,因为casex和casez的很多用法都只能停留在仿真阶段。

    Verilog循环语句

    Verilog中的循环语句有很多种,包括for循环、while循环、repeat循环以及forever循环等。这些循环语法中除了for循环有时候可以用来帮助我们简化一些代码的编写外,基本都是主要用于仿真激励的设计,因此本小节主要介绍一下Verilog中的for循环,剩下的将会在【功能仿真篇->仿真语法->Verilog Test Fixture】章节中介绍。
    for循环的语法为:
    integer ; //递减
    for ( = <initial_value>; >= <final_value>; =-1) begin
    ;
    end
    或者
    integer ; //递增
    for ( = <initial_value>; <= <final_value>; =+1) begin
    ;
    end
    例如,如果我们要将一个矢量信号高低位颠倒的赋给另一个矢量信号,可以用for循环简便的表述如下:
    integer i;
    for (i = 7; i >= 0; i=i+1) begin
    a[i] <= b[7-i];
    end
    注意,在描述设计时,for循环一般不应该进行功能描述,而应该只进行结构描述。否则,由于for循环抽象级别比较高,编译器不一定能够正确给对应的实现电路,而且有时候很可能就不存在能够对应该功能的电路。

    Verilog等待语句

    Verilog中有三种等待语句,分别介绍如下:

    事件等待

    事件等待的语法如下:
    @( or or … )
    每个always程序块中都必有一个事件等待语法,除此以外,事件等待语法还可以位于always程序块中,此时的always程序块主要是用于仿真。

    直接时间等待

    直接时间等待的语法如下:
    #

    表达式等待语句

    表达式等待语句的语法如下:
    wait ();
    当为真的时候程序才往下执行,它也主要用于仿真。

    由于等待语句主要用于仿真结构中,所以详情请参阅【功能仿真篇->仿真语法->Verilog Test Fixture】小节。

    Verilog函数调用语句

    函数调用语句即可以用在并行语句中,也可以用在串行语句中。

    Verilog任务调用语句

    任务即是task,它的语法如下:
    task <task_name>;
    input <input_name>;
    <more_inputs>
    output <output_name>;
    <more_outputs>

    begin
    ;
    end
    endtask
    关于任务调用有以下几点说明:
    1、任务中有输入端口也有输出端口,所以它的调用是通过输入端口将数据传入任务中,然后从输出端口得到结果,所以任务可以同时有多个输出,这点与函数不同;
    2、中,只能够声明寄存器类型的变量;
    3、任务中可以使用阻塞赋值也可以使用非阻塞赋值,具体要看调用任务的always是在描述时序逻辑还是组合逻辑;
    4、任务调用的时候只能使用位置赋值,因此需要严格按照端口的顺序罗列变量;
    5、任务调用只能用在串行语句中;
    6、任务中可以调用别的任务;
    7、任务支持递归,不过此情况一般用于仿真;
    8、任务中可以调用函数。

    举例如下:
    示例一:任务中的阻塞赋值与非阻塞赋值
    task bufTask;
    input a;
    output b;
    begin
    b = a;
    end
    endtask
    task regTask;
    input a;
    output reg b;
    begin
    b <= a;
    end
    endtask
    always@*
    bufTask(a,b);
    always@(posedge clk)
    regTask(a,b);
    示例二:任务调用任务
    task andTask;
    input a,b;
    output c;

    	reg t;
    	begin
    		t = bufFunc(a);
    		c = t & b;
    	end
    endtask
    示例三:任务递归
    

    task addTask ;
    input [3:0] a;
    output [3:0] b;
    reg [3:0] t,h;
    begin
    if(a == 4’b0)
    b = 4’b0;
    else begin
    t = a - 1’b1;
    addTask(t, h);
    b = h + 1’b1;
    end
    end
    endtask
    最后,需要提醒大家注意的是,任务和一般也多用于仿真时使用,虽然任务的描述跟模块有些类似,但是具体在设计FPGA时,如果需要重复使用某一个功能,完全可以通过模块实例化的方式来实现。

    更多相关内容
  • AD7606 高精度16位ADC芯片 ...【无需负电源,无需前端模拟运放电路,可直接接传感器输出输入范围正负5V,正负10V。可通过IO控制量程。 分辨率 16位。 最大采样频率 200Ksps。 支持8档过采样设置(可以有效降低抖动)
  • LFSR主要用于通信加扰解...线性反馈移位寄存器(LFSR)是内测试电路中最基本的标准模块结构,既用作伪随机测试码产生器,也作为压缩测试结果数据的特征分析器。 一个n阶的LFSR由n个触发器和若干个异或门组成。在实际...

    文章部分转自https://blog.csdn.net/yongan1006/article/details/8716456

    LFSR主要用于通信加扰解扰;CRC主要用于通信传输数据校验。

    一 、LFSR

    线性反馈移位寄存器(LFSR)是内测试电路中最基本的标准模块结构,既用作伪随机测试码产生器,也作为压缩测试结果数据的特征分析器。

    一个n阶的LFSR由n个触发器和若干个异或门组成。在实际应用当中,主要用到两种类型的LFSR,即异或门外接线性反馈移位寄存器(IE型LFSR,图1)和异或门内接线性反馈移位寄存器(EE型LFSR,图2)。其中g0 g1g2 gn为’0’或’1’,Q1 Q2Q3 Qn为LFSR的输出,M(x)是输入的码字多项式,如M(x)=x4+ x1+1,表示输入端的输入顺序为11001,同样,LFSR的结构也可以表示为多项式G(x),称为生成多项式:

    G(x)=gn*xn+ …+g1*x1+g0;

     

    LFSR的工作原理以及LFSR在CRC上的应用


                                                                                      图1 IE型LFSR

    LFSR的工作原理以及LFSR在CRC上的应用

                                                                                      图2 EE型LFSR

    需要注意的是一位寄存器的初始值不能为全0;

    二 、CRC

    循环冗余校验码(CRC)的基本原理是:在K位信息码后再拼接R位的校验码,整个编码长度为N位,因此,这种编码又叫(N,K)码。对于一个给定的(N,K)码,可以证明存在一个最高次幂为N-K=R的多项式G(x)可以使整个编码被除余数为0。根据G(x)可以生成K位信息的校验码,而G(x)叫做这个CRC码的生成多项式。校验码的具体生成过程为:假设发送信息用信息多项式C(X)表示,将C(x)左移R位,则可表示成C(x)*2的R次方,这样C(x)的右边就会空出R位,这就是校验码的位置。通过C(x)*2的R次方除以生成多项式G(x)得到的余数就是校验码。

    通过CRC的生成原理知道CRC的检验码生成是通过除法得到,由此联想到可以通过LFSR来产生校验码。

    假设原信息码子多项式为

    LFSR的工作原理以及LFSR在CRC上的应用

     

    生成多项式为

      

    LFSR的工作原理以及LFSR在CRC上的应用

     

    那么CRC的码字为LFSR的工作原理以及LFSR在CRC上的应用 ,使用用LFSR电路来进行实现,将M(x)向左移r位在电路中的意义即为输入完信息码后再输入r个0,所以在电路上的表现就如图5所示。

    LFSR的工作原理以及LFSR在CRC上的应用

     

                                                                             图5 使用LFSR来产生CRE校验码

    需要注意的是移位寄存器的初始值不同结果是不一致的。

    三、并行实现CRC校验

    上面介绍的CRC校验主要是用于单比特流的校验,但是很多实际应用中位宽很大的一组数据需要做CRC校验的情况时以上一位寄存器是不能实现的。例如M位宽的并行数据,我们可以根据移位前的N位寄存器值和M位并行数据的位宽数据做逻辑运算计算出(N+1)阶的CRC值。

    代码生成工具网站:https://www.easics.be/webtools/crctool

    此网站可以生成Verilog和VHDL的逻辑代码,如果是时序电路把逻辑电路可以修改成时序电路。

    生成代码的“Data”是每次输入位宽的数据;“CRC”是初始CRC值和上次生成的CRC。

    例如:生成多项式是x^16+x^15+x^2+1,初始值为全1;

    生成代码为

    
    // Copyright (C) 1999-2008 Easics NV.
    // This source file may be used and distributed without restriction
    // provided that this copyright statement is not removed from the file
    // and that any derivative work contains the original copyright notice
    // and the associated disclaimer.
    //
    // THIS SOURCE FILE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS
    // OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
    // WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
    //
    // Purpose : synthesizable CRC function
    //   * polynomial: x^16 + x^15 + x^2 + 1
    //   * data width: 16
    //
    // Info : tools@easics.be
    //        http://www.easics.com
    
    module CRC16_D16;
    
      // polynomial: x^16 + x^15 + x^2 + 1
      // data width: 16
      // convention: the first serial bit is D[15]
      function [15:0] nextCRC16_D16;
    
        input [15:0] Data;
        input [15:0] crc;
        reg [15:0] d;
        reg [15:0] c;
        reg [15:0] newcrc;
      begin
        d = Data;
        c = crc;
    
        newcrc[0] = d[15] ^ d[13] ^ d[12] ^ d[11] ^ d[10] ^ d[9] ^ d[8] ^ d[7] ^ d[6] ^ d[5] ^ d[4] ^ d[3] ^ d[2] ^ d[1] ^ d[0] ^ c[0] ^ c[1] ^ c[2] ^ c[3] ^ c[4] ^ c[5] ^ c[6] ^ c[7] ^ c[8] ^ c[9] ^ c[10] ^ c[11] ^ c[12] ^ c[13] ^ c[15];
        newcrc[1] = d[14] ^ d[13] ^ d[12] ^ d[11] ^ d[10] ^ d[9] ^ d[8] ^ d[7] ^ d[6] ^ d[5] ^ d[4] ^ d[3] ^ d[2] ^ d[1] ^ c[1] ^ c[2] ^ c[3] ^ c[4] ^ c[5] ^ c[6] ^ c[7] ^ c[8] ^ c[9] ^ c[10] ^ c[11] ^ c[12] ^ c[13] ^ c[14];
        newcrc[2] = d[14] ^ d[1] ^ d[0] ^ c[0] ^ c[1] ^ c[14];
        newcrc[3] = d[15] ^ d[2] ^ d[1] ^ c[1] ^ c[2] ^ c[15];
        newcrc[4] = d[3] ^ d[2] ^ c[2] ^ c[3];
        newcrc[5] = d[4] ^ d[3] ^ c[3] ^ c[4];
        newcrc[6] = d[5] ^ d[4] ^ c[4] ^ c[5];
        newcrc[7] = d[6] ^ d[5] ^ c[5] ^ c[6];
        newcrc[8] = d[7] ^ d[6] ^ c[6] ^ c[7];
        newcrc[9] = d[8] ^ d[7] ^ c[7] ^ c[8];
        newcrc[10] = d[9] ^ d[8] ^ c[8] ^ c[9];
        newcrc[11] = d[10] ^ d[9] ^ c[9] ^ c[10];
        newcrc[12] = d[11] ^ d[10] ^ c[10] ^ c[11];
        newcrc[13] = d[12] ^ d[11] ^ c[11] ^ c[12];
        newcrc[14] = d[13] ^ d[12] ^ c[12] ^ c[13];
        newcrc[15] = d[15] ^ d[14] ^ d[12] ^ d[11] ^ d[10] ^ d[9] ^ d[8] ^ d[7] ^ d[6] ^ d[5] ^ d[4] ^ d[3] ^ d[2] ^ d[1] ^ d[0] ^ c[0] ^ c[1] ^ c[2] ^ c[3] ^ c[4] ^ c[5] ^ c[6] ^ c[7] ^ c[8] ^ c[9] ^ c[10] ^ c[11] ^ c[12] ^ c[14] ^ c[15];
        nextCRC16_D16 = newcrc;
      end
      endfunction
    endmodule
    

    仅供参考的时序电路为:

    module CRC_16(
    	input clk,
    	input reset,
    	input sync,
        input [15:0] Data,
    	output [15:0] newcrc
        );
    
    	
    	
        reg [15:0] d;
       // wire [15:0] newcrc;
    	reg [15:0] c;
    	reg r_sync;
    	always@(posedge clk)
    		r_sync<=sync;
    	
    	always@(posedge clk or posedge reset) begin
    		if(reset) begin
    			d<=16'd0;
    			c<=16'd0;
    		end
    		else if(sync) begin
    			d<=16'd0;
    			c<=16'd0;
    		end
    		else if(r_sync) begin
    			d <= Data;
    			c <= 16'hFFFF;
    		end
    		else begin
    			d <= Data;
    			c <= newcrc;
    		end
    
    	end
    
    	assign newcrc[0] = d[15] ^ d[13] ^ d[12] ^ d[11] ^ d[10] ^ d[9] ^ d[8] ^ d[7] ^ d[6] ^ d[5] ^ d[4] ^ d[3] ^ d[2] ^ d[1] ^ d[0] ^ c[0] ^ c[1] ^ c[2] ^ c[3] ^ c[4] ^ c[5] ^ c[6] ^ c[7] ^ c[8] ^ c[9] ^ c[10] ^ c[11] ^ c[12] ^ c[13] ^ c[15];
    	assign newcrc[1] = d[14] ^ d[13] ^ d[12] ^ d[11] ^ d[10] ^ d[9] ^ d[8] ^ d[7] ^ d[6] ^ d[5] ^ d[4] ^ d[3] ^ d[2] ^ d[1] ^ c[1] ^ c[2] ^ c[3] ^ c[4] ^ c[5] ^ c[6] ^ c[7] ^ c[8] ^ c[9] ^ c[10] ^ c[11] ^ c[12] ^ c[13] ^ c[14];
    	assign newcrc[2] = d[14] ^ d[1] ^ d[0] ^ c[0] ^ c[1] ^ c[14];
    	assign newcrc[3] = d[15] ^ d[2] ^ d[1] ^ c[1] ^ c[2] ^ c[15];
    	assign newcrc[4] = d[3] ^ d[2] ^ c[2] ^ c[3];
    	assign newcrc[5] = d[4] ^ d[3] ^ c[3] ^ c[4];
    	assign newcrc[6] = d[5] ^ d[4] ^ c[4] ^ c[5];
    	assign newcrc[7] = d[6] ^ d[5] ^ c[5] ^ c[6];
    	assign newcrc[8] = d[7] ^ d[6] ^ c[6] ^ c[7];
    	assign newcrc[9] = d[8] ^ d[7] ^ c[7] ^ c[8];
    	assign newcrc[10] = d[9] ^ d[8] ^ c[8] ^ c[9];
    	assign newcrc[11] = d[10] ^ d[9] ^ c[9] ^ c[10];
    	assign newcrc[12] = d[11] ^ d[10] ^ c[10] ^ c[11];
    	assign newcrc[13] = d[12] ^ d[11] ^ c[11] ^ c[12];
    	assign newcrc[14] = d[13] ^ d[12] ^ c[12] ^ c[13];
    	assign newcrc[15] = d[15] ^ d[14] ^ d[12] ^ d[11] ^ d[10] ^ d[9] ^ d[8] ^ d[7] ^ d[6] ^ d[5] ^ d[4] ^ d[3] ^ d[2] ^ d[1] ^ d[0] ^ c[0] ^ c[1] ^ c[2] ^ c[3] ^ c[4] ^ c[5] ^ c[6] ^ c[7] ^ c[8] ^ c[9] ^ c[10] ^ c[11] ^ c[12] ^ c[14] ^ c[15];
    
    
    endmodule
    

    testbench

    module tb_crc_16;
    
    	// Inputs
    	reg clk;
    	reg reset;
    	reg sync;
    	reg [15:0] Data;
    
    	// Outputs
    	wire [15:0] c;
    
    	// Instantiate the Unit Under Test (UUT)
    	CRC_16 uut (
    		.clk(clk), 
    		.reset(reset), 
    		.sync(sync), 
    		.Data(Data), 
    		.newcrc(c)
    	);
    
    	initial begin
    		clk=0;
    		forever #5 clk=~clk;
    	end
    	initial begin
    		reset=1;
    		#30 reset=0;
    	end
    	initial begin
    		sync=1;
    		#50 sync=0;
    	end
    	initial begin
    		#50 Data<=16'h3132;
    		#10 Data<=16'h3334;
    
    	end
          
    endmodule
    
    

    在CRC在线计算器网站http://www.ip33.com/crc.html   设置如下,验证结果:

    展开全文
  • Nand Flash之并行串行

    千次阅读 2019-04-14 16:16:11
    之前我们有讲到NAND Flash与NOR Flash的封装、读取速率、写速率和电路设计等方面的差异,现在我们将会讲到并行串行Flash,主要是串行吧,并行需要讲的很少。 因为串行并行主要区别在于IO口的数量上,因为有些...

    串行Flash与并行Flash

    之前我们有讲到NAND Flash与NOR Flash的封装、读取速率、写速率和电路设计等方面的差异,现在我们将会讲到并行与串行Flash,主要是串行吧,并行需要讲的很少。

    因为串行与并行主要区别在于IO口的数量上,因为有些场景上我不会用到那么大的容量,E而且也不会有那么多的IO引脚,所以串行NOR与NAND Flash应运而生。

    并行与串行NAND Flash

    SERIAL NOR FLASH

    串行NOR Flash支持标准串行外设接口(SPI),双/四路I / O SPI:串行时钟,片选,串行数据I / O0(DI),I / O1(DO),I / O2和I / O3。

    也就是说对于串行NOR Flash而言,支持下面的几种模式。
    
    Standard SPI

    Standard SPI: CLK, /CS, DI, DO

    Dual SPI

    Dual SPI: CLK, /CS, IO0, IO1

    Quad SPI

    Quad SPI: CLK, /CS, IO0, IO1, IO2, IO3

    传输时钟速率

    • 串行NOR Flash 在Standard SPI模式下支持高达133MHz 时钟频率(标准模式);
    • 串行NOR Flash 在Dual SPI模式下支持高达133MHz *2 时钟频率(Dual);
    • 串行NOR Flash 在Quad SPI模式下支持高达133MHz *4 时钟频率(Quad);
    这些传输速率可以胜过标准的异步8和16位并行闪存。
    

    串行NOR Flash常见封装

    如下为SOIC/SOP8封装
    SOP8封装 串行NOR Flash
    引脚介绍
    NOR Flash引脚介绍

    1. IO0 and IO1 are used for Standard and Dual SPI instructions;
    2. IO0 – IO3 are used for Quad SPI instructions, /HOLD (or /RESET) function is only available for Standard/Dual SPI.

    以上引脚如何配置
    1)标准 SPI模式
    连接CS、CLK、DO和DI就可以,WP引脚和HOLD引脚视情况而定,看是要硬件上拉到Vcc还是电阻下拉到地。

    2)Dual SPI模式
    连接CS、CLK、DO(IO1)、DI(IO0),此时注意不是按照DO和DI工作,而是括号内的IO1和IO2。

    3)Quad SPI模式
    CS、CLK、DO(IO1)、DI(IO0)、WP(IO2)、HOLD/Reset/IO3,此时WP与Hold功能全都不能使用。

    标准SPI,双SPI和四线SPI操作
    标准SPI指令使用单向DI(输入)引脚在串行时钟(CLK)输入引脚的上升沿串行写入指令,地址或数据。
    标准SPI还使用单向DO(输出)在CLK下降沿读取器件的数据或状态。
    双SPI和四SPI指令使用双向IO引脚串行写入指令,在CLK的上升沿向器件发送地址或数据,并在CLK的下降沿从器件读取数据或状态。

    电路设计

    片选(/ CS)

    SPI片选(/ CS)引脚用于使能和禁止器件操作。 当/ CS为高电平时,器件被取消选择,串行数据输出(DO或IO0,IO1,IO2,IO3)引脚处于高阻态。取消选择后,器件功耗将处于待机电平,除非内部擦除,编程或写状态寄存器周期正在进行中。当/ CS变为低电平时,将选择器件,功耗将增加到有效电平,并且可以写入指令并从器件读取数据。
    上电后,/ CS必须在接受新指令之前从高电平转换为低电平。/ CS输入必须在上电和断电时跟踪VCC电源电平。如果需要,可以使用/ CS引脚上的上拉电阻来实现此目的。

    写保护(/ WP)

    写保护(/ WP)引脚可用于防止写入状态寄存器。扇区的部分或整个存储器阵列可以受硬件保护,需要与寄存器相互配置。 WP引脚为低电平有效。

    HOLD(/ HOLD)

    / HOLD引脚允许器件在主动选择时暂停。当/ HOLD变为低电平时,/ CS为低电平时,DO引脚将处于高阻态,DI和CLK引脚上的信号将被忽略)。当/ HOLD变为高电平时,器件操作可以恢复。
    当多个设备共享相同的SPI信号时,/ HOLD功能非常有用。 / HOLD引脚为低电平有效。

    Serial Clock (CLK)

    SPI串行时钟输入(CLK)引脚提供串行输入和输出操作的时序。

    复位(/ RESET)

    当它被驱动为低电平至少1μS的时间时,该器件将终止任何外部或内部操作并返回其上电状态。

    电源

    串行Flash的电压也有多种规格,具体的看数据书册就行。

    电路设计

    SPI Nor Flash
    如上图为串行 Nor Flash电路,
    WP引脚连接电阻至Vcc;为了不进行写保护,所以做上拉,但是不悬空是为了在上电时有一个确定的电平状态。
    Hold引脚连接上拉电阻至Vcc;为了不实现数据保持,所以做上拉,但是不悬空是为了在上电时有一个确定的电平状态。
    CS引脚也是上拉至Vcc。上电不选中该器件,只有后续CPU低电平才会选中该器件。

    以下请参考:
    [1].华邦
    [2].复旦微电子

    SERIAL NAND FLASH

    在这里插入图片描述
    注:因为串行NOR Flash与串行NAND Flash所要实现的功能一致,为了减少/简化并行Nand Flash的接口,所以两者的封装等都是一样的,差异只是在于存储器单位存储的结构不同,其余都是一样的,所以大家知道了串行NOR Flash的介绍后,同样就理解串行NAND Flash了。

    20194.14
    于深圳

    展开全文
  • 在一些电路中,我们需要对很多器件进行控制,但是...在这种情况下,采用串行并行芯片是一个很好的选择,通过串行的数据输入实现对并行器件的控制。 74HC595是一个典型的串行并行芯片,它的实物如下图所示 ...

    一、74HC595介绍

            在一些电路中,我们需要对很多器件进行控制,但是我们的控制单元(比如单片机)的引脚数量有限,没有足够的引脚对器件进行控制。在这种情况下,采用串行转并行芯片是一个很好的选择,通过串行的数据输入实现对并行器件的控制。

            74HC595是一个典型的串行转并行芯片,它的实物如下图所示

    它的引脚定义如下图所示,

    它的各引脚的作用

    (1)A:串行数据输入。

    (2)SHIFT CLOCK:串行时钟输入。

    (3)RESET:芯片复位。

    (4)LATCH CLOCK:锁存输入。

    (5)OUTPUT ENABLE:输出使能端。

    (6)QA~QH:并行数据输出。

    (7)SQH:串行数据输出。

    它的内部电路如下图所示

            由上图可以看出,74HC595的主体部分由16个锁存器组成,左侧8个锁存器串联在一起,每输入一个串行时钟脉冲,锁存器的输出值向下进行一次移位,并通过SQH引脚输出。右侧的8个锁存器有LATCH脉冲控制可以将左侧锁存器的输出锁存并通过芯片的并行输出引脚输出。

    二、应用举例

            在这个例子中,我们使用三片74HC595对一个8X8双色点阵进行控制。

            双色点阵的实物图如下图所示

            双色点阵的内部结构原理图如下图所示,整个点阵由64个红色发光管和64个绿色发光管组成。双色点阵的显示原理与数码管类似,需要通过扫描的方式来实现。点阵的5、6、7、8、9、10、11、12引脚为8列绿色发光管的负极,20、19、18、17、16、15、14、13引脚为8列红色发光管的负极,24、23、22、21、4、3、2、1引脚为8行红色和绿色发光管的公共正极。显示时,需要进行逐行扫描。

    为了对这个双色点阵进行控制,设计了下面的电路。

    在电路中,三片74HC595串联在一起,串行数据有U45的DATA引脚输入,U45的SQH引脚接到U46的DATA引脚,U46的SQH引脚接到U47的DATA引脚,这样由U45输入24位(即3个字节)的串行数据,之后锁存,就可以将这3个字节的数值分别由U45、U46、U47输出。其中输入的第一个字节由U47输出,第二个字节由U46输出,第三个字节由U45输出。U47用来控制两种颜色发光管的公共阳极,U45和U46分别用来控制绿色和红色发光管的阴极。

     

     

     

    展开全文
  • 前言:最近再搞个PCIe的高速串行总线的项目,关于串行并行数据的传输到底谁更快,分别有哪些优势和劣势呢等等一些问题现在做个整理,是对自己学习的一个记录,同时呢,也为刚刚想入门的或者对高速串行总线有些误解...
  • 并行串行传输,串行高速传输

    万次阅读 多人点赞 2018-03-24 17:17:05
    串行只有一根数据线,不存在信号线之间的串扰,而且串行还可以采用低压差分信号,可以大大提高它的抗干扰性,所以可以实现更高的传输速率,尽管并行可以一次传多个数据位,但是时钟远远低于串行,所以目前串行传输...
  • 8251A 并行传送和串行传送

    千次阅读 2018-10-01 08:41:32
    20. 8251A 芯片中实现并行数据转换为串行数据的部件 一、数据传送 并行传送 数据在多条并行 1 位宽的传输线上同时由源传送到目的。以 1 字节为例,在并行传送中, 1 字节数据通过 8 条并行传输线同时由源传送到目的...
  • 为什么USB要用串行通信而不是用并行呢?并行接口速度比串行接口速度快,这是若干年前的情况了。 在实际时钟频率比较低的情况下,并口因为可以同时传输若干比特,速率确实比串口快。 但是,随着技术的发展,时钟频率...
  • 在微机控制系统中,为了实现对生产过程的控制,要将对象的各种...该连接通道被称为输入输出通道,它包括模拟量输入通道、模拟量输出通道、数字量输入通道和数字量输出通道,其组成如图1所示。自动控制网www.eadia...
  • 总线是一种内部结构,它是cpu、内存、输入输出设备传递信息的公用通道,主机的各个部件通过总线相连接,外部设备通过相应的接口电路再与总线相连接,从而形成了计算机硬件系统。 在计算机系统中,各个部件之间...
  • 微机与I/O设备的接口按照数据传送方式的不同,可分为:并行接口和串行接口两种。 6.1 串行通信和串行接口 6.1.1 串行通信涉及的几个问题 串行通信:将数据分解成二进制位,用一条数据线,一位一位的顺序传送...
  • 2021考研408计算机组成原理:串行加法器和并行加法器2020-01-14 20:05|考研集训营统考408中的计算机组成原理,在试卷中所占45分。因此,备考2021计算机考研学子们,一定要一丝不苟地认真复习该科目知识。下面,文都...
  • 为什么说串行并行速度快?

    千次阅读 2018-09-20 22:15:07
    并行接口速度比串行接口速度快,这是若干年前的情况了。 在实际时钟频率比较低的情况下,并口因为可以同时传输若干比特,速率确实比串口快。 但是,随着技术的发展,时钟频率越来越高,并行导线之间的相互干扰越来越...
  • 我们都知道通信从大的方面有两种:串行并行串行的最大优点是占用总线少,但是传输速率低;并行恰恰相反,占用总线多,传输速率高。市面上有很多这样的芯片,有串入并出的(通俗讲就是 一个一个进,最后一块出来...
  • 第五节 输入输出接口如第六章所述,CPU要通过接口电路才能和外设交换信息.一般在接口电路中要有输入...随着大规模集成电路的发展,生产了许多通用的可编程接口芯片,这些接口芯片按数据传送方式可分成 并行接口和串行接...
  • 串行移位寄存器原理与结构分析

    千次阅读 2021-05-04 15:15:32
    1 串转并功能,通过移位寄存器和输出锁存器实现 2 FPGA需要通过74HC595这个芯片把16位的数据(sel+seg)变为并行端口用来驱动数码管 3 3.3V供电情况下,取SHCP时钟频率位12.5MHz(50-20ns,25-40ns,12.5-80ns),让...
  • 输入/输出系统

    千次阅读 2022-04-21 19:21:32
    I/O接口的类型 按照数据传送的方式,可以分为并行接口(一字节或一个字的所有位同时传送)和串行接口(一位一位地传送),接口要完成数据格式的转换。 (这里所说的数据传送方式是指外设和接口一侧的传送方式,而在...
  • 计算机组成原理——输入输出系统

    千次阅读 2022-04-05 21:38:11
    输入输出系统 5.1 概述 一、输入输出系统的发展概况 早期 分散连接 CPU 和 I/O设备串行工作 程序查询方式 接口模块和 DMA 阶段 总线连接 CPU 和 I/O设备 并行 工作 中断方式 DMA 方式 具有通道结构的...
  • ①硬盘、光盘――即可输入、又可输出的设备(有的教材称为:外存设备) ②鼠标、键盘――输入设备 ③显示器、打印机――输出设备 ④可统称“外部设备” 2.主机如何与l/O设备进行交互? (1)I/O接口:又称I/O控制器(I/...
  • 学习目标:重点分析I/O设备与主机交换信息的三种控制方式(程序查询、中断和DMA)及其相应的接口功能和组成,对输入输出系统有一个较清晰的认识,加深对整机工作的理解。 5.1.1 输入输出系统的发展概况 输入输出系统的...
  • 本系列文章为哈尔滨工业大学刘宏伟计算机组成原理学习笔记,前面的系列文章链接如下: 计算机组成原理:P1-计算机系统概述 计算机组成原理:P2-系统总线 ...输入输出系统是计算机系统当中种类最多、功能最多、结构最复
  • 本文首先分析了BCD码校验原理,进而从并行串行两种电路结构分析了BCD码校验逻辑。最后提出了一种高效,快速的BCD 码验证电路并对其进行了仿真。 关键词:BCD码、数值计算、并/串行、校验引言微处理器的工作过程是...
  • D是英文Data的意思 之前的文章介绍了RS触发器的内部电路实现,该电路有个缺点,就是需要用R和S两个控制端输入,才能控制Q端的输出,为了减少复杂度,D触发器诞生了,D触发器通过一个输入端,控制一个输出端 回顾一下...
  • 串行总线技术(一)-串行总线结构(以PCIe为例)串行总线的出现在早期的计算机系统中,多数外围设备使用并行总线结构。这些总线包括PCI和PATA(并行ATA)。当通信速率较低时,并行总线结...
  • 输入输出系统

    2019-10-03 13:47:48
    输入输出系统 输入输出系统是计算机系统中的主机与外部进行通信的系统。它由外围设备和输入输出控制系统两部分组成,是计算机系统的重要组成部分。外围设备包括输入设备、输出设备和磁盘存储器、磁带存储器、光盘...
  • 2-24 74H系列与非门(74H00)的电路结构及性能测试 2-25 74S系列与非门(74S00)的电路结构 2-26 CMOS反相器的电路结构 2-27 CMOS反相器的输入保护电路及特性测试 2-28 CMOS与非门 2-29 CMOS或非门 2-30 漏极...
  • 文章目录一、输入输出系统的发展概括1、早期阶段2、接口模块和DMA阶段3、具有通道结构的阶段4、具有I/O处理机的阶段二、输入输出系统的组成1、I/O软件2、I/O硬件三、I/O设备与主机的联系方式四、I/O设备与主机信息...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,095
精华内容 4,038
热门标签
关键字:

并行输入串行输出电路结构