文章目录

  • 1.数字电路系统设计的层次化
  • 2.典型电路设计
    • 2.1加法器树乘法器
    • 2.2Wallace树乘法器
    • 2.3复数乘法器
    • 2.4 FIR滤波器设计
    • 2.5 片内存储器的设计
    • 2.6 FIFO设计
    • 2.7 键盘扫描和编码器
    • 2.8 log函数的Verilog设计
    • 2.9 CORDIC算法的Verilog实现
  • 3.总线控制器设计
    • 3.1UART接口控制器
    • 3.2SPI接口控制器
  • 来源:蔡觉平老师的Verilog课程

1.数字电路系统设计的层次化


串行加法器:

一个四位串行加法器由4个全加器构成。全加器是串行加法器的子模块,而全加器是由基本的逻辑门构成,这些基本的逻辑门就是所说的叶子模块。这个设计中运用叶子模块(基本逻辑门)搭建成子模块(全加器),再用子模块搭建成所需要的电路(串行加法器)。
显然,Bottom-Up的设计方法没有明显的规律可循,主要依靠设计者的实践经验和熟练的设计技巧,用逐步试探的方法最后设计出一个完整的数字系统。系统的各项性能指标只有在系统构成后才能分析测试 此种设计方法常用于原理图的设计中,相比于其它方法此种方法对于实现各个子模块电路所需的时间较短。

使用Top-Down设计方法对一个典型cpu进行设计:

向量点积乘法器:
采用模块层次化设计方法,设计4维向量点积乘法器,其中向量a =(a1,a2,a3,a4);b=(b1,b2,b3,b4)。点积乘法规则:

Verilog代码:

