Posts

Showing posts from January, 2023

基于verilog的单周期CPU

 最近研究了一下CPU的运行原理,以MIPS指令集为例,用verilog写了个单周期的CPU,算是对CPU的理解加深了一些。   完整代码和注释在: https://github.com/ChenlinShi/Verilog_Basic_Circuits

基于verilog的微复杂电路

 Ripple cary adder: module eight_bit_adder (   a ,   b ,   cin ,   cout ,   sum ,   clk ,   rst ); input [ 7 : 0 ] a , b ; input cin , clk , rst ; output   reg cout ; output   reg [ 7 : 0 ] sum ; reg [ 6 : 0 ] bit_carry ; always @( posedge clk or negedge rst ) begin     if (~ rst ) begin     cout <= 0 ;     sum <= 0 ;     end     else begin     { bit_carry [ 0 ], sum [ 0 ]}<= a [ 0 ]+ b [ 0 ]+ cin ;     { bit_carry [ 1 ], sum [ 1 ]}<= a [ 1 ]+ b [ 1 ]+ bit_carry [ 0 ];     { bit_carry [ 2 ], sum [ 2 ]}<= a [ 2 ]+ b [ 2 ]+ bit_carry [ 1 ];     { bit_carry [ 3 ], sum [ 3 ]}<= a [ 3 ]+ b [ 3 ]+ bit_carry [ 2 ];     { bit_carry [ 4 ], sum [ 4 ]}<= a [ 4 ]+ b [ 4 ]+ bit_carry [ 3 ];     { bit_carry [ 5 ], sum [ 5 ]}<= a [ 5 ]+ b [ 5 ]+ bit_carry [ 4 ];     { bit_carry [ 6 ], sum [ 6 ]}<= a [ 6 ]+ b [ 6 ]+ bit_carry [ 5 ];     { cout , sum [ 7 ]}<= a [ 7 ]+ b [ 7 ]+ bit_carry [ 6 ];     end end endmodule //8_bit_adder testbench: module tb_rca_bi

verilog端口类型

 当定义一个module时,即在模块内部定义port的时候: 输入:wire 输出:wire or reg  当实例化一个module时,即在模块外部使用port的时候: 输入:wire or reg 输出:wire

