计组P1课下作业总结

P1.Q1 Verilog实现splitter

  • 题面:
    使用 Verilog 搭建一个32位 Splitter , 给定一个32位的二进制数作为输入,将其划分为四个8位的二进制数作为输出。
  • 端口定义:
  • 思路:
    就是每次使输入A向右逻辑移位8位,然后再取移位完以后的低8位即为对应输出。
    取低8位的方法我采用的是和8’b11111111进行按位与运算,输出的即为低8位。
    1
    2
    3
    4
    assign O4 = A & (8'b11111111);
    assign O3 = (A >> 8) & (8'b11111111);
    assign O2 = (A >> 16) & (8'b11111111);
    assign O1 = A >> 24;

P1.Q2 Verilog实现ALU

  • 题面:
    使用 Verilog 搭建一个 32 位六运算 ALU 并提交。
  • 端口定义:
  • 模块功能定义:
  • 思路:对ALUOp输入进行判断,然后进行相应的计算,最后输出即可。
  • 主要问题:
    首先就是判断输出,由于本题是组合逻辑,所以如果不用reg和always的组合,就不能使用if,case,while等等(只能在顺序块中使用)而应该使用三目运算符。
    其次就是进行算数位移,不能直接把 $signed(A) >>> B写在三目运算中,因为此之前还会有没有被声明为有符号数的A,导致此声明无效,最终还是逻辑位移。所以需要先对其计算,然后输出时直接输出计算值。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    wire [31:0] shift;
    assign shift = $signed(A) >>> B;
    assign C = (ALUOp == add) ? A + B :
    (ALUOp == sub) ? A - B :
    (ALUOp == and_gate) ? A & B :
    (ALUOp == or_gate) ? A | B :
    (ALUOp == logic_right) ? A >> B :
    (ALUOp == signed_right) ? shift :
    0;
    最后就是在tb中进行测试时,如果要测试算数右移,记得一定要使用三十二位符号数带入,如果你输入的符号数小于三十二位,那么高位补0后相当于将其又强制转化为了正数,此时依然是逻辑右移效果。

P1.Q3 Verilog实现EXT

  • 题面:
    EXT为扩展单元,其主要功能是完成将输入到其中的16位数据进行符号扩展、零扩展以及将输入的16位数加载到高位等操作。
  • 端口定义:
  • 功能定义:
  • 思路:
    利用位拼接运算符{}即可,根据题意拼接0或者最高位即可。
1
2
3
4
assign ext = (EOp == signed_extend) ? {{16{imm[15]}},imm} :
(EOp == logic_extend) ? {{16{1'b0}},imm} :
(EOp == high_load) ? {imm,{16{1'b0}}} :
{{14{imm[15]}},imm,{2{1'b0}}};

需要注意的点:不能写16{0}这样的式子,因为在你不指定0的限制时,默认是16位10进制数,此时拼接时相当于拼接了256位,明显错误。而应该写16{1‘b0}表示一位二进制0。

P1.Q4 Verilog时序逻辑

  • 题面:
    利用Verilog进行格雷码计数器的组成。
  • 端口定义:
  • 实现功能:
    1、 在任意一个时钟上升沿到来的时候,如果复位信号有效,则将计数器清零;
    2、 每个时钟上升沿到来的时候,如果使能信号有效,计数器的值+1;
    3、 在满足1时,即使2的条件满足,也不必执行2;
    4、 计数器初值为0;
    5、 当计数器的值在+1后出现溢出的情况时,将会回到零,同时从发生溢出的这个时钟上升沿开始,溢出标志位将会持续输出1,直到计数器被清零为止(其余情况下溢出标志位必须为0)。
  • 示范波形:
  • 格雷码计数方式:
  • 思路:
    其实就是利用一个寄存器进行计数器状态寄存(当前加到几),然后在输出时利用三目运算符分别将当前寄存器中的状态值(就是对应数)变成对应的格雷码即可。
    本题是同步复位,故Reset输入不需要写入敏感表,只需要注意先判断Reset是否为1即可。同时要注意此处溢出输出也需要利用单独的寄存器进行存储,才能在always块中改变(always 中不能有assign)。
    状态改变代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    always @(posedge Clk) begin
    if(Reset == 1) begin
    state <= 0;
    overflow_memory <= 0;
    end
    else if(En == 1) begin
    if(state + 1 < 8)
    state <= state + 1;
    else begin
    state <= 0;
    overflow_memory <= 1;
    end
    end
    else
    state <= state;
    end
    输出代码:(之前已经定义常量)
    1
    2
    3
    4
    5
    assign Output = (state == 0) ? grey_zero :
    (state == 1) ? grey_one :
    (state == 2) ? grey_two :
    (state == 3) ? grey_three :
    //……

P1.Q5 Verilog表达式状态机

  • 题面:
    现在有这样一类表达式F的字符串需要你来验证它们的合法性:
    1、表达式F中只含有数字0-9,加号+,乘号* 。
    2、表达式F可以按如下的规则产生:
    a.单个数字[0-9]是F; b. 如果X是F,Y是F,X+Y也是F; c. 如果X是F,Y是F,X * Y也是F。
  • 非法情况:

  • 端口定义:

  • 功能定义:
    每个时钟上升沿,状态机从 in 中读入一个ASCII编码的字符。假设读入的第i个字符为ci,则第n个时钟上升沿时,可以拼出一个字符串:
    s=c1c2….cn
    我们需要你此时判断 s 是否符合表达式F的定义。假如s符合F的定义,那么 out 应输出1,否则输出0。
    另外,每个 clr 上升沿时,请清零状态;如果 clk 的上升沿时 clr 为 1,也需要清零状态。清零后,上面定义的字符串s也应从空串开始计算。如果s当前是空串,out也应输出0。

  • 思路:
    本题类似于离散数学中的原子公式,首先画出状态转移图。

    然后根据此定义出四个状态(提前用常量规定),然后进行状态转移即可。本题的状态机比较奇特,因为是输入单个表达式而非考虑末尾,所以一旦进入错误状态(以上表格中4种)就无法再成功成为表达式,因此本题中state4进入就无法转移,不需要添加case4。
    注意本题是异步复位,所以要把clr信号加入敏感表(注意写法),最后输出即可。

    状态转移模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
always @(posedge clk or posedge clr) begin
if(clr == 1)
state = 0;
else begin
case (state)
state1:
state <= (in >= "0" && in <= "9") ? state2 : state4;
state2:
state <= (in >= "0" && in <= "9") ? state4 : state3;
state3:
state <= (in >= "0" && in <= "9") ? state2 : state4;
endcase
end
end

输出模块:

1
assign out = (state == state2) ? 1 : 0;

P1.附加题 语言块检查模拟

  • 题面:
    现在需要你用Verilog语言编写一个模拟语句块检查的工具。
    为了简化要求,输入由ASCII字母和空格组成。一个或多个连续出现的字母构成一个单词,单词不区分大小写,单词之间由一个或多个空格分隔开。检查工具检查自复位之后的输入中,begin和end是否能够匹配。
    匹配规则类似括号匹配:一个begin只能匹配一个end,但是一个匹配的begin必须出现在对应的end之前;允许出现嵌套;最后若出现不能按上述规则匹配的begin或end,则匹配失败。
    输入的读取在时钟上升沿进行。

  • 例子:
    匹配示例:Hello world,begin comPuTer orGANization End。
    不匹配示例:eND,beGin study。

  • 端口定义:

  • 其余要求:

  1. 必须严格按照模块的端口定义
  2. 模块内不要包含任何$display语句 ,以防造成误判
  3. 我们保证在使用模块前进行复位
  4. 保证输入的单词数和单词长度均小于2^32
  • 示例波形:

  • 思路:本题的主体思路就是,首先建立两个有限状态机(吃了P0的亏,还是感觉对于不同状态利用不同状态机进行检测更加靠谱,否则可能出现状态重叠等问题很难处理),然后建立一个类似栈的寄存器(我此处用的是reg_state寄存器),每当检测到一个begin时其值加一,表示进入一个begin,检测到一个end时值减一,表示进入一个end。

本题的状态转移比较简单,每进入一个符号就是一个新状态即可。同时要区分输入一个破坏单词的字符(比如begi后输入非a)并不是回到空状态,而是进入一个错误状态,因为此时本单词输入还没有结束,并且永远不可能等于begin了。

状态转移描写的重点在最后几个状态,我把begin检测状态机的代码放出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
state_begi:begin
if(in == "n" || in == "N") begin
reg_state <= reg_state + 1;
begin_state <= state_begin;
end
else if(in == " ")
begin_state <= spare;
else
begin_state <= state_wrong_begin;
end
state_begin: begin
if(in == " ")
begin_state <= spare;
else begin
begin_state <= state_wrong_begin;
reg_state <= reg_state - 1;
end
end

这是在检测到“begi”和“begin”时的情况,有以下几点需要注意:
首先,观察波形可以发现,当时钟上升沿输入d时,状态机立刻读取了begin,所以是否读取begin要在“begi且输入n”,也即上面的第一个case中就要判断。

其次,关注到给出波形在endc输入时的波动可以发现,当输入是end时,即使下一个输入不是空格,但在这之间(d输入到c输入间)依然将end看作已经输入,此时改变reg_state值,直到判断下一个输入不是空格,就再把这个输入删除。因此判断输入要在第一个case(reg_state加)而判断是否真的输入了要在第二个case中(如果输入不是空格,证明没有真正输入,reg_state再减回去)

最后,我这里reg_state的基准是1,也即begin和end匹配时其值为1(初始化也为1),因为如果初始化为0时,相当于可能检测到-1,而此时可能就需要符号转换$signed(),就比较容易出错,所以本题我设定基准是1,这样最少也是0就不会出错(为什么没有负数请看接下来的分析)

最后就是end检测和输出的时候的一点设计:
end:

1
2
3
4
5
6
7
8
9
10
11
12
state_end: begin
if(in == " ") begin
end_state <= spare;
flag_end <= 0;
if(reg_state == 0)
unable <= 1;
end
else begin
end_state <= state_wrong_end;
reg_state <= reg_state + 1;
end
end

输出:
assign result = (unable == 0 && reg_state == 1) ? 1 : 0;

这其实是为了让reg_state不会为负数的一点设计,但是也挺符合实际的,也就是,当之前字符串已经匹配(即reg_state==1)时出现了end在begin之前的情况,那么我们可以断言在reset之前这个字符串已经不可能符合条件了,因为无法用后置begin抵消前置end,因此只要发现reg_state为0时,就可以让一个寄存器(此处我命名为unable)为1,这时输出一定为0。
综上,当unable为0,而且reg_state为1时是全匹配状态,此时输出result为1。

这就是P1全部作业了……P0第二次上机真得加油,希望自己真的能顺利过关吧!