目录
本篇博客对 Verilog Language 剩余两个部分的题目写完,首先对题干先读懂是关键,然后思考如何实现并验证,这里采用先对题目解读,也就是要让我们干什么,然后直接给出答案。
分别用 assign 语句和 always @(*) 块语句实现与门操作。
- module top_module(
- input a,
- input b,
- output wire out_assign,
- output reg out_alwaysblock
- );
-
- assign out_assign = a & b;
-
- always @(*) begin
- out_alwaysblock = a & b;
- end
-
- endmodule
连续赋值(assign x = y;)。不能在 always 块中使用。
过程阻塞赋值:(x = y;)。只能在过程中使用。
过程非阻塞赋值:(x <= y;)。只能在过程中使用。
在组合 always 块中,使用阻塞赋值。
在时序 always 块中,使用非阻塞赋值。
- module top_module(
- input clk,
- input a,
- input b,
- output wire out_assign,
- output reg out_always_comb,
- output reg out_always_ff
- );
-
- assign out_assign = a ^ b;
-
- always @(*) begin
- out_always_comb = a ^ b;
- end
-
- always @(posedge clk) begin
- out_always_ff <= a ^ b;
- end
-
- endmodule
if 语句通常创建一个 2 对 1 多路复用器,如果条件为 ture,则选择一个输入,如果条件为 false,则选择另一个输入。这等效于使用带有条件运算符的连续赋值:
assign out = (condition) ? x : y;
构建一个在 a 和 b 之间进行选择的 2 对 1 多路复用器。如果sel_b1和sel_b2都为真,请选择 b。否则,请选择 a。执行相同的操作两次,分别使用赋值语句和过程 if 语句。
- module top_module(
- input a,
- input b,
- input sel_b1,
- input sel_b2,
- output wire out_assign,
- output reg out_always
- );
-
- assign out_assign = (sel_b1 & sel_b2) ? b : a;
-
- always @(*) begin
- if (sel_b1 & sel_b2) begin
- out_always = b;
- end
- else begin
- out_always = a;
- end
- end
-
- endmodule
学习如何避免产生 latch, 比如在 always 块中所列举的情况没有完全,会出现你没有列举的情况时,那输出会是什么呢?Verilog的答案是:保持输出不变。这种“保持输出不变”的行为意味着需要记住当前状态,从而产生latch。组合逻辑不能保存任何状态。组合电路必须在所有条件下为所有输出分配一个值。这通常意味着始终需要为输出分配 else 子句或默认值。
- module top_module (
- input cpu_overheated,
- output reg shut_off_computer,
- input arrived,
- input gas_tank_empty,
- output reg keep_driving ); //
-
- always @(*) begin
- if (cpu_overheated) begin
- shut_off_computer = 1;
- end
- else begin
- shut_off_computer = 0;
- end
- end
-
- always @(*) begin
- if (~arrived) begin
- keep_driving = ~gas_tank_empty;
- end
- else begin
- keep_driving = 0;
- end
- end
-
- endmodule
case语句的练习。case 语句以 case 开头,每个“case 项”都以冒号结尾。
每个事例项只能执行一条语句。这意味着,如果需要多个语句,则必须使用begin结束。允许重复(和部分重叠)案例项目。使用第一个匹配的。
在本练习中,创建一个 6 对 1 多路复用器。当 sel 介于 0 和 5 之间时,选择相应的数据输入。否则,输出 0。数据输入和输出均为4位宽。
- module top_module (
- input [2:0] sel,
- input [3:0] data0,
- input [3:0] data1,
- input [3:0] data2,
- input [3:0] data3,
- input [3:0] data4,
- input [3:0] data5,
- output reg [3:0] out
- );
-
- always @(*) begin
- case(sel)
- 0: out = data0;
- 1: out = data1;
- 2: out = data2;
- 3: out = data3;
- 4: out = data4;
- 5: out = data5;
- default: out = 0;
- endcase
- end
-
- endmodule
优先级编码器是一种组合电路,当给定输入位矢量时,输出矢量中第一个1位的位置。例如,给定输入 8'b100 1 0000 的 8 位优先级编码器将输出 3'd4,因为 bit[4] 是第一个高位。构建 4 位优先级编码器。对于此问题,如果输入位均为零,则输出为零。请注意,4 位数字有 16 种可能的组合。
- module top_module (
- input [3:0] in,
- output reg [1:0] pos
- );
-
- always @(*) begin
- case(in)
- 4'b0000: pos = 0;
- 4'b0001: pos = 0;
- 4'b0010: pos = 1;
- 4'b0100: pos = 2;
- 4'b1000: pos = 3;
- 4'b0011: pos = 0;
- 4'b0110: pos = 1;
- 4'b1100: pos = 2;
- 4'b0101: pos = 0;
- 4'b1010: pos = 1;
- 4'b1001: pos = 0;
- 4'b0111: pos = 0;
- 4'b1110: pos = 1;
- 4'b1011: pos = 0;
- 4'b1101: pos = 0;
- 4'b1111: pos = 0;
- endcase
- end
-
- endmodule
case 语句就像是按顺序检查每个情况,这是一个很大的组合逻辑函数。请注意,某些输入(例如,4'b1111)将如何匹配多个事例项。选择第一个匹配项(因此 4'b1111 匹配第一个项目,out = 0,但后面的任何一个都不匹配)。还有一个类似的案例,它将x和z视为不在乎。我看不出在casez上使用它有多大意义。数字 ? 是 z 的同义词。所以 2'bz0 与 2'b?0 相同。
- module top_module (
- input [7:0] in,
- output reg [2:0] pos
- );
-
- always @(*) begin
- casez(in)
- 8'bzzzzzzz1: pos = 0; //z表示不在乎这个是什么值。只需要满足后面位的情况
- 8'bzzzzzz10: pos = 1;
- 8'bzzzzz100: pos = 2;
- 8'bzzzz1000: pos = 3;
- 8'bzzz10000: pos = 4;
- 8'bzz100000: pos = 5;
- 8'bz1000000: pos = 6;
- 8'b10000000: pos = 7;
- endcase
- end
-
- endmodule
在使用case语句时避免产生latch的方法。
为避免产生锁存器,必须在所有可能条件下为所有输出分配一个值。仅仅有一个默认的案例是不够的。必须为所有四种情况下的所有四个输出分配一个值,并在所有默认情况下分配一个值。这可能涉及大量不必要的键入。
解决此问题的一种方法是在case语句之前为输出分配一个“默认值”:
always @(*) begin
up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0;
case (scancode)
... // Set to 1 as necessary.
endcase
end
这种代码样式可确保在所有可能的情况下为输出分配一个值,除非case语句覆盖赋值。
提醒:逻辑合成器生成一个组合电路,其行为与代码描述的行为等效。硬件不会按顺序“执行”代码行。
- module top_module (
- input [15:0] scancode,
- output reg left,
- output reg down,
- output reg right,
- output reg up
- );
-
- always @(*) begin
- up = 1'b0;
- down = 1'b0;
- left = 1'b0;
- right = 1'b0;
- case(scancode)
- 16'he075: up = 1'b1;
- 16'he072: down = 1'b1;
- 16'he06b: left = 1'b1;
- 16'he074: right = 1'b1;
- default: begin
- up = 1'b0;
- down = 1'b0;
- left = 1'b0;
- right = 1'b0;
- end
- endcase
- end
-
- endmodule
给定四个无符号数字,找到最小值。无符号数字可以与标准比较运算符(a < b)进行比较。使用条件运算符制作双向最小电路,然后组合其中的几个以创建 4 路最小电路。可能需要一些导线向量作为中间结果。
- module top_module (
- input [7:0] a, b, c, d,
- output [7:0] min
- );
- wire [7:0] smaller0;
- wire [7:0] smaller1;
- wire [7:0] smaller2;
-
- assign smaller0 = (a > b)? b : a;
- assign smaller1 = (c > d)? d : c;
- assign smaller2 = (smaller0 > smaller1)? smaller1 : smaller0;
- assign min = smaller2;
-
- endmodule
注:也可以设计为流水线的形式,流水线的设计方法可以使系统的运行速率提高。
奇偶校验通常用作通过不完美通道传输数据时检测错误的简单方法。创建一个电路,该电路将计算 8 位字节的奇偶校验位(这将向该字节添加第 9 位)。将使用“偶数”奇偶校验,奇偶校验位只是所有8个数据位的XOR。
一个简单的连续位做异或运算。
- module top_module (
- input [7:0] in,
- output parity
- );
-
- assign parity = ^in;
-
- endmodule
在[99:0]中构建具有100个输入的组合电路。
有 3 个输出:
- module top_module(
- input [99:0] in,
- output out_and,
- output out_or,
- output out_xor
- );
-
- assign out_and = & in;
- assign out_or = | in;
- assign out_xor = ^ in;
-
- endmodule
给定一个 100 位输入向量 [99:0],反转其位排序。
- module top_module(
- input [99:0] in,
- output [99:0] out
- );
- integer i;
- always @(*) begin
- for (i=0;i<100;i=i+1) begin
- out[i] = in[99-i];
- end
- end
- endmodule
对输入位宽为255的数计算其各个位为 1 的个数并输出
- module top_module(
- input [254:0] in,
- output [7:0] out
- );
-
- reg [7:0] cnt;
- integer i;
- always @(*) begin
- cnt = 'd0;
- for (i=0;i<255;i=i+1) begin
- cnt = (in[i])? (cnt+1'b1):cnt;
- end
- end
- assign out = cnt;
-
- endmodule
通过实例化 100 个全加器来创建一个 100 位二进制行波进位加法器。加法器将两个 100 位数字和一个随身数字相加,以产生 100 位总和并执行。实际实例化全加器,还要从行波进位加法器中的每个全加器输出执行。cout[99]是最后一个全加器的最终执行。
注意对第一个全加器计算时的特殊情况。
- 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=i+1) 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
以一个四位的全加器模块,计算两个输入数据的相加,其位宽为400。
在这里吐槽一下 HDLBits 自带的编译器有点神经质,有些没必要的细节过度把控,而有些带有技巧性的语法又不支持...
- module top_module(
- input [399:0] a,b,
- input cin,
- output cout,
- output [399:0] sum
- );
-
- wire [99:0] cin_cnt;
- genvar i;
- generate
- for (i=0;i<100;i=i+1) begin:test
- if (i == 0) begin
- bcd_fadd bcd_fadd_inst(a[3:0],b[3:0],cin,cin_cnt[0],sum[3:0]);
- end
- else begin
- bcd_fadd bcd_fadd_inst(a[4*(i+1)-1:4*i],b[4*(i+1)-1:4*i],cin_cnt[i-1],cin_cnt[i],sum[4*(i+1)-1:4*i]);
- end
-
- end
- assign cout = cin_cnt[99];
- endgenerate
-
- endmodule