HDLBits练习

HDLBits:专门用来学习和练习Verilog,有基础语法教程与题目相搭配
基于题目的目录,参考一篇汇总了全部答案和讲解的博客:HDLBits答案汇总,主要记录练习时一些不太熟悉的语法知识,督促自己学习,仅记录部分题目和知识

1. Getting Started

两道小题目,主要是介绍HDLBits练习模式,和普通的OJ差不多,通过比较程序运行结果与正确结果判断正误

2. Verilog Language

2.1 Basics

一些wire、reg和基础门部件的使用

2.2 Vectors

2.2.2 Vectors in more detail

Implicit nets

主要是指一些没有声明,而是通过(1)assign赋值语句,(2)module模块端口引用直接创建的变量,是一位wire类型变量,有时候会产生一些bugs,可以通过 `default_nettype none指令禁用

wire [2:0] a, c;   	// Two vectors
assign a = 3'b101;  // a = 101
assign b = a;       // b =   1  implicitly-created wire
assign c = b;       // c = 001  <-- bug
my_module i1 (d,e); // d and e are implicitly one-bit wide if not declared.
                    // This could be a bug if the port was intended to be a vector.
Unpacked vs. Packed Arrays

二维数组的声明

reg [7:0] mem [255:0];   // 256 unpacked elements, each of which is a 8-bit packed vector of reg.
reg mem2 [28:0];         // 29 unpacked elements, each of which is a 1-bit reg.
Accessing Vector Elements: Part-Select

向量赋值时,位数不匹配则会默认补零和截断

assign w = a;

w[3:0]      // Only the lower 4 bits of w
x[1]        // The lowest bit of x
x[1:1]      // ...also the lowest bit of x
z[-1:-2]    // Two lowest bits of z
b[3:0]      // Illegal. Vector part-select must match the direction of the declaration. 
			// 需与声明方向一致
b[0:3]      // The *upper* 4 bits of b.
assign w[3:0] = b[0:3];    	// Assign upper 4 bits of b to lower 4 bits of w. w[3]=b[0], w[2]=b[1], etc. 
							// 对应位一一赋值

2.2.5 Four-input gates

缩位运算,位之间运算得一位结果,与&、或|、异或^

module top_module( 
    input [3:0] in,
    output out_and,
    output out_or,
    output out_xor
);
    

assign out_and = &in;
assign out_or = |in;
assign out_xor = ^in;

endmodule

2.2.6 Vector concatenation operation

向量拼接,注意位宽不匹配时有截断和补零
比较了下自己的答案和网上别人的答案,自己写的还有点繁琐
自己的:

module top_module (
    input [4:0] a, b, c, d, e, f,
    output [7:0] w, x, y, z );//

    // assign { ... } = { ... };
    assign w = {a[4:0], b[4:2]};
    assign x = {b[1:0], c[4:0], d[4]};
    assign y = {d[3:0], e[4:1]};
    assign z = {e[0], f[4:0], 2'b11};

endmodule

网上的:

module top_module (
    input [4:0] a, b, c, d, e, f,
    output [7:0] w, x, y, z );
    wire [31:0] temp;
    assign temp = {a,b,c,d,e,f,2'b11};
    assign {w,x,y,z}=temp;
endmodule

2.2.7 vector reversal 1

for循环的使用(上课时记得老师说写v最好不用for循环,最后综合会很麻烦……待确认),这里用for循环更适配

module top_module( 
    input [7:0] in,
    output [7:0] out
);
	integer i
	always @(*) begin
		for( i = 0; i < 8; i = i + 1) begin
			out[i] = in[7-i];
		end
	end
endmodule

2.3 Modules: Hierarchy

模块实例化的使用,模块实例化:按顺序一一对应;按端口名称对应

2.3.9 Adder-subtractor

对输入的sub信号进行判断,如果sub=0,则输出(a + b + 0);如果sub=1,则输出(a + ~b + 1)。使用sub对b处理和作为cin,很巧妙

module top_module(
    input [31:0] a,
    input [31:0] b,
    input sub,
    output [31:0] sum
);
    wire cout1_cin2;
    wire [31:0] b_in;
    assign b_in = b ^ {32{sub}};
    
    add16 low (.a(a[15:0]), .b(b_in[15:0]), .cin(sub), .sum(sum[15:0]), .cout(cout1_cin2) );
    add16 high (.a(a[31:16]), .b(b_in[31:16]), .cin(cout1_cin2), .sum(sum[31:16]));
endmodule

2.4 Procedures

2.4.1 Always blocks (combinational)

有两种always块:
组合:always @(*)
时序:always @(posedge clk)
组合逻辑的always块和assign赋值是等价的。always块内可有更丰富的状态,如if-then,case等,但不能有连续赋值语句assign。
assign赋值语句的左边一般为wire类型,always块中左边的变量一般为reg类型

2.4.2 Always blocks (clocked)

verilog中有三种赋值方式:
连续赋值(assign x=y;),只能在always块外使用。
阻塞赋值(x=y;),只能在always块内使用。
非阻塞赋值(x<=y;)只能在always块内使用。
组合逻辑的always块中(always @(*))使用阻塞赋值语句;
时序逻辑的always块中(always @(posedge clk))使用非阻塞赋值语句。

2.4.3 If statement

if语句与2选1电路:不仅if选择的数据被实现成电路模式,else语句也会被实现为电路,然后用选择器选择输出。

2.4.4 If statement latches

避免在使用if语句时生成锁存器
【锁存器是一种对脉冲电平(也就是0或者1)敏感的存储单元电路,而触发器是一种对脉冲边沿(即上升沿或者下降沿)敏感的存储电路。】
if语句当不满足条件时,verilog默认保持输出不变,因为组合逻辑电路不能记录当前的状态,所以就会综合出锁存器。
所以当使用if语句或者case语句时,必须考虑到所有情况并给对应情况的输出进行赋值,要为else或者default中的输出赋值

2.4.5 Case statement

优先编码器,惯性思维需要列举所有情况的case,换种思路,去比较1所在位置

// synthesis verilog_input_version verilog_2001
module top_module (
    input [3:0] in,
    output reg [1:0] pos  );
    
    always @(*) begin
        case(1)
            in[0]: pos = 0;
            in[1]: pos = 1;
            in[2]: pos = 2;
            in[3]: pos = 3;
            default: pos = 0;
        endcase
    end
    
endmodule

2.4.7 Priority encoder with casez

上一题的另一解法,使用casez语句,其中将值z的位视作无关位,也可以用?代替z(2‘bz0=2’b?0)

// demonstration
always @(*) begin
    casez (in[3:0])
        4'bzzz1: out = 0;   // in[3:1] can be anything
        4'bzz1z: out = 1;
        4'bz1zz: out = 2;
        4'b1zzz: out = 3;
        default: out = 0;
    endcase
end

2.4.8 Avoiding latches

为了避免生成锁存器,除了在Always if2中提到要为else或者default中的输出赋值,另一种方法是事先给所有输出赋值,不需要使用default默认保持初值。

2.5 More Verilog Features

2.5.2 Reduction operators

缩位运算符:对向量的位进行 AND、OR 和 XOR,产生一位输出

// demonstration
& a[3:0]     // AND: a[3]&a[2]&a[1]&a[0]. Equivalent to (a[3:0] == 4'hf)
| b[3:0]     // OR:  b[3]|b[2]|b[1]|b[0]. Equivalent to (b[3:0] != 4'h0)
^ c[2:0]     // XOR: c[2]^c[1]^c[0]

按位运算逻辑运算
&:按位相与,结果是一个计算得出来是个数,也可以是boolean值( true or false)
1001&1 ----> 1001&0001 -----> 结果为1
&&:逻辑与,结果是一个boolean值( true or false),运算符两边的结果都必须是boolean值

2.5.5 Combinational for-loop: 255-bit population count

数1操作,统计一个向量里1的个数,for循环

module top_module( 
    input [254:0] in,
    output [7:0] out );
	
    integer i;
    always @(*) begin
        out = 8'd0;
        for(i = 0; i < 255; i++) begin
            out = out + in[i];
        end
    end
endmodule

参考博文提到:这里会产生锁存器,实际运用需斟酌。
锁存器会对电路的影响:
锁存器对毛刺敏感,无异步复位端,不能让芯片在上电时处在确定的状态;
锁存器会使静态时序分析变得很复杂,不利于设计的可重用。

所以,在ASIC设计中,除了CPU高速电路,或者RAM这种对面积很敏感的电路,一般不提倡用锁存器

2.5.6 Generate for-loop: 100-bit binary adder 2

generate语句:常用于编写可配置的、可综合的RTL的设计结构,它可用于创建模块的多个实例化,或者有条件的实例化代码块。与for循环语句的语法很相似,但是在使用时必须先在genvar声明中声明循环中使用的索引变量名,然后才能使用它。
generate循环中的generate块可以命名也可以不命名。如果已命名,则会创建一个generate块实例数组。如果未命名,则有些仿真工具会出现警告,因此,最好始终对它们进行命名
实例化100个全加器:

module top_module( 
    input [99:0] a, b,
    input cin,
    output [99:0] cout,
    output [99:0] sum );
	genvar i;
    generate
        for(i = 0; i < 100; i++) begin:adder
            if(i == 0)
                assign {cout[0], sum[0]} = a[0] + b[0] + cin;
            else
                assign {cout[i], sum[i]} = a[i] + b[i] + cout[i-1];
    	end
    endgenerate
endmodule

3. Circuits

3.1 Combinational Logic

3.1.1 Basic Gates

3.1.1.13 Ring or vibrate?

锻炼只使用assign赋值语句,将问题描述转化为逻辑门的集合

  • 设计电路时,人们通常必须“backwards”思考问题,从输出到输入这通常与人们对(顺序的、命令式的)编程问题的看法相反,在这种问题中,人们会先查看输入,然后再决定操作(或输出)
  • 对于顺序程序,通常是"If (inputs are xxx ) then (output should be xxx)",而硬件设计通常是“The (output should be xxx) when (inputs are xxx)
  • 问题描述是用适合软件编程的命令式形式写的(if ring then do this),必须把它转换成更适合硬件实现的声明形式(assign ringer = xxx)
3.1.1.16 Gates and vectors

首先会想到用for循环,参考其他答案有另一种更简便的方法,向量拼接选择后直接assign赋值

module top_module( 
    input [3:0] in,
    output [2:0] out_both,
    output [3:1] out_any,
    output [3:0] out_different );

    assign out_both = in[2:0] & in[3:1];
    assign out_any = in[3:1] | in[2:0];
    assign out_different = in[3:0] ^ {in[0],in[3:1]};
    
endmodule

3.1.2 Multiplexers

3.1.2.4 256-to-1 multiplexer

位宽为1的256-1多路选择器,256个输入都被打包成一个256位的输入向量,sel=0表示选择in[0],sel=1表示选择in[1],以此类推。题中提示到:向量的索引可以是变量,可以根据索引进行数据的选择,但要注意的是被选取数据的位宽是否为常数

Vector indices can be variable, as long as the synthesizer can figure out that the width of the bits being selected is constant. In particular, selecting one bit out of a vector using a variable index will work.

module top_module( 
    input [255:0] in,
    input [7:0] sel,
    output out );
    assign out = in[sel];
endmodule

3.1.2.5 246-to-1 4-bit multiplexer

位宽为4的256-1多路选择器,256个4位输入都被打包成一个1024位的输入向量,sel=0选择in[3:0],sel=1选择in[7:4],sel=2选择in[11:8],以此类推。与上一题的区别在于,输出位宽为4位。
不能使用assign out = in[sel * 4 + 3 : sel * 4],此时会报错,因为该等式不能证明选择的这个位宽是个常数,即输出的位宽不确定,而上一题中能明确输出的位宽就是1位

module top_module( 
    input [1023:0] in,
    input [7:0] sel,
    output [3:0] out );
    assign out = in >> {sel,2'b0};	// 高位会被截断
    // assign out = {in[sel*4+3],in[sel*4+2],in[sel*4+1],in[sel*4]};
endmodule

3.1.3 Arithmetic Circuits

3.1.3.5 Signed addition overflow

判断有符号数相加是否溢出,有符号数的溢出分两种:两个正数相加产生一个负数;两个负数相加产生一个正数。更详细可以参考:深入理解机器码(原码,反码,补码)和算术溢出
该题中检测有符号溢出的方法就是比较输入数a、b和输出结果s的最高位符号位,分两种:正数相加产生负数(a[7] & b[7] & !s[7])和负数相加产生正数(!a[7] & !b[7] & s[7])

module top_module (
    input [7:0] a,
    input [7:0] b,
    output [7:0] s,
    output overflow
); 
    assign s = a + b;
    assign overflow = (a[7]&b[7]&~s[7]) | ((~a[7])&(~b[7])&s[7]);
endmodule

3.1.4 Karnaugh Map to Circuit

该部分的题目涉及到卡诺图、真值表等与逻辑代数有关知识,很多都忘了……重新整理了一下数电中学过的逻辑代数基础内容:逻辑代数基础

3.1.4.3 4-variable

卡诺图中的d为逻辑函数中的无关项,最后可化简为 out = a + a’b’c

module top_module(
    input a,
    input b,
    input c,
    input d,
    output out  ); 
    assign out = a | (~a & ~b & c);
endmodule
3.1.4.4 4-variable

卡诺图变成了0、1相邻排列,几何相邻不成立,无法用与或表达式方便地表示了。一种思路仍然是最小项之和一一表示。
参考Verilog HDL题库练习–题目来源HDLBits,提供了更巧妙的思路,直接四个变量异或:如果两数相异结果为1,多个数中如果出现奇数个1结果为1、偶数个1为0

module top_module(
    input a,
    input b,
    input c,
    input d,
    output out  ); 
	assign out = a ^ b ^ c ^ d;
endmodule

3.2 Sequential Logic

3.2.1 Latches and Flip-Flops

D触发器

D触发器有集成触发器和门电路组成的触发器,触发方式有电平触发和边沿触发两种,前者在CP(时钟脉冲)=1时即可触发,后者多在CP的前沿(正跳变0→1)触发
D触发器的次态取决于触发前D端的状态,即次态=D。因此,它具有置0、置1两种功能。
对于边沿D触发器,由于在CP=1期间电路具有维持阻塞作用,所以在CP=1期间,D端的数据状态变化,不会影响触发器的输出状态。

DCLKQQN
0时钟上升沿01
1时钟上升沿10
x0last Qlast QN
x1last Qlast QN

在这里插入图片描述

同步复位与异步复位

同步复位:复位信号只有在时钟上升沿到来时,才能有效;否则,无法完成对系统的复位工作

always @ (posedge clk) begin
	if (!Rst_n)
		...
end

异步复位:无论时钟沿是否到来,只要复位信号有效,就对系统进行复位

always @ (posedge clk or negedge Rst_n) begin
	if (!Rst_n)
		...
end
3.2.1.15 Detect an edge

脉冲边沿检测(转载参考博文)
检测下降沿:高电平变低电平;
检测上升沿:低电平变高电平。
检测脉冲边沿,只需将前后进来的信号做异或运算,即两个电平不相同则是发生边沿。


设计寄存器用来接收被检测的信号;若{先进reg,后进reg}=2’b10,则是下降沿;
若{先进reg,后进reg}=2’b01,则为上升沿。
使用多个寄存器可以更好的检测边沿,防止干扰脉冲。
下述代码方法可以滤除20-40ns的毛刺

always @ (posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        rs232_rx0 <= 1'b0;
        rs232_rx1 <= 1'b0;
        rs232_rx2 <= 1'b0;
        rs232_rx3 <= 1'b0;
	end
    else begin
        rs232_rx0 <= rs232_rx;
        rs232_rx1 <= rs232_rx0;
        rs232_rx2 <= rs232_rx1;
        rs232_rx3 <= rs232_rx2;
    end
end

assign neg_rs232_rx = rs232_rx3 & rs232_rx2 & ~rs232_rx1 & ~rs232_rx0;

后进信号rs232_rx0,rs232_rx1,必须都为0,且先进信号rs232_rx3 ,rs232_rx2都必须为1,neg_rs232_rx 才会为1,则此时判断为下降沿。

该题要求检测每一位的上升沿,当且仅当,前输入信号为0,后输入信号为1时,产生上升沿

module top_module (
    input clk,
    input [7:0] in,
    output [7:0] pedge
);
    reg [7:0] temp;
    always @(posedge clk) begin
        temp_in <= in;
        pedge <= ~temp & in;
    end

endmodule
3.2.1.18 Dual-edge triggered flip-flop

时钟双沿触发器always @(posedge clk or negedge clk) 不可综合

module top_module (
    input clk,
    input d,
    output q
);
    reg q1,q2;
    always @(posedge clk) begin
        q1<=d;
    end 
    always @(negedge clk) begin
        q2<=d;
    end    
    assign q = clk?q1:q2;     
endmodule
module top_module (
    input clk,
    input d,
    output q
);
    reg q1,q2;
    always @(posedge clk) begin
        q1<= d ^ q2;
    end
    always @(negedge clk) begin
        q2<= d ^ q1;
    end  
    assign q = q1 ^ q2; 
endmodule

任何一个数异或一个数再异或同一个数,将得到本身(最简单的加密与解密原理)
第二种方法相较于第一种方法少了使用clk信号进行选择,可以避免产生毛刺,推荐使用第二种方法进行双边检测。

3.2.2 Counters

3.2.2.5 Counter 1-12

每太看懂题目意思,参考了其他博客的答案

module top_module (
    input clk,
    input reset,
    input enable,
    output [3:0] Q,
    output c_enable,
    output c_load,
    output [3:0] c_d
); 
	// 检测使能端
	assign c_enable = enable;
	// load用于检测是不是要加载数据,只有当复位 或者 计数达到最大值且使能端为1 时,才可以加载数据
    assign c_load = reset | ((Q == 4'd12) && (enable == 1'b1));
    // 判断有没有加载数据
    assign c_d = c_load ? 4'd1 : 4'd0;
    // 例化
    count4 the_counter (clk, c_enable, c_load, c_d, Q);
endmodule
3.2.2.6 Counter 1000

例化BCD模块实现降频操作,1kHz->1Hz。

module top_module (
    input clk,
    input reset,
    output OneHertz,
    output [2:0] c_enable
); //
    wire [3:0] Q1, Q2, Q3;
    assign c_enable[0] = 1'b1;
    assign c_enable[1] = (Q1 == 4'd9) ? 1'b1 : 1'b0;
    assign c_enable[2] = (Q2 == 4'd9 && Q1 == 4'd9) ? 1'b1 : 1'b0;
    assign OneHertz = (Q1 == 4'd9 && Q2 == 4'd9 && Q3 == 4'd9) ? 1'b1 : 1'b0;
    
    bcdcount counter0 (clk, reset, c_enable[0], Q1);
    bcdcount counter1 (clk, reset, c_enable[1], Q2);
    bcdcount counter2 (clk, reset, c_enable[2], Q3);

endmodule
3.2.2.8 12-hour-clock

该题需要注意的是输出的各hh、mm、ss每位4位宽,进制为10,mm和ss的个位满9(1001)时就要进位,十位个位组成59时,要向前一个进位,而hh的取值范围为01、02、……、12。
(梦回刚开始学C语言时 算第多少天的年月日……)

module top_module(
    input clk,
    input reset,
    input ena,
    output pm,
    output [7:0] hh,
    output [7:0] mm,
    output [7:0] ss); 
	
    // pm
    always @(posedge clk) begin
        if(reset) begin
            pm <= 1'b0;
        end
        else begin
            if(ena) begin
                // hh mm ss == 11 59 59
                if(hh == {4'd1,4'd1} && mm == {4'd5,4'd9} && ss == {4'd5,4'd9}) begin
                    pm <= ~pm;
                end
                else begin
                    pm <= pm;
                end
            end
            else begin
                pm <= pm;
            end
        end
    end
    
    // ss
	always @(posedge clk) begin
        if(reset) begin
            ss <= 8'h0;
        end
        else begin
            if(ena) begin
                // ss == x9
                if(ss[3:0] == 4'd9) begin
                    // ss == 59
                    if(ss[7:4] == 4'd5) begin
                        ss <= 8'd0;
                    end
                    else begin
                        ss[3:0] <= 4'd0;
                        ss[7:4] <= ss[7:4] + 1'b1;
                    end
                end
                else begin
                    ss <= {ss[7:4], ss[3:0]+1'b1};
                end
            end
            else begin
                ss <= ss;
            end
        end
    end
    
    // mm
	always @(posedge clk) begin
        if(reset) begin
            mm <= 8'h0;
        end
        else begin
            if(ena) begin
                // ss == 59
                if(ss == {4'd5, 4'd9}) begin
                    // mm == x9
                    if(mm[3:0] == 4'd9) begin
                        // mm == 59
                        if(mm[7:4] == 4'd5) begin
                            mm <= 8'd0;
                        end
                        else begin
                            mm <= {mm[7:4]+1'b1, 4'd0};
                        end
                    end
                    else begin
                        mm <= {mm[7:4], mm[3:0]+1'b1};
                    end
                end
                else begin
                    mm <= mm;
                end
            end
            else begin
                mm <= mm;
            end
        end
    end
    
    // hh
	always @(posedge clk) begin
        if(reset) begin
            hh <= {4'd1,4'd2};
        end
        else begin
            if(ena) begin
                if(mm == {4'd5, 4'd9} && ss == {4'd5, 4'd9}) begin
                    // 01 02 03 ... 09 10 11 12
                    if(hh[7:4] == 4'd1) begin
                        if(hh[3:0] == 4'd2) begin
                            hh <= {4'd0, 4'd1};
                        end
                        else begin
                            hh <= {hh[7:4], hh[3:0]+1'b1};
                        end
                    end
                    else begin
                        if(hh[3:0] == 4'd9) begin
                            hh <= {4'd1, 4'd0};
                        end
                        else begin
                            hh <= {hh[7:4], hh[3:0]+1'b1};
                        end
                    end
                end
                else begin
                    hh <= hh;
                end
            end
            else begin
                hh <= hh;
            end
        end
    end
    
endmodule

参考博文的博主建议:时钟的进位条件单独用assign列出,层次感更加清晰

3.2.3 Shift Register

3.2.3.9 3-input LUT

3输入查找表LUT的设计(以前完全没有听说过LUT这个概念,学FPGA后才逐渐知道是最底层的资源,但还是很模模糊糊没有完全理解,更多参考:FPGA LUT查找表原理和编程方式),该问题需实现根据3位地址输出8位数据中的一位,具体功能设定如题:

题述翻译
在这个问题中,你将为一个8x1存储器设计一个电路,在这个电路中,写入到存储器是通过移位来完成的,而读取是“随机访问”,就像在一个典型的RAM中一样。然后您将使用该电路实现一个3输入逻辑功能。
首先,用8个d类型触发器创建一个8位移位寄存器。标记为Q[0]到Q[7]。移位寄存器输入称为S,输入Q[0] (MSB先移位)。使能输入enable控制是否移位,扩展电路使其有3个额外的输入A,B,C和一个输出Z。电路的行为应该如下:当ABC为000时,Z=Q[0],当ABC为001时,Z=Q[1],以此类推。你的电路应该只包含8位移位寄存器和多路复用器。(这个电路称为3输入查找表(LUT))。

module top_module (
    input clk,
    input enable,
    input S,
    input A, B, C,
    output Z );     
    reg [7:0] Q;
    always @(posedge clk) begin
        if(enable) begin
            Q <= {Q[6:0], S};
        end
        else begin
            Q <= Q;
        end
    end    
    assign Z = Q[{A,B,C}];
endmodule

3.2.4 More Circuits

3.2.4.2 Rule110

根据给出的真值表,画出卡诺图,化简可以得到代数逻辑式,以L为q的Left,C为q,R为q的Right,我化简出来的逻辑式是 C’R + L’C + CR’
其中,C = q,L = {1’b1, q[511:1]},R = {q[510:0], 1’b0}

module top_module(
    input clk,
    input load,
    input [511:0] data,
    output [511:0] q
); 
    always @(posedge clk) begin
        if(load) begin
            q <= data;
        end
        else begin
            q <= (~q & {q[510:0],1'b0}) | (~{1'b0,q[511:1]}&q) | (~{q[510:0],1'b0}&q);
        end
    end
endmodule
3.2.4.3 Conway’s Game of Life 16x16

该题涉及到了阻塞赋值:因为需在当前周期内算出count并对当前算出的count值进行判断,需用阻塞赋值。

module top_module(
    input clk,
    input load,
    input [255:0] data,
    output [255:0] q ); 
    
    integer i;
    reg [3:0] counter;
    
    always @(posedge clk) begin
        if(load) begin
            q <= data;
        end
        else begin
            for(i = 0; i < 256; i++) begin
                // (0, 0)
                if(i == 0) begin
                    counter = q[1] + q[15] + q[16] + q[17] + q[31] + q[240] + q[241] + q[255];
                end
                // (0, 15)
                else if(i == 15) begin
                    counter = q[14] + q[0] + q[30] + q[31] + q[16] + q[254] + q[255] + q[240];
                end
                // (15, 0)
                else if(i == 240) begin
                    counter = q[224] + q[225] + q[239] + q[241] + q[255] + q[0] + q[1] + q[15];
                end
                // (15, 15)
                else if(i == 255) begin
                    counter = q[238] + q[239] + q[224] + q[254] + q[240] + q[14] + q[15] + q[0];
                end
                // (1~14, 0)
                else if(i % 16 == 0) begin
                    counter = q[i-1] + q[i-16] + q[i-15] + q[i+15] + q[i+1] + q[i+31] + q[i+16] + q[i+17];
                end
                // (1~14, 15)
                else if(i % 16 == 15) begin
                    counter = q[i-17] + q[i-16] + q[i-31] + q[i-1] + q[i-15] + q[i+1] + q[i+15] + q[i+16];
                end
                // (0, 1~14)
                else if(0 < i && i < 15) begin
                    counter = q[239+i] + q[240+i] + q[241+i] + q[i-1] + q[i+1] + q[i+15] + q[i+16] + q[i+17];
                end
                // (15, 1~14)
                else if(240 < i && i < 255) begin
                    counter = q[i-17] + q[i-16] + q[i-15] + q[i-1] + q[i+1] + q[i-239] + q[i-240] + q[i-241];
                end
                else begin
                    counter = q[i-17] + q[i-16] + q[i-15] + q[i-1] + q[i+1] + q[i+15] + q[i+16] + q[i+17];
                end

                case(counter)
                    4'd2: q[i] <= q[i];
                    4'd3: q[i] <= 1'b1;
                    default: q[i] <= 1'b0;
                endcase
            end
        end
    end
endmodule

3.2.5 Finite State Machines

整章节练习题全部关于状态机,已对状态机做简单总结:Verilog状态机
【待完整更新该章节内容】

3.3 Building Larger Circuits

3.3.3 FSM: Sequence 1101 recognizer

经典的状态机序列检测,形式比较简单,不需要重复检测,需要注意的是该题要求一旦检测到序列则信号一直抬高不变,即状态保持不变,直到reset信号重置归位。

3.3.4 FSM: Enable shift register

使用状态机检测

module top_module (
    input clk,
    input reset,      // Synchronous reset
    output shift_ena);
    
    parameter S0 = 'd0, S1 = 'd1, S2 = 'd2, S3 = 'd3, S4 = 'd4;
    
    reg [2 : 0] current_state, next_state;
    
    always @(posedge clk) begin
        if(reset) begin
            current_state = S0;
        end else begin
            current_state = next_state;
        end
    end
    
    always @(*) begin
        case(current_state)
            S0: next_state = reset ? S0 : S1;
            S1: next_state = reset ? S0 : S2;
            S2: next_state = reset ? S0 : S3;
            S3: next_state = reset ? S0 : S4;
            S4: next_state = reset ? S0 : S4;
            default:next_state = S0;
        endcase
    end
    
    assign shift_ena = (current_state == S4) ? 1'b0 : 1'b1;

endmodule

3.3.6 The complete timer

以 3.3.5 FSM: The complete FSM 按照提示给出的状态机为基础,在B0-B3状态读取delay,Count状态时使用计数器计数

module top_module (
    input clk,
    input reset,      // Synchronous reset
    input data,
    output [3:0] count,
    output counting,
    output done,
    input ack );
    
    
    parameter S = 'd0, S1 = 'd1, S2 = 'd2, S3 = 'd3;
    parameter B0 = 'd4, B1 = 'd5, B2 = 'd6, B3 = 'd7;
    parameter Count = 'd8, Wait = 'd9;
    
    reg [3 : 0] current_state, next_state;
    reg	[3 : 0]	delay;
    
    
    // counter
    reg	[15 : 0]	cnt;
    always @(posedge clk) begin
        if(reset) begin
            cnt <= 'd0;
        end else if(next_state == Count) begin
            cnt <= cnt + 1'b1;
        end else begin
            cnt <= 'd0;
        end
    end
    
    wire [15 : 0] delay_cyc;
    assign delay_cyc = (delay + 1'b1) * 'd1000;
    
    // state machine
    always @(posedge clk) begin
        if(reset) begin
            current_state <= S;
        end else begin
            current_state <= next_state;
        end
    end
    
    always @(*) begin
        case(current_state)
            S: 		next_state = data ? S1 : S;
            S1:		next_state = data ? S2 : S;
            S2:		next_state = data ? S2 : S3;
            S3:		next_state = data ? B0 : S;
            B0: begin
                	next_state = B1;
                	delay[3] = data;
            	end
            B1: begin
                	next_state = B2;
                	delay[2] = data;
            	end
            B2: begin
                	next_state = B3;
                	delay[1] = data;
            	end
            B3: begin
                	next_state = Count;
                	delay[0] = data;
            	end
            Count:	next_state = cnt == delay_cyc ? Wait : Count;
            Wait:	next_state = ack ? S : Wait;
            default:next_state = S;
        endcase
    end
    
    assign count = (delay_cyc - cnt) / 1000;
    assign counting = (current_state == Count);
    assign done = (current_state == Wait);

endmodule

4 Verification: Reading Simulations

4.1 Finding bugs in code

根据波形结果和给定代码解决bug

4.2 Build a circuit from a simulation waveform

从波形图决定组合逻辑(画卡诺图)和时序逻辑的电路结构

4.2.2 Combinational circuits 2

参考 3.1.4.4 4-variable,卡诺图变成了0、1相邻排列,几何相邻不成立,无法用与或表达式方便地表示了。
一种思路仍然是最小项之和一一表示。
参考Verilog HDL题库练习–题目来源HDLBits,提供了更巧妙的思路,直接四个变量异或:如果两数相异结果为1,多个数中如果出现奇数个1结果为1、偶数个1为0

module top_module (
    input a,
    input b,
    input c,
    input d,
    output q );//

    assign q = !(a ^ b ^ c ^ d); // Fix me

endmodule

4.2.10 Sequential circuit 10

参考 HDLbits答案更新系列22(4.2 Build a circuit from a simulation waveform) 中的评论,提到该题对时序电路中else语句的解释:

always@(posedge clk)begin
	if(a == b)begin
		state <= a;
	end
	else begin
		state <= 1'bx; 

Q:可不可以这么理解,当a不等于b的时候,state的值可以为0,可以为1;是不定值;

A:你可以理解为带保持功能的D触发器
在数字IC或FPGA领域,D触发器相对于RS触发器用的更多,你以后接触到的基本都是D触发器,如果是组合逻辑,if后面没有else会生成锁存器,如果是时序逻辑,if后面没有else会生成带保持功能的D触发器,在很多场景,时序逻辑不加else可以节省功耗,综合的时候会自动生成clock gating,可以了解一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值