verilog实现pipeline的理解

 Verilog中的always语句块都是并行实现的,所以很容易实现流水线处理。 如以下伪代码处理8bit加法:  always a {  低四位相加 }  always b { 存储高四位的值}  always c {用高四位的值与低四位的进位值相加并与低四位的值串接} 在计算机执行这个代码的时候,第一次处理abc时,由于是并行处理,所以只有a和b能正确处理,而c则是输出的并不正确的结果,因为低四位的值还没计算好,还在并行计算中。 而第二次执行时,a和b已经是处理新数据,而c执行的是上一个时钟周期的值,因为b的值在并行处理中,并没有被刷新,而上一个时钟周期计算出的低四位的值也同样并未刷新,所以c可以正常处理结果,最终得出第一个加法结果。 而在第三个时钟周期中,c将得出第二个加法结果。从而每三个时钟周期就可以实现2次加法,比直接串行处理,先处理低四位后处理高四位,要快1.5倍。 下面的verilog代码实现了一个两级pipeline的fulladder,实现了上方伪代码的思路: module pipeline_8bit_adder ( enable , rst , a , b , cin , cout , sum ); input [ 7 : 0 ] a , b ; output reg [ 7 : 0 ] sum ; output reg cout ; input cin , rst , enable ; reg [ 3 : 0 ] temp_a ; reg [ 3 : 0 ] temp_b ; reg temp_cin ; reg [ 3 : 0 ] low_bit_sum ; reg [ 3 : 0 ] high_bit_sum ; always @( posedge enable   or negedge rst ) begin     if (~ rst ) begin       sum = 0 ;       cout = 0 ;       end     else begin     { temp_cin , low_bit_sum }<= a [ 3 : 0 ]+ b [ 3 : 0 ]+ cin ;     temp_a <= a [ 7 : 4 ];     temp_b <= b [ 7

iverilog+gtkwave的使用方法

首先要在testbench.v中增加以下内容:   initial begin     $dumpfile ( "dump.vcd" );     $dumpvars ;   end 在命令行中输入以下命令即可轻松查看波形:    iverilog -o output_file_name testbench.v design.v vvp output_file_name  gtkwave dump.vcd

基于verilog的简单逻辑电路

 半加器: 使用门: module ADD_half ( output c_out, sum,   input a,b );     xor (sum,a,b);     and (c_out,a,b); endmodule 使用抽象语句: module ADD_half_nogate ( output cout,sum, input a,b,cin); assign      sum=a^b; assign     cout=a&&b; endmodule 使用门定义去编写逻辑电路过于麻烦,以下都电路只会使用逻辑符号来描述。 全加器: module ADD_full ( output c_out,sum, input a,b,cin); wire w1, w2, w3; ADD_half_nogate M1 (w2,w1,a,b); ADD_half_nogate M2 (w3,sum,cin,w1); assign c_out=w3||w2;     endmodule testbench: module t_Add (); wire sum, c_out; reg a, b,cin; ADD_full M1 (c_out,sum,a,b,cin);   initial begin     $dumpfile ( "dump.vcd" );     $dumpvars ;   end initial begin     #100 $finish ; end initial begin     #10 a= 0 ;b= 0 ;cin= 1 ;     #10 b= 1 ;     #10 a= 1 ;     #10 b= 0 ; end endmodule 2位选择器: module multiplexer ( output mux_out , input sel, a,b); assign mux_out=(sel)?a:b; endmodule testbench: module t_mux (); wire mux_out; reg a, b,sel;   multiplexer M1 (mux_out,sel,a,b);   initial

verilog tips

 任何没有定义类型的变量和input output变量,都会被默认为wire型变量。 如果引用一个module,module一有很多的port,那么在实际调用的时候,可以使用formal_name(actual_name)这种结果来规定port名字和实际port的对应。 用#来标注delay如 wire #1 a ; 用@来标注事件如:always @ (.....) initial是只执行一次,always是循环执行 在begin end里,reg变量用<=赋值 形如reg [7:0] mem [255:0]的结构的意思是,256个8位的寄存器,即后面的数表示变量的个数,第一个数才是变量的位宽。 阻塞赋值和非阻塞赋值,简单理解就是always block里适合用 reg来进行并行逻辑,即“<=”进行赋值,而顺序逻辑则是用wire,“=”来进行赋值。

Verilog full adder

Image
 最基础的full adder: combinational circuit: full adder可以由两个half adder拼起来。 第一个half adder计算 a+b,得出计算结果sum1和cout1,然后第二个half adder计算cin加sum1的结果,并给出结果sum2和cout2,然后再计算cout1+cout2来计算最终的进位结果。 module ADD_half ( output c_out, sum,  input a,b );     xor (sum,a,b);     and (c_out,a,b); endmodule module ADD_full ( output c_out,sum, input a,b,cin); wire w1, w2, w3; ADD_half m1 (w2,w1,a,b); ADD_half m2 (w3,sum,cin,w1); or(c_out,w3,w2);     endmodule 可以写成以上代码形式。 实际的电路图为以下所示:

RISC-V中检测乘法是否溢出的方法

 RISC-V有四种乘法指令分别是:    mul:乘    mulh:乘后取高位    mulhu:无符号数乘法取高位, 64bit * 64bit其128bit结果中的前64bit的值    mulhsu: 有符号和无符号数乘法取高位 如果想得到比如128位积中的高64位,且两个操作数都是有符号的,则应使用mulh。mulhu和mulhsu则各自对应各自定义中的操作数。   所以软件上可以采用乘法取高位的方法来测试是否出现溢出:    如果mulhu的结果为0,那么必然是没有发生溢出。    如果mulh的结果都是mul结果的符号位的复制,那么显然也没有发生溢出。

简单串行乘法器与和并行乘法器

Image
 串行乘法器:  串行乘法非常类似于小学学的乘法原理,即,拿被乘数去乘以乘数的每一对应位的数字,并将被乘数在乘数对应位移位,然后相加:    如     那么实际计算机中的计算就是如此:       以下图为例:计算64bit的乘法       将被乘数的64bit的数,存放在一个128bit被乘数寄存器的右半边,左半边全部为0,方便之后进行移位。       然后取乘数寄存器最右边的位的值,对被乘数进行相乘,乘数寄存器再进行右移。       将乘积与积寄存器的值相加然后写入积寄存器,不断循环。       直至循环64次。      每次对乘数右移实际是不断提取从低到高提取位的乘数,然后和左移后,也就是的值移位后的数值去相乘,就能得出对应位的乘积,最终与积寄存器的值相加然后写入积寄存器。 (小)并行乘法器:      并行乘法器采用比较巧妙的办法同时将积与乘数一同移位:      将乘数存储到积寄存器的右半边,并每次提取积寄存器的最右位的值,然后与被乘数相乘,将其结果加到积寄存器的左64位。然后再对积寄存器进行移位,以此类推。    此过程中,实际是将被乘数与乘数对应位的值的乘积先放大,然后通过右移位的方式不断回归其本身的位:   如: 以4bit乘法为例(以下均为无符号数):                            乘数:           1111           那么积寄存器应为:00001111                           被乘数:         1000            那么第一次运算后,积寄存器的结果应为:10000111                   第二次后为:                                     11000011                   第三次后为:                                     11100001                   第四次后为:                                     11110000      一开始虽然是乘数最低位的1去乘以被乘数并且被加到了最高位,但是由于每次新加的值都会加到最高位,而旧最高位的值会右移,所以最终的结果恰好是高位的积加到了高位,

竞争冒险

    什么是竞争和冒险:     逻辑表达式形如:f=ac'+bc会引发static harzard,简单理解:     c经过非门时会有一定的延迟,所以在比如A=1,B=1时,C从1切换至0,由于延迟,ac'从0变成1的时间会滞后,所以会有一小短时间ac'=0,而bc同样等于0,所以此时逻辑电路的结果会短暂为0。        以上描述的情况中,ac'和bc改变数据的时刻有差距,这种情况被称为竞争,而最终导致的错误结果则被称为冒险(hazard),有冒险的发生必定有竞争的发生,但是有竞争未必有冒险,比如将上述表达式改为ac'*bc,那么即便有竞争时两个0的情况,也不会影响最终的输出。        判定是否会发生竞争的办法:      若表达式为长且复杂,则用K-map去看各表达式元素之间是否有接触,或者说是否有相邻的表达式,若有则有可能发生static harzard。    解决方法:       将Kmap中表达式接触的边缘覆盖,添加多余项。       这个方法的本质是,竞争的发生时刻,比如上式中,当A=B=1时,表达式实际结果等效为f=C+C', 那么当C翻转时,自然会因为Inverter的延迟而发生竞争。增加冗余项后如: 表达式变为  f=ac'+bc+ab ,那么当a=b=1时,表达式仍有ab子项制衡结果,来保持结果不会因为C的值而受影响。

二进制加减法归纳

首先,加减法的基础:  计算机以补码作为计算基础:  不论正负数,最高位永远代表符号位,正数的符号位为0,负数为1。  正数的补码就是其本身,即01011为01011.  负数的补码略微复杂,我自己比较喜欢的计算方式为:    首先最高位是符号位1,以4bit为例,那么1000就代表着 -1*2^3=-8。    其余位按正常处理,如1011就是 -8+2+1=-5。  如果计算机有很多富裕的位,那么正数需要将富裕的最高位全部补0,负数全部补1。正数很好理解,负数全部补1可以理解为:     如1011,如果增加一位最高位补1,那么新的最高位(2^4)就是之前最高位(2^3)的两倍,由于2^3不再是最高位,所以其符号变为正号,新最高两位的数字和仍然是-2^3,并不会发生变化,如果再增加最高位,同理,新最高位是旧最高位的两倍,即2^5=2*2^4=2*2*2^3,而其后两位2^4+2^3=3*2^3,和最高位的计算和仍然是-8。也就是说,不论如何增高位的1,在补码的计算规则下,高位1的计算和永远是不变的。     本质上,这是由于:比如11111=-1,当每一位都是1的时候,值为-1,其余负值都是在此基础上去减去对应位的数值。所有不论前面有多少位是1,并不影响后续位需要扣除的值。    所以,001011=01011,             11011=1011=-16+8+2+1=-5。 溢出: 当符号位不同的数字相加肯定不会溢出,因为最终数一定会比原来某一个数小。 当符号位相同的数字相减肯定不会溢出,因为减法的本质是加上一个负数,即两个符号位不同的数字相加。 只有两个数字都很大的时候,相加或相减有可能发生溢出,具体的归纳可以查询书籍。

Little-Endian&Big-Endian

 大端格式与小端格式: 如数据 0x01020304 在内存中,如若为小端格式则,低位内存存储低位数据,高位内存存储高位数据: 地址  4000  4001  4002  4003 对应  04        03      02       01 大端格式则相反。 目前主流CPU均采用小端格式。

Sequence Detector

Image
    时序检测机用来检测一系列数据是否满足要求,比如,只有当输入为1101时,机器才会输出1,否则为0。类似这样的工作都是可以用Sequence Detector来完成。     时序检测机基于State machine,moore和mealy两种,而每种机器又分为non-overlap型和overlap型。overlap的电路会将输入一系列被检测数据的最后一位当做新的检测数据的第一位。    以non-overlap型为例去描述输入1101才输出为1的电路应有以下步骤:   确定state machine:   比较难的部分是,当输入数据不符合预期数据流时,我们需要重新选定状态,这个状态的选定需要仔细判断。如图中当连续输入数据为111时,这并不符合1101的数据流,但是111的后两位1却仍然符合数据流,所以只需要返回自身然后再判断就可。可以逐一对每一state的数据流分析,在每个state中确定如若不能继续前进,应退回哪一位最合理。   

对MOSFET的电压曲线的归纳

最基本的: 只有VDD: 只存在漏电流。 VDD,VG共同作用: 当VG<Vth时,除了漏电流,并不存在电子的流动,所以实际上能检测到的电流也就是漏电流,但此时的漏电流可能会略比只存在VDD时的漏电流略大。 当VG>Vth, 电子开始受到Gate极的电压的吸引,形成导电沟道,而由于VDD存在电压,所以形成导电沟道的电子受到VDD的吸引,形成电流,电流方向为Drain to source。 预夹断: 在VG>Vth的情况下,VDD不断增加,当VDD=VG-Vth时,此时,VDD的电压便和导电沟道的正中间的电压相等,电子不再集中像Gate极靠拢,也就是电子会逐渐在Drain极附近形成包围圈,从而使得从Source导向Drain极的电子数量变少,所以导电沟道的电子无法再不断显著增加。这便是预夹断形成的本质原因,从而使得MOSFET进入saturation区。 如果此时不断增加VDD,聚集在VDD的电子会越来越多,也就是导电沟道越来越短,从而Ids的值基本不再增加。 关于预夹断的可以从以下URL中寻得更多介绍与讲解: http://www.kiaic.com/article/detail/2672.htm