1-7:HDLBits刷题记录(持续更新中)
前几天想要通过HDLBits刷题来熟练使用verilog语音,这几天想清楚一件事情,很多事情浅尝辄止都是只能停留在表面的很多东西也都不能深入的了解,这对个人发展来说是无益的,说到底还是应该熟练掌握一门技术,我选择了自己相对比较熟悉一点的verilog,并没有因为多熟悉,只是因为相对熟悉一些,再加上以后说不定能靠这个吃饭,所以在这里记录下自己刷题的记录。
此前已经刷了一小部分内容,突然想记录下来。
Getting Started
Step ONE
-
We’re going to start with a small bit of HDL to get familiar with the interface used by HDLBits. Here’s the description of the circuit you need to build for this exercise:
-
Build a circuit with no inputs and one output. That output should always drive 1 (or logic high).
-
这个页面是一个简单的问题,让你构建一个没有输入、一个输出的电路,该输出输出一个常数0。
-
HDLBits使用Verilog-2001 ANSI风格的端口声明语法,因为它更易于阅读,减少了拼写错误。你也可以使用旧的Verilog-1995语法。例如,下面两个模块声明是可以接受的,且等效的:
module top_module( output one );
// Insert your code here
assign one = 1;
endmodule
Output Zero
module top_module(
output zero
);// Module body starts after semicolon
endmodule
Verilog Languages
Basics
simple wire
module top_module( input in, output out );
assign out = in;
endmodule
与物理连线不同,Verilog 中的连线(和其他信号)是有方向性的。这意味着信息仅沿一个方向流动,从(通常是一个)源到接收器(源也通常称为将值驱动到线路上的驱动程序)。在 Verilog“连续分配”( assign left_side = right_side;
) 中,右侧信号的值被驱动到左侧的线上。赋值是“连续的”,因为即使右侧的值发生变化,赋值也会一直持续。连续分配不是一次性事件。
four wires
module top_module(
input a,b,c,
output w,x,y,z );
assign w = a;
assign x = b;
assign y = b;
assign z = c;
endmodule
有多个 assign 语句时,它们在代码中出现的顺序并不重要。与编程语言不同, assign 语句(“连续赋值”)描述事物之间的连接,而不是将值从一个事物复制到另一个事物的操作。 这里叫人感受到了verilog的设计哲学,wire表示连接而不是赋值,给人一种来自于物理世界的抽象,但又不像编程语言那样高度抽象的介于中间的感受。
inverter
module top_module( input in, output out );
assign out = !in;
endmodule
Use an assign statement. The assign
statement will continuously drive the inverse of in
onto wire out
.
这句话似乎暗示着,assign在verilog语言里有一种表示着驱动的力
and gate
module top_module(
input a,
input b,
output out );
assign out = a & b;
endmodule
nor gate
module top_module(
input a,
input b,
output out );
assign out = !(a | b);
endmodule
assign
语句用一个值驱动一条线(或“网络”,更正式的称呼)。该值可以是您想要的复杂函数,只要它是组合(即无内存,没有隐藏状态)函数即可。 assign
语句是连续赋值,因为只要任何输入发生变化,输出就会永远“重新计算”,就像简单的逻辑门一样。
xnor gate
module top_module(
input a,
input b,
output out );
assign out = ~(a ^ b);
endmodule
declaring wires
`default_nettype none
module top_module(
input a,
input b,
input c,
input d,
output out,
output out_n );
wire wire_a;
wire wire_b;
assign wire_a = a & b;
assign wire_b = c & d;
assign out = wire_a | wire_b;
assign out_n = !(wire_a | wire_b);
endmodule
这是第一次声明wire,端口声明的不需要重复声明,内部必须在使用之前就声明(这里值得回顾 https://hdlbits.01xz.net/wiki/Wire_decl
7458 chip
module top_module (
input p1a, p1b, p1c, p1d, p1e, p1f,
output p1y,
input p2a, p2b, p2c, p2d,
output p2y );
wire p2ab;
wire p2cd;
wire p1abc;
wire p1fed;
assign p2ab = p2a & p2b;
assign p2cd = p2c & p2d;
assign p1abc = p1a & p1b & p1c;
assign p1fed = p1f & p1e & p1d;
assign p2y = p2ab | p2cd;
assign p1y = p1abc | p1fed;
endmodule
一个综合性的练习
vectors
vectors
module top_module (
input wire [2:0] vec,
output wire [2:0] outv,
output wire o2,
output wire o1,
output wire o0 ); // Module body starts after module declaration
assign outv = vec;
assign o0 = vec[0];
assign o1 = vec[1];
assign o2 = vec[2];
endmodule
wire [7:0] w; 声明一个名为 w 的 8 位向量,其功能相当于具有 8 条独立的连线。
vertors in more detail
隐含网线可能存在的问题:
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.
这段代码主要展示了 Verilog 中隐含网线可能导致的问题。首先,代码定义了两个三位向量 a
和 c
,然后给 a
赋值 3'b101
。接下来,b
被赋值为 a
,但是请注意 b
在之前并没有被声明,所以 b
将会被隐式地创建为一个一位的 wire
,其值为 a
的最低位,即 1
。然后 c
被赋值为 b
,由于 b
是一位的 wire
,所以 c
的值将会是 001
,这就是一个 bug,因为我们原本可能希望 c
的值和 a
相同。
接下来,模块 my_module
的实例 i1
的端口 d
和 e
没有被声明,所以它们将会被隐式地创建为一位宽的 wire
。如果我们原本希望 d
和 e
是向量的话,这就是一个 bug。
在代码的最后,作者提到,如果添加了 default_nettype none
,那么 b = a
这一行就会导致一个错误,这样 bug 就更容易被发现了。因为 default_nettype none
会禁止 Verilog 隐式地创建网线,所以如果 b
没有被声明,代码就会报错。
即隐式创建的的网络其实只有一位,而不是向量的相同位数
`default_nettype none // Disable implicit nets. Reduces some types of bugs.
module top_module(
input wire [15:0] in,
output wire [7:0] out_hi,
output wire [7:0] out_lo );
assign out_hi = in [15:8];
assign out_lo = in [7:0];
endmodule
vector part select
module top_module(
input [31:0] in,
output [31:0] out );//
// assign out[31:24] = ...;
assign out[31:24] = in[7:0];
assign out[23:16] = in[15:8];
assign out[15:8] = in[23:16];
assign out[7:0] = in[31:24];
endmodule
bitwise operators
module top_module(
input [2:0] a,
input [2:0] b,
output [2:0] out_or_bitwise,
output out_or_logical,
output [5:0] out_not
);
assign out_or_bitwise = a | b;
assign out_or_logical = (a[0] || a[1] || a[2]) || (b[0] || b[1] || b[2]);
assign out_not = {~b, ~a};
endmodule
按位或和逻辑或的区别
four-input gates
module top_module(
input [3:0] in,
output out_and,
output out_or,
output out_xor
);
assign out_and = in[0] & in[1] & in[2] & in[3];
assign out_or = in[0] | in[1] | in[2] | in[3];
assign out_xor = in[0] ^ in[1] ^ in[2] ^ in[3];
endmodule
vector concatenation operator
module top_module (
input [4:0] a, b, c, d, e, f,
output [7:0] w, x, y, z );//
// assign { ... } = { ... };
wire [31:0] concatenated = {a, b, c, d, e, f, 2'b11};
assign w = concatenated[31:24];
assign x = concatenated[23:16];
assign y = concatenated[15:8];
assign z = concatenated[7:0];
endmodule
//或者直接一行
// assign {w, x, y, z} = {a, b, c, d, e, f, 2'b11} ;
连接运算符 {a,b,c} 用于通过将向量的较小部分连接在一起来创建更大的向量。
vector reversal 1
module top_module(
input [7:0] in,
output [7:0] out
);
assign out = {in[0], in[1], in[2], in[3], in[4], in[5], in[6], in[7]};
endmodule
replication operator
这里卡了我很久,结果是自己粗心大意看的时候没注意,用到复制操作符的时候应该是 {24{in}} 要注意括号数
一开始我就只是24{in}, 这样怎么可行呢 执行 assign a = {b,b,b,b,b,b}; 之类的操作仍然很乏味。复制运算符允许重复向量并将它们连接在一起:
{num{vector}}
module top_module (
input [7:0] in,
output [31:0] out );//
// assign out = { replicate-sign-bit , the-input };
//wrong way, my first try:assign out = {24{in[7]}, in};,其中24{in[7]}应该为一个整体
assign out = {{24{in[7]}}, in};
endmodule
more replication
module top_module (
input a, b, c, d, e,
output [24:0] out );
assign out = {~(a ^ a), ~(a ^ b), ~(a ^ c), ~(a ^ d), ~(a ^ e),
~(b ^ a), ~(b ^ b), ~(b ^ c), ~(b ^ d), ~(b ^ e),
~(c ^ a), ~(c ^ b), ~(c ^ c), ~(c ^ d), ~(c ^ e),
~(d ^ a), ~(d ^ b), ~(d ^ c), ~(d ^ d), ~(d ^ e),
~(e ^ a), ~(e ^ b), ~(e ^ c), ~(e ^ d), ~(e ^ e)};
//assign out = {{5{a}}, {5{b}}, {5{c}}, {5{d}}, {5{e}}} ~^ {5{a, b, c, d, e}};
endmodule
Modules:Hierarchy
modules
module top_module ( input a, input b, output out );
mod_a module_a (.out(out), .in1(a), .in2(b));
endmodule
mod_a instance2 ( .out(wc), .in1(wa), .in2(wb) ); 调用的时候前面为模块,后面为实例
connecting ports by position
module top_module (
input a,
input b,
input c,
input d,
output out1,
output out2
);
mod_a u_mod_a (out1, out2, a, b, c, d );
endmodule
按位置连接,比较严格,个人不喜欢这种方式,稍微改一下全都得改
connecting ports by name
module top_module (
input a,
input b,
input c,
input d,
output out1,
output out2
);
mod_a u_mod_a (.out1(out1), .out2(out2), .in1(a), .in2(b), .in3(c), .in4(d));
endmodule
这种有一定鲁棒性,便于维护
three modules
写一个模块,当三个的用 这里相当于d延迟3个周期之后出现在q信号
module top_module ( input clk, input d, output q );
wire q1;
wire q2;
my_dff u1_my_dff(
.clk(clk),
.d(d),
.q(q1)
);
my_dff u2_my_dff(
.clk(clk),
.d(q1),
.q(q2)
);
my_dff u3_my_dff(
.clk(clk),
.d(q2),
.q(q)
);
endmodule
modules and vectors
这里相当于可以选择输出d第几个周期的移位信号
module top_module (
input clk,
input [7:0] d,
input [1:0] sel,
output [7:0] q
);
wire [7:0] q1;
wire [7:0] q2;
wire [7:0] q3;
my_dff8 u1_my_dff8(
.clk(clk),
.d(d),
.q(q1)
);
my_dff8 u2_my_dff8(
.clk(clk),
.d(q1),
.q(q2)
);
my_dff8 u3_my_dff8(
.clk(clk),
.d(q2),
.q(q3)
);
always@(*)begin
case(sel)
2'b00:begin
q = d;
end
2'b01:begin
q = q1;
end
2'b10:begin
q = q2;
end
2'b11:begin
q = q3;
end
endcase
end
endmodule
adder 1
这里通过位连接符连接两个加法器
module top_module(
input [31:0] a,
input [31:0] b,
output [31:0] sum
);
wire cout;
add16 u1_add16(
.a(a[15:0]),
.b(b[15:0]),
.cin(1'b0),
.cout(cout),
.sum(sum[15:0])
);
add16 u2_add16(
.a(a[31:16]),
.b(b[31:16]),
.cin(cout),
.cout(1'b0),
.sum(sum[31:16])
);
endmodule
adder 2
module top_module (
input [31:0] a,
input [31:0] b,
output [31:0] sum
);
wire cout;
add16 u1_add16(
.a (a[15:0]),
.b (b[15:0]),
.cin(1'b0),
.sum(sum[15:0]),
.cout(cout)
);
add16 u2_add16(
.a (a[31:16]),
.b (b[31:16]),
.cin(cout),
.sum(sum[31:16]),
.cout(1'b0)
);
endmodule
module add1 ( input a, input b, input cin, output sum, output cout );
// Full adder module here
assign {cout, sum} = a + b + cin;
endmodule
carry-select adder
module top_module(
input [31:0] a,
input [31:0] b,
output [31:0] sum
);
wire cout;
wire[15:0] sum_1;
wire[15:0] sum_2;
add16 u1_add16(
.a (a[15:0]),
.b (b[15:0]),
.cin(1'b0),
.sum(sum[15:0]),
.cout(cout)
);
add16 u2_add16(
.a (a[31:16]),
.b (b[31:16]),
.cin(1'b0),
.sum(sum_1),
.cout(1'b0)
);
add16 u3_add16(
.a (a[31:16]),
.b (b[31:16]),
.cin(1'b1),
.sum(sum_2),
.cout(1'b0)
);
assign sum = cout ? {sum_2,sum[15:0]} : {sum_1, sum[15:0]};
endmodule
提升加法器速度,(提前运算,结果选择)
adder-subtractor
module top_module(
input [31:0] a,
input [31:0] b,
input sub,
output [31:0] sum
);
wire [31:0] b_inv;
wire cout1, cout2;
// Invert 'b' if 'sub' is high.
assign b_inv = b ^ {32{sub}};
/*
1 0 = 1
1 1 = 0
0 1 = 1
0 1 = 1
*/
// Instantiate the first 16-bit adder
add16 adder1 (
.a(a[15:0]),
.b(b_inv[15:0]),
.cin(sub),
.sum(sum[15:0]),
.cout(cout1)
);
// Instantiate the second 16-bit adder
add16 adder2 (
.a(a[31:16]),
.b(b_inv[31:16]),
.cin(cout1),
.sum(sum[31:16]),
.cout(cout2)
);
endmodule
可完成加减法运算,最终的结果是一个可以执行两个操作的电路:(a + b + 0) 和 (a + ~b + 1)
sub输入决定我们是执行加法还是减法。当sub为0时,我们执行加法;当sub为1时,我们执行减法。
我们使用一个XOR门来根据sub的值选择性地反转b的所有位。XOR门的功能是,当且仅当两个输入不同时,输出为1。所以,当sub为0时,b的所有位都保持不变;当sub为1时,b的所有位都被反转。
procedures
always blocks(combinational)
// synthesis verilog_input_version verilog_2001
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
always blocks(clocked)
// synthesis verilog_input_version verilog_2001
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 statement
// synthesis verilog_input_version verilog_2001
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
if statement latches
// synthesis verilog_input_version verilog_2001
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 statement
// synthesis verilog_input_version verilog_2001
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 // This is a combinational circuit
case(sel)
3'b000: out = data0;
3'b001: out = data1;
3'b010: out = data2;
3'b011: out = data3;
3'b100: out = data4;
3'b101: out = data5;
default: out = 0;
endcase
end
endmodule
priority encoder
// synthesis verilog_input_version verilog_2001
module top_module (
input [3:0] in,
output reg [1:0] pos );
always@(*) begin
case(in)
4'b0000: pos = 2'b00;
4'b0001: pos = 2'b00;
4'b0010: pos = 2'b01;
4'b0011: pos = 2'b00;
4'b0100: pos = 2'b10;
4'b0101: pos = 2'b00;
4'b0110: pos = 2'b01;
4'b0111: pos = 2'b00;
4'b1000: pos = 2'b11;
4'b1001: pos = 2'b00;
4'b1010: pos = 2'b01;
4'b1011: pos = 2'b00;
4'b1100: pos = 2'b10;
4'b1101: pos = 2'b00;
4'b1110: pos = 2'b01;
4'b1111: pos = 2'b00;
endcase
end
endmodule
priority encoder with casez
// synthesis verilog_input_version verilog_2001
module top_module (
input [7:0] in,
output reg [2:0] pos );
always@(*) begin
casez(in)
8'bzzzzzzz1: pos = 3'b000;
8'bzzzzzz10: pos = 3'b001;
8'bzzzzz100: pos = 3'b010;
8'bzzzz1000: pos = 3'b011;
8'bzzz10000: pos = 3'b100;
8'bzz100000: pos = 3'b101;
8'bz1000000: pos = 3'b110;
8'b10000000: pos = 3'b111;
default: pos = 0;
endcase
end
endmodule
avoiding latches
// synthesis verilog_input_version verilog_2001
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'he06b: left = 1'b1;
16'he072: down = 1'b1;
16'he074: right = 1'b1;
16'he075: up = 1'b1;
endcase
end
endmodule
More Verilog Feature
conditional ternary operator
reduction operator
reduction:even wider gates
combinational for-loop:vector reversal 2
combinational for-loop:255-bit population count
generate for-loop:100-bit binary adder2
generate for-loop:100-digit BCD adder