module vector(a1,a2,a3,a4,b1,b2,b3,b4,out); input [3:0] a1,a2,a3,a4,b1,b2,b3,b4;output [9:0] out;wire [7:0] out1,out2,out3,out4; wire [8:0] out5, out6; wire [9:0] out;mul_addtree U1(.x(a1), .y(b1), .out(out1)); mul_addtree U2(.x(a2), .y(b2), .out(out2));mul_addtree U3(.x(a3), .y(b3), .out(out3));mul_addtree U4(.x(a4), .y(b4), .out(out4)); add #(8) U5(a(out1), .b(out2), .out(out5)); add #(8) U6(.a(out3), .b(out4), .out(out6)); add #(9) U7(.a(out5), .b(out6), .out(out));
endmodule
//adder
module add(a,b,out); parameter size=8;input [size-1:0] a,b; output [size:0j out; assign out=a+b;
endmodule
//Multiplier
module mul_addtree(mul_a,mul_b,mul_out); input [3:0] mul_a.mul_b; output [7:0] mul_out;wire [3:0] muI_out; wire [3:0] stored0,stored1,stored2,stored3;wire [3:0] add0l, add23;assign stored3=mul_b[3]?{1'b0,mul_a,3'b0):8’b0; assign stored2=mul_b[2]?{2’b0,mul_a ,2’b0}:8’b0; assign stored1=mul_b[1]?{3'b0,mul_a,1'b0}:8’b0; assign storedo=mul_b[0]?{4’b0,mul_a}:8’b0; assign add01=storedl +stored0; assign add23=stored3+stored2; assign mul_out=add0l +add23;
endmodule

2.典型电路设计

2.1加法器树乘法器

加法器树乘法器的设计思想是”移位后加",并且加法运算采用加法器树的形式。乘法运算的过程是:被乘数与乘数的每一位相乘并且乘以相应的权值,最后将所得的结果相加,便得到了最终的乘法结果。
例:下图是一个4位的乘法器结构,用verilog设计一个加法器树4位乘法器

module mul_addtree(mul_a,mul_b,mul_out);input [3:0] mul_a,mul_b;output [7:0] mul_out;wire [7:0] mul_out;wire [7:0] stored0,stored1,stored2,stored3;wire [7:0] add01,add23;assign stored3 = mul_b[3]?{1'b0,mul_a,3'b0}:8'b0;assign stored2 = mul_b[2]?{2'b0,mul_a,2'b0}:8'b0;assign stored1 = mul_b[1]?{3'b0,mul_a,1'b0}:8'b0;assign stored0 = mul_b[0]?{4'b0,mul_a,0'b0}:8'b0;assign add01=stored1+stored0;assign add23=stored3+stored2;assign mul_out=add01 + add23;
endmodule
module mult_addtree_tb;reg [3:0] mult_a;reg [3:0] mult_b;wire [7:0] mult_out;mul_addtree U1(.mul_a(mult_a), .mul_b(mult_b), .mul_out(mult_out));initialbeginmult_a = a;mult_b = 0;repeat(9)begin#20 mult_a = mult_a + 1;mult_b = mult_b + 1;endendendmodule

流水线结构:
例:下图是一个4位的乘法器结构, 用Verilog设计一个两级流水线加法器树4位乘法器。


两级流水线加法器树4位乘法器结构如图所示,通过在第一级与第二级、第二级与第三级加法器之间插入D触发器组,可以实现两级流水线设计。

module mul_addtree_2_stage(clk,clr,mul_a,mul_b,mul_out); input clk,clr;input [3:0] mul_a,mul_b; output [7:0] mul_out;reg [7:0] add_tmp_1,add_tmp_2,mul_out;wire [7:0] stored0,stored1,stored2,stored3;
assign stored3 = mul_b[3]?{1'b0,mul_a,3'b0}:8'b0;
assign stored2 = mul_b[2]?{2'b0,mul_a,2'b0}:8'b0;
assign stored1 = mul_b[1]?{3'b0,mul_a,1'b0}:8'b0;
assign stored0 = mul_b[0]?{4'b0,mul_a}:8'b0;
always@(posedge clk or negedge clr) beginif(!clr) begin add_tmp_1 <= 8'b0000_0000; add_tmp_2 <= 8'b0000_0000; mul_out <= 8'b0000_0000;endelse begin add_tmp_1 <= stored3 + stored2;add_tmp_2 <= stored1 +stored0;     mul_out <= add_tmp_1 + add_tmp_2;endend
endmodule
module mult_addtree_2_stag_tb;reg clk,clr;reg [3:0] mult_a,mult_b;wire [7:0] mult_out;mul_addtree_2_tage U1(.mul_a(mult_a), .mul_b(mult_b), .mul_out(mult_out), .clk(clk), .clr(clr));initialbeginclk = 0;clr = 0;mult_a = 1;mult_b = 1;#5 clr = 1;endalways #10 clk = ~clk;initialbeginrepeat(5)begin#20 mult_a = mult_a + 1;mult_b = mult_b + 1;endendendmodule

2.2Wallace树乘法器

Wallace树乘法器运算原理如下图,其中FA为全加器HA为半加器。其基本原理是,加法从数据最密集的地方开始,不断地反复使用全加器半加器来覆盖"树"。这一级全加器是一个3输入2输出的器件,因此全加器又称为3-2压缩器。通过全加器将树的深度不断缩减,最终缩减为一个深度为2的树。最后一级则采用一个简单的两输入加法器组成。

module wallace(x,y,out);parameter size=4;input [size-1:0] x,y;output [2*size-1:0] out;wire [size*size-1:0] a; wire [1:0] b0,b1,c0,c1,c2,c3; wire [5:0] add_a,add_b; wire [6:0] add_out;wire [2*size-1 :0] out;assign a={x[3],x[3],x[2],x[2],x[1],x[3],x[1],x[0],x[3],x[2],x[1],x[0],x[2],x[1],x[0],x[0]}& {y[3],y[2],y[3],y[2],y[3],y[1],y[2],y[3],y[0],y[1],y[1],y[2],y[0],y[0],y[1],y[0]};hadd U1(.x(a[8]), .y(a[9]), .out(b0));hadd U2(.x(a[11]), .y(a(a[12]), .out(b1));hadd U3(.x(a[4]), .y(a[5]), .out(c0));fadd U4(.x(a[6]), .y(a[7]), .z(b0[0]),.out(c1)); fadd U5(.x(a[13]), .y(a[14]), . z(b0[1]), .out(c2));fadd U6(.x(b1[0]), .y(a[10]), .z(b1[1], .out(c3));assign add_a = {c3[1],c2[1],c1[1],c0[1],a[3],a[1]); assign add_b ={ a[15],c3[0],c2[0],c1[0],c0[0],a[2]};assign add_out = add_a + add_b;assign out={add_out,a[0]};endmodule
module fadd(x, y, z, out); output [1:0] out;input x,y,z;assign out=x+y+z;
endmodule
module hadd(x, y, out);output [1:0] out;input x.y;assign out=x+y;
endmodule
module wallace_tb; reg [3:0] x, y;wire [7:0] out;wallace m(.x(x), .y(y), .out(out));
initialbeginx=3; y=4;#20 x=2; y=3;#20 x=6; y=8; end
endmodule

2.3复数乘法器


复数乘法器的电路结构如下图所示。将复数x的实部与复数y的实部相乘,减去x的虚部与y的虚部相乘,得到输出结果的实部。将x的实部与y的虚部相乘,加上x的虚部与y的实部相乘,得到输出结果的虚部。

module complex(a,b,c,d,out_real,out_im);input [3:0]a,b,c,d;output [8:0] out_real,out_im; wire [7:0] sub1,sub2,add1,add2; wallace U1(.x(a), .y(c), .out(sub1)); wallace U2(.x(b), .y(d), .out(sub2)); wallace U3(.x(a), .y(d), .out(add1)); wallace U4(.x(b), .y(c), .out(add2));assign out_real=subl - sub2; assign out_im = add1 + add2;
endmodule
 module complex_tb;reg [3:0] a,b,c,d;wire [8:0] out_real;wire [8:0] out_im;complex U1(.a(a), .b(b), .c(c), .d(d), .out_real(out_real), .out_im(out_im));initialbegina=2;b=2;c=5;d=4;#10a=4;b=3;c=2;d=1;#10a=3;b=2;c=3;d=4endendmodule

2.4 FIR滤波器设计

有限冲激响应(FIR)滤波器就是一种常用的数字滤波器,采用对己输入样值的加权和来形成它的输出。其系统函数为:

其中Z-1表示延时一个时钟周期,Z-2表示延时两个时钟周期。
对于输入序列X[n]的FIR滤波器可用下图所示的结构示意图来表示,其中X[n]是输入数据流。各级的输入连接和输出连接被称为抽头,并且系数(b0,b1,…,bn)被称为抽头系数。一个M阶的FIR滤波器将会有M+1个抽头。
通过移位寄存器用每个时钟边沿n(时间下标)处的数据流采样值乘以抽头系数,并将它们加起来形成输出Y[n]。

代码如下:

module FIR(Data_out, Data_in ,dock,reset); //模块FIRoutput [9:0] Data_out;input [3:0] Data_in;input clock,reset;wire [9:0] Data_out;wire [3:0] samples_0,sampies_1 ,samples_2,samples_3,samples_4,samples_5, samples_6, samples_7, samples_8;shift_register U1(.Data_in(Data_in), .clock(ciock), .reset(reset),.samples_0(samples_0), .samples_1(samples_1),.samples_2(samples_2), .samples_3(samples_3),.samples_4(samples_4), .samples_5(samples_5),.samples_6(sam ples_6), .samples_7(samples_7),.samples_8(samples_8));caculator U2(.samples_0(samples_0), .samples_1 (samples_1),.samples_2(samples_2), .sam ples_3(samples_3),.samples_4(samples_4), .samples_5(sam ples_5),.sampIes_6(samples_6), .samples_7(sampies_7),.samples_8(sam pies_B), .Data_out( Data_out));
endmoduleshift_register
module shift_register(Data_in,clock,reset,samples_0,samples_1 ,samples_2,samples_3,samples_4,samples_5,samples_6,samples_7, samples_8); input [3:0] Data_in;input clock,reset;output [3:0] samples_0,samples_1 ,samples_2,samples_3,samples_4, samples_5,samples_6, samples_7,samples_8;reg [3:0] samples_0,samples_l ,samples_2,samples_3,samples_4, samples_5, sam pies 6, sampies_7,samptes_8;
always(posedge clock or negedge reset)beginif(reset)beginsamples_0 <= 4’b0; samples_1 <= 4’b0; samples_2 <= 4’b0; samples_3 <= 4’b0; samples_4 <= 4’b0; samples_5 <= 4’b0; samples_6 <= 4’b0; samples_7 <= 4’b0; samples_8 <= 4’b0;endelsebeginsamples_0 <= Data_in;samples_1 <= samples_0; samples_2 <= samples_1; samples_3 <= samples_2; samples_4 <= samples_3; samples_5 <= samples_4; samples_6 <= samples_5; samples_7 <= samples_6; samples_8 <= samples_7; endend
endmodule//模块caculator
module caculator(sampies_O,samples_i ,samples_2,samples_3,samples_4,samples_5,samples_6, samples_7,samples_8,Data_out);input [3:0] samples_0,samples_1 ,samples_2,samples_3,samples_4,samples_5,samples_6, samples_7,samples_8;output [9:0] Data_out; wire [9:0] Data_out; wire [3:0] out_tmp_1 ,out_tmp_2,out_tmp_3,out_tmp_4,out_tmp_5; wire [7:0] outl,out2,out3,out4,out5;parameter b0=4’b0010; parameter b1=4’b0011; parameter b2=4’b0110; parameter b3=4’b1010; parameter b4=4’b1100; mul_addtree U1(.mul_a(b0),.mul_b(out_tmp_1),.mul_out(out1)); mul_addtree U2(.mul_a(b1),.mul_b(out_tmp_2),.mul_out(out2)); mul_addtree U3(.mul_a(b2),.mul_b(out_tmp_3),.mul_out(out3)); mul_addtree U4(.mul_a(b3),.mul_b(out_tmp_4),.mul_out(out4)); mul_addtree U5(.mul_a(b4),.mul_b(samples_4),.mul_out(out5)); assign out_tmp_1 = samples_0 + samples_8;assign out_tmp_2  =samples_1 + samples7; assign out_tmp_3 = samples_2 + samples_6; assign out_tmp_4 = samples3 + samples_5; assign Data_out = out1 +out2 + out3 + out4  + qout5;
endmodule
//模块FIR_tb
module FIR_tb; reg clock,reset; reg [3:0] Data_in; wire [9:0] Data_out;FIR U1(.Data_out(Data_out), .Data_in(Data_in), .clock(clock), reset(reset)); initialbeginData_in = 0; clock = 0; reset = 1;#10 reset = 0; endalwaysbegin#5 clock <= ~clock;#5 Data_in <= Data_in+1;end
endmodule

2.5 片内存储器的设计

(1)RAM 的 Verilog描述
RAM是随机存储器,存储单元的内容可按需随意取出或存入。这种存储器在断电后将丢失掉所有数据,一般用来存储一些短时间内使用的程序和数据。其内部结构如下所示:

例:用Verilog设计深度为8,位宽为8的单端口RAM。单口RAM,只有一套地址总线,读操作和写操作是分开的。

module ram_single(clk, addm, cs_n, we_n, din, dout);
input clk;   //clock signal
input [2:0] addm;    //address signal
input cs_n;  //chip select signal
input we_n;     //write enable signal
input [7:0] din;    //input data
output[7:O] dout;   //output data
reg [7:0] dout;
reg [7:0] raml [7:0];   //8*8 bites register
aIways(posedge clk)beginif(cs_n)dout <= 8’bzzzz_zzzz;else if(we_n)  //read data dout <= raml[addm];else  //write dataraml[addm] <= din;end
end modulemodule ram single tb;reg clk, we_n, cs_n;reg [2:0] addm;reg [7:0] din;wire [7:0] dout;ram_single U1(.clk(clk),.addm(addm),.cs_n(cs_n),.we_n(we_n),.din(din),.dout(dout));initial beginclk=0; addm=0; cs_n=1; we_n=0; din=0;#5 cs_n=0;#315 we_n=1;end
always #10 clk=~clk;
initialbeginrepeat(7) begin#40 addm=addm+1;din=din+1;end#40 repeat(7)#40 addm=addm-1;end
endmodule


例:用Verilog设计深度为8,位宽为8的双端口RAM。双口RAM具有两套地址总线,一套用于读数据,另一套用于写数据。 二者可以分别独立操作。

module ram_dual(q, addr _n, addr_out, d, we, rd, clk1, clk2);output [7:0] q; //output datainput [7:0] d; //input datainput [2:0] addr_in;    //write data address signalinput [2:0] addr_out;    //output data address signalinput we;   //write data control signalinput rd;    //read data control signalinput clk1;   //write data clockinput clk2;   //read data clockreg[7:0] q;reg[7:0] mem[7:0];   //8*8 bites registeralways@(posedge clk1)beginif(we)mem[addr_n] <= d;endalways@(posedge clk2)beginif(rd)q <= mem[addr_out];end
endmodulemodule ram_dual_tb;reg clk1, clk2, we, rd;reg [2:0] addr_in;reg [2:0 ]addr_out;reg [7:0] d;wire [7:0] q;ram_dual U1(.q(q),.addr_in(addr_in),.addr_out(addr_out),.d(d),.we(we),.rd(rd),.clk1(clk1),.clk2(clk2));initialbeginclk1=0; clk2=0; we=1; rd=0; addr_in=0; addr_out=0; d=0;#320 we=0;rd=1;endalwaysbegin#10 clk1 = ~clk1;clk2 = ~clk2;end
initialbeginrepeat(7)begin#40 addr_in=addr_in+1;d=d+1;end#40repeat(7) #40 addr_out=addr_out+1;end
endmodule


(2)ROM的Verilog描述
ROM即只读存储器,是一种只能读出事先存储的数据的存储器,其特性是存入数据无法改变,也就是说这种存储器只能读不能写。由于ROM在断电之后数据不会丢失,所以通常用在不需经常变更资料的电子或电脑系统中,资料并不会因为电源关闭而消失。

module rom(dout, clk, addm, cs_n);input clk, cs_n;input [2:0] addm;output [7:0] dout;reg [7:0] dout;reg [7:0] rom[7:0];initialbeginrom[0]=8b0000_0000;rom[1]=8b0000_0001;rom[2]=8b0000_0010;rom[3]=8b0000_0011;rom[4]=8b0000_0100rom[5]=8b0000_0101;rom[6]=8b0000_0110;rom[7]=8'b0000_0111;endalways@(posedge clk)beginif(cs_n) dout<=8'bzzzz_zzzz;elsedout<=rom[addm];end
endmodulemodule rom_tb;reg clk, cs_n;reg [2:0] addm;wire [7:0] dout;rom U1(.dout(dout),.clk(clk),.addm(addm),.cs_n(cs_n));initial beginclk=0; addm=0; cs_n=0;endalways #10 clk=~clk;initial beginrepeat(7)#20 addm=addm+1;end
endmodule

2.6 FIFO设计

FIFO(First In First Out) 是一种先进先出的数据缓存器,通常用于接口电路的数据缓存。与普通存储器的区别是没有外部读写地址线,可以使用两个时钟分别进行写和读操作。FIFO只能顺序写入数据和顺序读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
FIFO由存储器块和对数据进出FIFO的通道进行管理的控制器构成,每次只对一个寄存器提供存取操作,而不是对整个寄存器阵列进行。FIFO有两个地址指针,一个用于将数据写入下一个可用的存储单元,一个用于读取下一个未读存储单元的操作。读写数据必须一次进行。
其读写过程如图所示:

当一个堆栈为空时 (图A),读数据指针和写数据指针都指向第一个存储单元如所示;当写入一个数据时(图B)写数据指针将指向下个存储单元;经过七次写数据操作后(图C)写指针将指向最后一个数据单元;当经过连续八次写操作之后写指针将回到首单元并且显示堆栈状态为满(图D)。数据的读操作和写操作相似,当读出一个数据时,读数据指针将移向下一个存储单元,直到读出全部的数据,此时读指针回到首单元,堆栈状态显示为空。

一个FIFO的组成一般包括两个部分: 地址控制部分和存储数据的RAM部分。如下图所示。地址控制部分可以根据读写指令生成RAM地址。RAM用于存储堆栈数据,并根据控制部分生成的地址信号进行数据的存储和读取操作。这里的RAM采用的是前面提到的双口RAM。

例: 用Verilog HDL设计深度为8,位宽为8的FIFO

//顶层模块:
module FIFO_buffer(clk,rst,write_to_stack,read_from_stack,Data_in,Data_out);input clk,rst;input write_to_stack,read_from_stack;input [7:0] Data_in;output [7:0] Data_out;wire [7:0] Data_out;wire stack_full, stack_empty;wire [2:0] addr_in, addr_out;FIFO_control U1(.stack_full(stack_full),.stack_empty(stack_empty),.write_to_stack(write_to_stack),.write_ptr(addr_in),read_ptr(addr_out),.read from stack(read from stack),.clk(clk),.rst(rst));ram_dual U2(.q(Data out),.addr_in(addr_in),.addr_out(addr_out),.d(Data_in),.we(write_to_stack),.rd(read_from_stack),.clk1(clk),.clk2(clk));
endmodule//控制模块:
module FIFO_control(write_ptr, read_ptr, stack_full, stack_empty, write_to_stack,read_from_stack, clk, rst);parameter stack_width=8;parameter stack_height=8parameter stack_ptr_width=3,output stack_full;        //stack full flagoutput stack_empty;        //stack empty flagoutput [stack_ptr_width-1:0] read_ptr;  //read data addressoutput[stack_ptr_width-1:0] write ptr;  //write data addressinput write_to_stack;   //write data to stackinput read_from_stack;  //read data from stackinput clk;input rst;reg [stack_ptr_width-1:0] read_ptr;
reg [stack_ptr_width-1:0] write_ptr;
reg [stack_ptr_width:0] ptr_gap;
reg [stack_width-1:0] Data_out;
reg [stack_width-1:0] stack[stack_height-1:0];//stack status signalassign stack_full=(ptr_gap==stack height);assign stack_empty=(ptr_gap==0);always@(posedge clk or posedge rst)beginif(rst)beginData_out<=0;read_ptr<=0;write_ptr<=0;ptr_gap<=0;endelse if(write_to_stack && (!stack_full) && (!read_from_stack))beginwrite_ptr<=write_ptr+1;ptr_gap<=ptr_gap+1;endelse if(!write_to_stack && (!stack_empty) && (read_from_stack))beginread_ptr<=read_ptr+1;ptr_gap<=ptr_gap-1;endelse if(write_to_stack && stack_empty && read_from_stack)beginwrite_ptr<=write_ptr+1;ptr_gap<=ptr_gap+1;endelse if(write_to_stack && stack_full && read_from_stack)beginread_ptr<=read_ptr+1;ptr_gap<=ptr_gap-1;endelse if(write_to_stack && read_from_stack&& (!stack_full)&&(!stack_empty))beginread_ptr<=read_ptr+1;write_ptr<=write_ptr+1;endend
endmodulemodule FIFO_tb;reg clk, rst;reg [7:0] Data_in;reg write_to_stack, read_from_stack;wire [7:0] Data_out;FIFO_buffer U1(.clk(clk),.rst(rst),.write_to_stack(write_to_stack),.read_from_stack(read_from_stack),.Data_in(Data_in),.Data_out(Data_out));
initialbeginclk=0; rst=1; Data_in=0, write_to-stack=1; read_from_stack=0;#5 rst=0;#155 write_to_stack=0;read _rom_stack=1:end
always #10 clk = ~clk;initialbeginrepeat(7)#20 Data_in =Data_in+1;end
endmodule

2.7 键盘扫描和编码器

键盘扫描和编码器用于在拥有键盘的数字系统中手工输入数据,通过检测按键是否按下,产生一个唯一对应此按键的扫描码。
例:用Verilog设计十六进制键盘电路的键盘扫描和编码器:

控制信号状态机转移图如下图所示:


详情见:16进制键盘扫描器的Verilog实现

此时行列线的交叉处就是按键的位置。根据已确定的按键的位置输出其对应的编码信息。其键盘编码表如下表所示。

Key Row[3:0] Col[3:0] Code
0 0001 0001 0000
1 0001 0010 0001
2 0001 0100 0010
3 0001 1000 0011
4 0010 0001 0100
5 0010 0010 0101
6 0010 0100 0110
7 0010 1000 0111
8 0100 0001 1000
9 0100 0010 1001
A 0100 0100 1010
B 0100 1000 1011
C 1000 0001 1100
D 1000 0010 1101
E 1000 0100 1110
F 1000 1000 1111

为了使测试更接近于真实的物理环境,测试平台中必须包括模拟按键状态的信号发生器,能确认按键对应行线的模块Row_signal和被测试模块Hex Keypad Grayhill 072。模拟按键状态的信号发生器可以嵌入在测试平台中,通过不断地给key信号赋值,模拟产生不同的按键信号。Row_Signal模块用于检测按键的有效性并确定按键所处的行。而Synchronizer模块通过检测各个行线值的或来确定是否有按键按下,当此模块的输出发生变化时,被测模块Hex Keypad Grayhil 072将会确定按键的位置并输出相应的代码

其Verilog HDL程序代码是:

// 顶层模块:
module keypad(clock,reset,row,code,vaild,col);input clock,reset;input [3:0] row;output [3:0] code;output vaild;output [3:0] col;wire s_row;hex_keypad_grayhill U1(.code(code),.col(col),.valid(valid),.row(row),.s_row(s_row),.clock(clock),.reset(reset));synchronizer U2(.s_row(srow),.row(row),.clock(clock),.reset(reset));
endmodule//编码模块:
module hex_keypad_grayhill(code,col,valid,row,s_row,clock,reset);output [3:0] code;output valid;output [3:0] col;input [3:0] row;inputs row;input clock,reset;reg [3:0] col;reg[3:0] code;reg [5:0] state,next_state,parameter s_0=6'b000001,s_1=6'b000010,s_2=6'b000100;parameter s_3=6'b001000,s_4=6'b010000,s_5=6'b100000;assign valid=((state==s_1)|(state==s_2)|(state==s_3)|(state==s_4))&&row;always@(row or col)case(frow,col})8'b0001_0001: code=0;8'b0001_0010: code=1;8'b0001_0100: code=2;8'b0001_1000: code=3;8'b0010_0001: code=4;8'b0010_0010: code=5;8'b0010_0100: code=6;8'b0010_1000: code=7;8'b0100_0001: code=8,8'b0100_0010: code=9;8'b0100_0100: code=10;8'b0100_1000: code=11;8'b1000_0001: code=12;8'b1000_0010: code=13;8'b1000_0100: code=14;8'b1000_1000: code=15;default code=0;endcasealways@(state or s_row or row)   //next-state logic
begincol=0:next_state=state;case(state)s_0:begincol=15;if(s_row) next_state=s_1;ends_1:begincol=1;if(row) next_state=s_5;else next_state=s_2ends_2:begincol=2;if(row) next_state=s_5;else  next_state=s_3,ends_3:begincol=4;if(row) next_state=s_5;else next_state=s _4;ends_4:begincol=8;if(row) next_state=s_5;else next_state=s_0;ends_5: begincol=15;if(!row) next_state=s_0;endendcase
end
always@(posedge clock or posedge reset)if(reset)state<=s_0;elsestate<=next_state;
endmodulemodule synchronizer(s_row,row,clock,reset);output s_row;input [3:0] row;input clock,reset;reg a_row,s_row;
always@(negedge clock or posedge reset)begin if(reset)begina_row<=0;s_row<=0;endelsebegina_row<=(row[0]llrow[1]llrow[2]llrow[3]);s row<=a row;end
endendmodule//模拟键盘产生信号
module row_signal(row,key,col);output [3:0] row;input [15:0] key;input[3:0] col;reg[3:0] row;always@(key or col)beginrow[0]=key[0]&&col[0]||key[1]&&col[1]||key[2]&&col[2]||key[3]&&col[3];row[1]=key[4]&&col[0]||key[5]&&col[1]||key[6]&&col[2]||key[7]&&col[3];row[2]=key[8]&&col[0]||key[9]&&col[1]||key[10]&&col[2]||key[11]&&col[3];row[3]=key[12]&&col[0]||key[13]&&col[1]key[14]&&col[2]||key[15]&&col[3];end
endmodule//Testbench
module hex_keypad_grayhill_tb;wire [3:0] code;wirevalid;wire [3:0] col;wire [3:0] row;reg clock;reg reset;reg [15:0] key;integer j,k;reg [39:0] pressed;parameter [39:0] key_0="key_0";parameter [39:0] key_1="key_1";parameter [39:0] key_2="key_2";parameter [39:0] key_3="key_3";parameter [39:0] key_4="key_4";parameter [39:0] key_5="key_5";parameter [39:0] key_6="key_6";parameter [39:0] key_7="key_7";parameter [39:0] key 8="key 8";parameter [39:0] key_9="key_9";parameter [39:0] key_A="key_A";parameter [39:0] key_B="key_B";parameter [39:0] key_C="key_C";parameter [39:0] key_D="key_D";parameter [39:0] key_E="key_E";parameter [39:0] key_F="key_F";parameter [39:0] None="None";keypad U1(.clock(clock),.reset(reset),.row(row),.code(code),.vaild(vaild),.col(col));       //top modulerow_signal U2(.row(row),.key(key),.col(col));   // Simulatesignal generationalways@(key)begincase(key)16'h0000: pressed=None;16'h0001: pressed=key_0;16'h0002: pressed=key_1;16'h0004: pressed=key_2;16'h0008: pressed=key_3;16'h0010: pressed=key_4;16'h0020: pressed=key_5;16'h0040: pressed=key_6;16'h0080: pressed=key_7;16'h0100: pressed=key_8;16'h0200: pressed=key_9;16'h0400: pressed=key_A;16'h0800: pressed=key_B;16'h1000: pressed=key_C;16'h2000: pressed=key_D;16'h4000: pressed=key_E;16'h8000: pressed=key_F;default: pressed=None;endcaseend
initial #2000 $stop;
initial begin clock=0;forever #5 clock=~clock;end
initial begin reset=1;#10 reset=0;end
initial begin for(k=0;k<=1;k=k+1)begin key=0;#20 for(j=0;j<=16;j=j+1)begin#20 keyli]=1;#60 key=0;endendend
endmodule

2.8 log函数的Verilog设计

log函数是一种典型的单目计算函数,与其相应的还有指数函数、三角函数等。对于单目计算函数的硬件加速器设计一般两种简单方法:一种是查找表的方式;一种是使用泰勒级数展开成多项式进行近似计算。这两种方式在设计方法和精确度方面有很大的不同。查找表方式是通过存储器进行设计,设计方法简单,其精度需要通过提高存储器深度实现,在集成电路中占用面积大,因此着这种方式通常在精度要求不高的近似计算中使用。泰勒级数展开方式采用乘法器和加法器实现,可以通过增加展开级数提高计算精确度。
例:用Verilog HDL设计采用查找表方式的log函数,输入信号位宽4bits,输出信号位宽8bits

其中输入数据为一位整数位三位小数位精确到2-3,输出结果两位整数位六位小数位精确到2-6。其Verilog程序代码是:

module log_lookup(x,clk,out);input [3:0] x;input clk;output [7:0] out;reg [7:0] out;always@(posedge clk)begincase(x)4b1000:out<=8b00000000;4b1001:out<=8b00000111;4b1010:out<=8b00001110;4b1011:out<=8b00010101;4b1100:out<=8b00011001;4b1101:out<=8b00100000;4b1110:out<=8b00100100;4b1111:out<=8b00101000default:out<=8'bz;endcaseend
endmodulemodule log_lookup_tb;reg clk;reg [3:0]x;wire [7:0] out;initialbeginx=4'b1000;clk=1'b0;repeat(7)#10 x=x+1;endalways #5 clk=~clk;log_lookup U1(.x(x),.ck(clk),.out(out));endmodule


例:用Verilog设计采用泰勒级数展开方式的log函数,输入信号位宽4bits,输出信号位宽8bits
泰勒级数的定义:若函数f (x) 在点的某一邻域内具有直到 (n+1)阶导数,则在该邻域内f (x) 的阶泰勒公式为:

泰勒级数可以将一些复杂的函数用多项式相加的形式进行近似,从而简化其硬件实现。logax在x0=b处的泰勒展开为:

误差范围为:

在x0=1处展开为:

误差范围:

电路结构图如下:

上述的log函数在X=1处展开,并且要求X的取值范围为1<X<2,输入4位二进制数据X精确到2-3,其中一位整数位四位小数位,输出8位二进制数据精确到2-6,其中两位整数位六位小数位。设计当中所用到的乘法器和减法器均采用前文所给出的减法器和乘法器。

module log(x,out);input[3:0] x;output[7:0] out;wire [3:0] out1;wire [7:0] out2,out3, out5, out;wire [3:0] out4;assign out4={out3[7:4]};assign out1=x-4'b1000;wallace U1(.x(out1),.y(4'b0111),.out(out2));wallace U2(.x(out1),.y(out1),.out(out3));wallace U3(.x(out4),.y(4'b011),.out(out5)); assign out=out2-out5;
endmodulemodule log_tb;reg [3:0] x=4'b1000wire [7:0] out;log U1(.x(x),.out(out));always#10 x=x+1;always@(x)beginif(x==4'b0000)$stop;end
endmodule

2.9 CORDIC算法的Verilog实现

坐标旋转数字计算机CORDIC(Coordinate Rotation Digital Computer)算法,通过移位和加减运算,能递归计算常用函数值,如sin, cos,sinh,cosh等函数,最早用于导航系统,使得矢量的旋转和定向运算不需要做查三角函数表、乘法、开方及反三角函数等复杂运算。J.Walther在1971年用它研究了一种能计算出多种超越函数的统一算法引入参数m将CORDIC实现的三种迭代模式:三角运算、双曲运算和线性运算统一于同一个表达式下。形成目前所用的CORDIC算法的最基本的数学基础。该算法的基本思想是通过一系列固定的、与运算基数相关的角度不断偏摆以逼近所需的旋转角度。可用下列等式进行描述。

提出,从而得到

这里取,所有迭代的角度的这里,这里矩阵中的所以矩阵就变为:

上式中的。随着迭代次数的增加,改式就会收敛为一个常数:

k作为一个常数增益,可以暂不考虑,这时上式就会变为:

如果用Z来表示相位累加的部分和,则

若想使Z旋转到0,则Sn的符号由Zn,来确定,如下:

旋转后的最终结果为:

对于一组特殊的初始值:

得到的结果为:

将这种工作模式称为旋转工作模式通过旋转模式就可以求出一个角度的sin和cos值。

迭代结构
简单地将CORDIC算法的公式复制到硬件描述上,就可以实现迭代的CORDIC算法,其结构如下图所示。

流水线结构
流水线结构虽然比迭代结构占用的资源多,但是它大大的提高了数据的吞吐率。流水线结构是将迭代结构展开,因此n个处理单元中的每个都可以同时并行处理一个相同的迭代运算。其结构如下图所示。

例:用Verilog HDL设计基于7级流水结构求正余弦的CORDIC算法在CORDIC算法中有一个初始的X、Y值。输入变量Z是角度变量,首先将X、Y输入到固定移位次数的移位寄存器进行移位,然后将结果输入到加/减法器,并且根据角度累加器的输出结果来确定加减法器的加减操作,这样就完成了一次迭代,将此次迭代运算的结果作为输入传送到下一级的迭代运算,将迭代运算依次进行下去,当达到所需要的迭代次数(本例为7次) 的时候将结果输出,此时就是想要的结果所以整个CORDIC处理器就是一个内部互联的加/减法器阵列。

module sincos(clk,rst_n,ena,phase_in,sin_out,cos_out,eps);parameter DATA_WIDTH=8;parameter PIPELINE=8;input clk;inpu trst_n;input ena;input [DATA _WIDTH-1:0] phase_in;output [DATA_WIDTH-1:0] sin_out;output [DATA_WIDTH-1:0] cos_out;output [DATA_WIDTH-1:0] eps;reg [DATA_WIDTH-1:0] sin_out;reg [DATA_WIDTH-1:0] cos_out;reg [DATA_WIDTH-1:0] eps;reg[DATA_WIDTH-1:0] phase_in_reg;reg [DATA_WIDTH-1:0] x0,y0,z0;wire [DATA_WIDTH-1:0] x1,y1,z1;wire [DATA_WIDTH-1:0] x2,y2,z2;wire [DATA_WIDTH-1:0] x3,y3,z3;wire [DATA_WIDTH-1:0] x4,y4,z4;wire [DATA_WIDTH-1:0] x5,y5,z5;wire [DATA_WIDTH-1:0] x6,y6,z6;wire [[DATA_WIDTH-1:0] x7,y7,z7;reg [1:0] quadrant[PIPELINE:0];integer i;always@(posedge clk or negedge rst n)beginif(!rst_n)phase_in_reg<=8b0000_0000;elseif(ena)begincase(phase_in[7:6])2b00:phase_in_reg<=phase_in;2b01:phase_in_reg<=phase_in-8'h40;2b10:phase in reg<=phase_in-8'h80;2b11:phase_in_reg<=phase_in-8hc0;endcaseendend
always@(posedge clk or negedge rst_n)beginif(!rst_n)beginx0<=8b00000000;y0<=8b00000000;z0<=8b00000000;endelseif(ena)beginx0<=8'h4D;y0<=8'h00;z0<=phase_in_reg;endend
lteration #(8,0,8'h20)u1(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x0),.y0(y0),.z0(z0),.x1(x1),.y1(y1),.z1(z1));
lteration #(8,1,8'h12)u2(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x1),.y0(y1),.z0(z1),.x1(x2),.y1(y2),.z1(z2));
lteration #(8,2,8'h09)u3(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x2),.y0(y2),.z0(z2),.x1(x3),.y1(y3),.z1(z3));
lteration #(8,3,8'h04)u4(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x3),.y0(y3),.z0(z3),.x1(x4),.y1(y4),.z1(z4));
lteration #(8,4,8'h02)u5(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x4),.y0(y4),.z0(z4),.x1(x5),.y1(y5),.z1(z5));
Iteration #(8,5,8'h01)u6(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x5),.y0(y5),.z0(z5),.x1(x6),.y1(y6),.z1(z6));
Iteration #(8,6,8'h00)u7(.clk(clk),.rst_n(rst_n),.ena(ena),.x0(x6),.y0(y6),.z0(z6),.x1(x7),.y1(y7),.z1(z7));
always@(posedge clk or negedge rst _n)beginif(!rst_n)for(i=0;i<=PIPELINE;i=i+1)quadrant[i]<=2'b00;elseif(ena)beginfor(i=0;i<=PIPELINE;i=i+1)quadrant[i+1]<=quadrant[i];quadrant[0]<=phasein[7:6];endend
always@(posedge clk or negedge rst_n)beginif(!rst_n)beginsin_out<=8'b00000000;cos_out<=8'b00000000;eps<=8'b00000000;endelseif(ena)case(quadrant[7])2'b00:beginsin_out<=y6;cos_out<=x6;eps<=z6;end2b01:beginsin_out<=x6;cos_out<=~(y6)+1'b1;eps<=z6;end2b10:beginsin_out<=~(y6)+1b1;cos_out<=~(x6)+1b1;eps<=z6end2'b11:beginsin_out<=~(x6)+1'b1;cos_out<=y6;eps<=z6;endendcaseend
endmodule//迭代模块:
module lteration(clk,rst_n,ena,x0,y0,z0,x1,y1,z1):parameter DATA_WIDTH=8;parameter shift=0;parameter constant=8"h20;input clk,rst_n,ena;input [DATA_WIDTH-1:0] x0,y0,z0;output[DATA_WIDTH-1:0] x1,y1,z1;reg [DATA_WIDTH-1:0] x1,y1,z1;
always@(posedge ck or negedge rst_n)beginif(!rst_n)beginx1<=8'b00000000;y1<=8'b00000000z1<=8'b00000000endelseif(ena)if(z0[7]==1'b0)beginx1<=x0-{{shift{y0[DATA_WIDTH-1]}},y0[DATA_WIDTH-1:shift]};y1<=y0+{{shift{x0[DATA_WIDTH-1]},x0[DATA_WIDTH-1:shift]};z1<=z0-constant;endelsebeginx1<=x0+{{shift{y0[DATA_WIDTH-1]}},y0[DATA_WIDTH-1:shift]};y1<=y0-{{shift{x0[DATA_WIDTH-1]},x0[DATA_WIDTH-1:shift]};z1<=z0+constant;endend
endmodulemodule sincos tb;reg clk,rst_n,ena;reg [7:0] phase_in;wire [7:0] sin_out,cos_out,eps;sincos U1(.clk(clk),.rst_n(rst_n),.ena(ena),.phase_in(phase_in),.sin_out(sin_out),.cos_out(cos_out),.eps(eps));initialbeginclk=0;rst_n=0;ena=1;phasei_n=8b00000000;#3 rst_n=1;endalways #5 clk=~clk;always #10phase_in=phase_in+1;
endmodule

3.总线控制器设计

3.1UART接口控制器

串口也称作UART(Universal Asynchronous Receiver/Transmitters),在实际应用中,通常只用TXD和RXD两个脚,而其它的管脚都不使用,UART接口时序如图所示:

一个简单的UART结构图如下图所示:

发送模块:发送模块的功能是将数据以串行的形式发送出去,并且将每一组的串行数据加上开始位和停止位。当byte_ready信号有效时数据被载入移位寄存器并添加开始位(低电平)和停止位(高电平)。当byte_ready信号无效时移位寄存器开始移位操作将数据以串行的形式发送出去。

module UART_transmitter(clk,reset,byte_ready,data,TXD);inputclk,reset;input byte_ready;input[7:0] data;output TXD;reg [9:0] shift_reg;
assign TXD=shift_reg[0];
always@(posedge clk or negedge reset)beginif(!reset)shift reg<=10'b1111111111;else if(byte_ready)shift reg<=(1'b1,data,1'b0);elseshift reg<=(1'b1,shift reg[9:1]);
endmodule

接收模块:接收模块的功能是接收发送模块输出的串行数据,并以并行的方式将数据送入存储器。当接收模块检测到开始位(低电平) 时开始接收数据,并且输入串行数据存入移位寄存器,当接收完成时将数据并行输出。

module UART_receiver(clk,reset,RXD,data_out);parameter idle=2'b00;parameter receiving=2'b01;inputclk,reset;input RXD;output [7:0] data out;reg shift;reg inc_count;reg [7:0] data_out;reg[7:0] shift_reg;reg(3:0] count;reg[2:0] state,next state;always@(state or RXD or count)beginshift=0;inc_count=0;next_state=state,case(state)idle:if(!RXD)next state=receiving;receiving:beginif(count==8)begindata_out=shift_reg;next_state=idle;count=0;inc_count=0;endelsebegininc_count = 1;shift=1;endenddefault:next_state<=idle;endcaseendalways@(posedge clk or negedge reset)beginif(!reset)begindata_out<=8'b0;count<=0;state<=idle;endelse beginstate<=next_state;if(shift)shift_reg<={shift_reg[6:0],RXD):if(inc_count)count<=count+1;endend
endmodulemodule UART_tb;reg clk,reset;reg [7:0] data;reg byte_ready;wire [7:0] data_out;wire serial_datainitialbeginclk=0;reset=0;byte_ready=0;data=8'b10101010;#40 byte_ready=1;#50 reset=1;#170 byte_ready=0:endalways #80 clk=~clkUART transmitterU1(.clk(clk),.reset(reset),.byte ready(byte_ready),.data(data),.TXD(serial data));UART receiverU2(.clk(clk),.reset(reset),.RXD(serial data),.data out(data out));
endmodule

3.2SPI接口控制器

串行外设接口(Serial PeripheralInterface SPI) 是一种同步串行外设接口,能够实现在微控制器之间或微控制器与各种外设之间以串行方式进行通信数据交换。SPI可以共享,便于组成带多个SPI接口器件的系统,且传送速率高,可编程,连接线少,具有良好的扩展性,是一种优秀的同步时序电路SPI总线通常有4条线:串行时钟线 (SCLK)、主机输入/从机输出数据线MISO)、主机输出/从机输入数据线(MOSI)。低电平有效从机选择线 (SS N)。SPI系统可分为主机设备和从机设备两大类,主机提供SPI时钟信号和片选信号,从机是接收SPI信号的任何集成电路。当SPI工作时,在移位寄存器中的数据逐位从输出引脚(MOSI)输出,同时从输入引脚(MISO)逐位接收数据。发送和接收数据操作都受控于SPI主设备时钟信号(SCLK),从而保证了同步。因此只能有一个主设备,但可以有多个从设备,可以通过片选信号 (SSN)可同时选中一个或多个从设备。
其典型结构如下图:

SPI总线典型时序图如下图:

例:采用Verilog设计一个简化的SPI接收机,用来完成8bits数据的传输SPI接收机框图如图所示:

module SPI(sdout,MISO,sclk,srst,sen,ss_n);output [7:0] sdout;output ss_n;input MISO,sclk,srst,sen;reg [2:0] counter;reg [7:0] shift regist;reg ss_n;always @(posedge sclk)if (!srst)counter<=3'b000;else if(sen)if (counter==3'b111)begincounter<=3'b000:ss_n<=1'b1;endelsebegincounter<=counter+1;ss_n<=1'b0;endelsecounter<=counter;always@(posedge sclk)if(sen)shift_regist<={shift_regist[6:0],MISO};elseshift_regist<=shift_regist;assign sdout=ss_n?shift_regist:8'b00000000;
endmodule

代码中,sclk为接口时钟。srst为清零信号,低电平有效。sen为接口使能信号,高电平有效。ss_n为片选信号,选择从设备,高电平有效当电路上电时,首先将清零信号置为有效,初始化电路。当sen使能信号有效后,开始传输数据,由于传输数据为8bits,因此sen使能信号应至少保持8个时钟周期。当8bits数据全部输入后,片选信号ss n有效,选择从设备,将数据整体输出。片选信号ss n由3bits计数器产生,当计数器计数到111状态时,ss_n=1,其它状态下ss_n=0

module SPI_tb;reg MISO,sclk,sen,srst;wire [7:0] sdout;wire ss_n;SPI U1(.sdout(sdout),.MISO(MISO),.sclk(sclk),.srst(srst),.sen(sen),ss_n(ss_n));initialbeginMISO=0;sclk=0;srst=0:sen=0;#10 srst=1;#10 sen=1;#80 sen=0;#10 sen=1;#80 sen=0;end
initialbegin#30 MISO=1;#10 MISO=0;#10 MISO=1;#10 MISO=0:#10 MISO=1;#10 MISO=0;#10 MISO=1;#20 MISO=1;#10 MISO=0;#10 MISO=1;#10 MISO=0;#10 MISO=1;#10 MISO=0;#10 MISO=1;#10 MISO=0;end
always #5 sclk<=~sclk;
endmodule

来源:蔡觉平老师的Verilog课程

Verilog学习笔记(5):Verilog高级程序设计相关推荐

  1. Verilog学习笔记-——Verilog模块例化

    Verilog学习笔记---Verilog模块例化 在一个模块中引用另一个模块,对其端口进行相关连接,叫做模块例化.模块例化建立了描述的层次.信号端口可以通过位置或名称关联,端口连接也必须遵循一些规则 ...

  2. 【Verilog学习笔记】D触发器(门级和行为级)+4位寄存器+一个完整的激励程序

    [Verilog学习笔记]D触发器(门级和行为级)+4位寄存器+一个完整的激励程序 首先展示以下完整的程序 `timescale 1ns / 1psmodule hardreg( input wire ...

  3. verilog学习笔记之一--(简化)华莱士(Wallace)树形乘法器设计--(原代码出自用芯学项目)

    verilog学习笔记之一–(简化)华莱士(Wallace)树形乘法器设计–(原代码出自用芯学项目) 学习准备1: 树形乘法器原理:参考<数字集成电路-电路.系统与设计(第二版)>–P43 ...

  4. Verilog学习笔记——入门

    Verilog学习笔记 01 基本逻辑门代码设计与仿真 Veriog基本逻辑门代码结构--以一位反相器为例 ModelSim仿真基本流程 02 组合逻辑代码设计与仿真--多路选择器 二选一逻辑--as ...

  5. Verilog学习笔记

    Verilog学习笔记 本文根据学习菜鸟教程下Verilog教程总结得到,主要记载一些硬件语言描述下的骚操作,仅供学习. 归约操作符 归约操作符包括:归约与(&),归约与非( ~ &) ...

  6. JavaScript学习笔记06【高级——JavaScript中的事件】

    w3school 在线教程:https://www.w3school.com.cn JavaScript学习笔记01[基础--简介.基础语法.运算符.特殊语法.流程控制语句][day01] JavaS ...

  7. JavaScript学习笔记05【高级——DOM对象】

    w3school 在线教程:https://www.w3school.com.cn JavaScript学习笔记01[基础--简介.基础语法.运算符.特殊语法.流程控制语句][day01] JavaS ...

  8. JavaScript学习笔记04【高级——DOM和事件的简单学习、BOM对象】

    w3school 在线教程:https://www.w3school.com.cn JavaScript学习笔记01[基础--简介.基础语法.运算符.特殊语法.流程控制语句][day01] JavaS ...

  9. 【学习笔记】C++语言程序设计(郑莉):多态性

    [学习笔记]C++语言程序设计(郑莉):多态性 1. 多态性 2. 运算符重载 2.1 运算符重载的规则 2.2 运算符重载为成员函数 2.3 运算符重载为非成员函数 3. 虚函数 3.1 一般虚函数 ...

  10. 【学习笔记】C++语言程序设计(郑莉):数据的共享与保护

    [学习笔记]C++语言程序设计(郑莉):数据的共享与保护 1. 标识符的作用域与可见性 1.1 作用域 1.1.1 函数原型作用域 1.1.2 局部作用域 1.1.3 类作用域 1.1.4 命名空间作 ...

最新文章

  1. [WPF疑难]避免窗口最大化时遮盖任务栏
  2. Window编程主函数详解
  3. 第十章 Linux下RPM软件的安装与卸载
  4. Jmeter之Beanshell使用(二)Java处理JSON块
  5. adaboost和GBDT的区别以及xgboost和GBDT的区别
  6. Hive(三)hive的高级操作
  7. 文献记录(part95)--CCMS: A nonlinear clustering method based on crowd movement and selection
  8. Angular getOrCreateInjectable的实现原理调试
  9. 记一次Sentry部署过程
  10. 关于CSDN官方对博文点赞漏洞的处理(Cookie劫持攻击)
  11. 【Python】第三方库安装脚本
  12. css3实现的the Sexy Buttons
  13. 详解Python生成器函数和生成器对象的原理和用法
  14. 【原创】MapReduce实战(一)
  15. 如何用法向量求点到平面距离_无论大考小考,无论校考,还是联考,老师钟爱的题型“空间距离”...
  16. 元素定位-XPATH定位方法总结
  17. ETC passwd 用户和组管理
  18. stata图像绘制专题【计量经济系列(二)】
  19. Python数据分析(Pandas)
  20. 如何做好实施前准备?

热门文章

  1. Windows下载安装Cytoscape3.8.2
  2. linux:线程同步的5种方法
  3. from keras.preprocessing.sequence import pad_sequences 标红
  4. PM常用语看这篇就够了
  5. GC参数解析 UseSerialGC、UseParNewGC、UseParallelGC、UseConcMarkSweepGC
  6. 「UG/NX」BlockUI 字符串String
  7. 微信摇一摇php,微信摇一摇功能实现 - 微信公众平台开发:微信
  8. Linux也有全功能杀毒软件啦!
  9. adobe cs5 indesign 不显示文本框_Adobe CS5 InDesign自动添加页码
  10. SLAM学习资源免费分享-转载