HDLbits练习
always@()begin
end
- 加法数选器
问题:2选1 多路复用,可以用assign直接赋值 也可用case
掌握了端口顺序和 .端口名(信号名)两种例化方法
- 加法减法器
注意进入模块例化前是否计算过,注意一位信号与多位信号的逻辑运算。
B[15:0]^sub ×
{32{sub}}^b √
注意运算在模块外还是模块内,注意赋值assign的使用。
- 如果语句(二选一多路选择器)
可以用assign 和 always
- 锁存器:锁存器是一种对脉冲电平(也就是0或者1)敏感的存储单元电路,而触发器是一种对脉冲边沿(即上升沿或者下降沿)敏感的存储电路。
if和case中的锁存器若情况没有声明完全,则Verilog中会默认保持输出结果,在综合之后会生成锁存器,导致不想要的结果。
- Case中必须要写好default,不能遗漏。在一个case里若要写多行语句需要用 case()括号中为一个数 遇到多个数需要用连接符{,}且下面分支要写2‘b00 不能是00!!!!
Begin end,Case中可以重复,若重复按第一次匹配执行。
- 四位优先级编码器需要注意 ①最低位为0位 ②输入一个矢量,若出现第一个高位则输入此位数。
- Case/casez/casex 若为casez 则不用管Z,Z可以和任何数相等,例如z=0,z=1, z=z。
真值表如下:
- Scancode中 题目需求为当扫描码到来时,要确定是否按下键盘下的某个箭头,若按下则为1,不按则为0(扫描一次要给出四个值)
module top_module (
input [15:0] scancode,
output reg left,
output reg down,
output reg right,
output reg up );
always @(*)begin
case(scancode)
16'he06b:
begin
left=1;right = 0;up = 0; down = 0;
end
16'he072:
begin
down=1;left=0;right = 0;up = 0;
end
16'he074:
begin
right=1;down=0;left=0;up = 0;
end
16'he075:
begin
up=1;left = 0; right = 0;down = 0;
end
default:
begin
left = 0; right = 0;up = 0; down = 0;
end
endcase
end
endmodule
如上述所示,太麻烦,改进方法如下,在case前设置好默认值如下所述:
always @(*) begin
left=0;down=0;right=0;up=0;
case(scancode)
16'he06b:left=1;
16'he072:down=1;
16'he074:right=1;
16'he075:up=1;
Endcase
这样除非case语句中重新赋值,否则left等仍处于默认值。注意不能在case里写默认值,这样会出错。
2022.4.5
& a[3:0] == a[3] & a[2] & a[1] & a[0];
Verilog中所有的语句必须写到initial和always语句块中 循环语句一定在always中
integer i; integer i;
always@(*) for(i=0;i<100;i=i+1)
begin out[i] = in[99-i];
for(i=0;i<100;i=i+1) √ ×
out[i] = in[99-i];
end
初始化参数时,在always后for之前初始化。
2022.4.6
bcd fadd单位加法器的实现 √
module top_module( 为什么错?因为该题目给出了要实例化的模块。而上一题并未给出实例化的模块,则用assign代替,若要用
input [399:0] a, b, add实例化模块也可以不过需要自己写一个,不能用现成的。
input cin,
output cout,
output [399:0] sum );
wire [99:0]cout_temp;
genvar j;
generate
for(j=0;j<100;j++)
begin:bcd_fadd
if(j==0)
bcd_fadd b0 (a[3:0],b[3:0],cin,cout_temp[0],sum[3:0]);
else
bcd_fadd bj (a[4*j+3:4*j],b[4*j+3:4*j],cout_temp[j-1],cout_temp[j],sum[4*j+3:4*j]);
end
assign cout = cout_temp[99];
endgenerate
endmodule
module top_module( ×
input [399:0] a, b,
input cin,
output cout,
output [399:0] sum );
wire [99:0] cout_temp;
genvar j;
generate
for(j=0;j<100;j++)
begin:bcd_fadd
if(j==0)
assign {cout_temp[0],sum[3:0]}=a[3:0]+b[3:0]+cin;
else
assign {cout_temp[j],sum[4*j+3:4*j]}=a[4*j+3:4*j]+b[4*j+3:4*j]+cout_temp[j-1];
end
endgenerate
assign cout = cout_temp[99];
endmodule
用真值表构建组合电路方法:使用与门和或门,找出输出为1的输入,0位取反,例~x3,1为本身,例x3. 将所有为1的输入加或门即可。
硬件工程师的思路是先看输出,再看输入,即输出是XX时输入是多少?
人口计数问题中,为什么不能用for() if(in[i]) 可以 if()括号中的为逻辑判断,if(in[i]) 意思是in的第i位为真,而我们需要判断是否为1。
2022.4.7
always 行为级建模和 assign 数据流建模不能嵌套
d只能表示0-9 f为14需要用h十六进制来表示 '1是一种特殊的语法,即为将所有的位全设为1,'0 , 'z, 'x同理。 一般case可以设置默认值从而可以免去写default。
例化既能用add a1(举例) 也可以用assign
两数相加判断是否溢出:看符号位(两个正数相加变负数溢出,两个负数相加变正数溢出)
2022.4.11
bcd_fadd4 为什么这样写是错误的?
module top_module (
input [15:0] a, b,
input cin,
output cout,
output [15:0] sum );
wire [3:0] cout_temp;
genvar i;
generate
for(i = 0 ;i < 4;i++)
begin:bcd_fadd
if(i == 0)
assign{cout_temp[0],sum[3:0]} = a[3:0] + b[3:0] + cin;
else
assign{cout_temp[i],sum[3+4*i:4*i]} = a[3+4*i:4*i] + b[3+4*i:4*i] + cout_temp[i-1];
end
assign cout = cout_temp[3];
endgenerate
endmodule
卡诺图简化
1.先用最小项表达式画出基本卡诺图(为与或) 2.再进行化简写出最简逻辑表达式
化简时要注意:n为变量数,圈的时候只能圈2n个1
- 不允许零。
- 没有对角线。
- 每组只有2个单元的幂。
- 组应尽可能大。
- 每个人必须至少有一组。
- 允许重叠。
- 允许环绕。
- 可能有最少的组数。
圈完后,变化了的变量舍去,未变化的变量0位非,1为本身(sop标准式)
- SOP标准式:找出真值表中所有输出为1的表项,按照输入的情况,为1用变量表示,为0则用反变量表示,得出若干乘积项,然后求和。
- POS标准式:找出真值表中所有输出为0的表项,按照输入的情况,为1用反变量表示,为0则用原变量表示,得出若干求和项,然后求积。
2.若卡诺图中出现d,d既可以为0 也可以为1,具体是多少,尽量按照化简规则最优化控制。
3.题中所述:数字系统在输入上出现2、7或15时生成逻辑1,当0、1、4、5、6、9、10 13或14出现时,生成逻辑0。数字3、8、11和12的输入不会出现在这个系统中,即为d。
2022.4.12(时序电路一般用非阻塞赋值 )时序电路非阻塞赋值
D触发器的同步复位和异步复位,同步:复位端只有在时钟上升沿到来时有效,异步:无论时钟是什么状态,只要复位即有效。
上升沿D触发器特点:次态与时钟上升沿到来之前的状态相同并保持一个时钟周期。注意看是高复位端有效还是低复位端有效!
0x34表示16进制的数 34,题目没有要求的话不用转换成二进制。
同步复位(posedge clk)begin 异步复位(posedge clk or posedge reset) 或 (posedge clk or negedge reset)
if(!reset) if(reset) if(!reset)
else else else
end end end
脉冲边缘检测未搞懂 脉冲边沿特性:两侧电平发生变化
1.若检测上升沿,则由0变为1,若检测下降沿,由1变为0; 2.若检测任意边缘,只需要将先进来的值和后进来的值做异或即可。
若遇到边缘检测题先写下一个时钟的生成信号
2.边缘捕获寄存器(重点关注) 尝试用两种方法! (只要从一个时钟周期中的 1 变为下一个时钟周期中的 0 时,请进行捕获。“捕获”意味着输出将保持1,out=1)
2022.4.14
解决了12:00:00制计数器问题 (重点时间转换 在11:59:59-12:00:00 和12:59:59-01:59:59 和09:59:59)
计数器问题关键:
1.搞清楚使能信号,当有使能信号时且最低位为9时,下一个时钟周期,相应位置的数值才会变。
2.BCD计数器是将一个十进制的数用四位2进制表示出来,reg[3:0]one。
3.为什么代码中用了许多assign? 例:11:48:59 若ss_ten需要进位等价于ss_one进位且ss_one==9; 若mm_one进位等价于ss_ten进位且ss_ten==5;
而ss_ten进位上面已经描述过,只需要赋值引用即可(为了方便)
4.将BCD的每一位都用一个模块写清楚。
5.并且用&&表示。 6.AM,PM问题 重点搞清楚 11:59:59AM 到 12:00:00PM 再从11:59:59PM 到 12:00:00AM。
7.代码如下:
module top_module(
input clk,
input reset,
input ena,
output pm,
output [7:0] hh,
output [7:0] mm,
output [7:0] ss);
reg [3:0] ss_one;
reg [3:0] ss_ten;
reg [3:0] mm_one;
reg [3:0] mm_ten;
reg [3:0] hh_one;
reg [3:0] hh_ten;
wire ss_one_ena;
wire ss_ten_ena;
wire mm_one_ena;
wire mm_ten_ena;
wire hh_one_ena;
wire hh_ten_ena;
wire hh_ten_ena_0;
wire hh_ten_ena_1;
wire pm1;
assign ss_one_ena = ena;
always@(posedge clk)begin
if(reset)
ss_one <= 4'd0;
else if(ss_one_ena)begin
if(ss_one == 4'd9)
ss_one <= 4'd0;
else
ss_one <= ss_one + 1'b1;
end
end
assign ss_ten_ena = (ss_one_ena) && (ss_one == 4'd9);
always@(posedge clk)begin
if(reset)
ss_ten <= 4'd0;
else if(ss_ten_ena)begin
if(ss_ten == 4'd5)
ss_ten <= 4'd0;
else
ss_ten <= ss_ten + 1'b1;
end
end
assign mm_one_ena = ss_ten_ena && (ss_ten == 4'd5);
always@(posedge clk)begin
if(reset)
mm_one <= 4'd0;
else if(mm_one_ena)begin
if(mm_one == 4'd9)
mm_one <= 4'd0;
else
mm_one <= mm_one + 1'b1;
end
end
assign mm_ten_ena = mm_one_ena && (mm_one == 4'd9);
always@(posedge clk)begin
if(reset)
mm_ten <= 4'd0;
else if(mm_ten_ena)begin
if(mm_ten == 4'd5)
mm_ten <= 4'd0;
else
mm_ten <= mm_ten + 1'b1;
end
end
assign hh_one_ena = mm_ten_ena && (mm_ten == 4'd5);
always@(posedge clk)begin
if(reset)
hh_one <= 4'd2;
else if(hh_one_ena)begin
if(hh_one == 4'd9)
hh_one <= 4'd0;
else if(hh_one == 4'd2 && hh_ten == 4'd1)
hh_one <= 4'd1;
else
hh_one <= hh_one + 1;
end
end
assign hh_ten_ena_0 = hh_one == 4'd9 && hh_ten == 4'd0;
assign hh_ten_ena_1 = hh_one == 4'd2 && hh_ten == 4'd1;
always@(posedge clk)begin
if(reset)
hh_ten <= 4'd1;
else if(hh_one_ena)begin
if(hh_ten_ena_0)
hh_ten <= 4'd1;
else if(hh_ten_ena_1)
hh_ten <= 4'd0;
else
hh_ten <= hh_ten;
end
end
always@(posedge clk)begin
if(reset)
pm1 <= 1'b0;
else if (hh_ten == 4'd1 && hh_one == 4'd1 && hh_one_ena )
pm1 <= ~pm1;
end
assign ss = {ss_ten, ss_one};
assign mm = {mm_ten, mm_one};
assign hh = {hh_ten, hh_one};
assign pm = pm1;
endmodule
20222.4.18
if语句中,优先级越高,越先写,否则可能会出错
算术右移和逻辑右移区别是,算术右移需要给最高位补和符号位一样的数字,而逻辑右移补0即可,算术左移和逻辑左移都是右边补零
如果要用连接符,中间的数字不能直接写比如{0,q[63:0]},需要写{1'b0,q[63:0]}。因为后者都是2进制,前者是10进制。
移位寄存器循环时,若中间几位有抽头,可以使用逻辑或并联,而不需要重新写一个循环。
always@(posedge clk)begin 麻烦 循环从0-31,写清楚 i 处于不同值时结果可能的情况即可。
if(reset)
q <= 32'h1;
else begin
for(int i = 3;i < 22;i++)
q [i-1] <= q[i];
for(int j = 23;j < 32;j++)
q [j-1] <= q[j];
q[31] <= 1'b0 ^ q[0];
q[21] <= q[22] ^ q[0];
q[1] <= q[2] ^ q[0];
q[0] <= q[1] ^ q[0];
end
end
电路应具有8位寄存器,若没有定义则等价于 reg [7:0] Q;
异或符号 ^ 表示按位异或,more circuits中,q的值是根据data前后来判断,若使用for循环就改变了data前后的值,导致电路出错,按位异或直接解决。
注意本题按位异或是怎么异或的!
摩尔状态机中,有两个状态,一个状态是1,另一个状态是0. assign out = (state == B) 什么意思? 应该是用最后的out结果判断此时状态是否为B。
袋鼠下落问题:飞溅和死亡不一样,注意下落时超过20个时钟周期才会死亡,要注意新定义的时钟周期怎么变化?(只有在下落时才会变)
若用one-hot FSM可以确保一个状态为为1,这意味着可以通过仅检查一个状态位而不检查所有状态位来确定状态机是否处于特定状态
直接用assign next_state = 更简单。
sel[1]区分不了c和d,此处应该还是sel[0]。此外例化名与变量名不能重复;且wire信号的位宽也不对。
result_is_zero为reg型,若不给出其他可能, 只要触发则锁存,就会产生锁存电路
2022.4.26
若要输出数据例如data[7:0],输入却是in(一位)则要将串联变为并联
创建更大的电路中,第三题状态逻辑改变时,时多多考虑当前是什么状态(错在没有考虑到 当前所处状态的情况)
FSM中周期是4,可以用reg[1:0]表示,根据波形创建电路5 (多路选择器q的值根据c的值变化而变化)
创建电路5 若信号值和时钟上升沿下降沿无关,则不考虑上升沿或下降沿。但本题 p一定是根据a和另一个控制信号变化的所以只能是clk
输出不一定只和输入变化,也有可能和输出有关 例如out = a&b | out;
写tb时,initial 里 不能嵌套always!!