ALU模块设计

发布时间 2023-06-23 13:20:20作者: 可达达鸭
  • 该文章主要记录ALU-DMA系统设计中ALU的设计点。

1. ALU_TOP架构

  • 主要包含四个模块
    • ALU_RF:主要由一个深度为16,宽度为32bits的双端口RAM组成。主要用于存放ALU中操作数。
    • ALU_EXEC:主要根据输入的inst进行运算,执行乘法、加法,减法,与,或,异或,异或非等运算。
    • ALU_FIFO:主要用于缓存ALU_SLAVE输出的指令以及ALU_EXEC输出的运算结果。
    • ALU_SLAVE:根据ALU_TOP的输入s_din和s_addr以及一些控制信号s_sel等,产生其它模块的控制信号,以及ALU_TOP层的数据输出和中断。

2. ALU_SLAVE

  • s_addr设置不同的值,s_din输入值所代表含义如下。
s_addr value s_din
[7:0] 8'h00 OPERATION_START
[7:0] 8'h01 INTERRUPT
[7:0] 8'h02 INTERRUPT_ENABLE
[7:0] 8'h03 INSTRUCTION
[7:0] 8'h04 ALU_READ
[7:0] 8'h05 ALU_STATUS
[7:0] 8'h1x ALU_RF
  • 根据s_addr和s_sel,s_wr信号进行RF模块的读写、FIFO中reslut的读操作和inst的写操作。
  • 根据s_addr和s_in的值产生一些状态控制信号如:op_start,opdone_clear,以用于ALU_EXEC中;还有中断信号s_interrupt。

3. ALU_RF

  • 该模块有3个读地址和1个写地址;
    • 写相关信号由ALU_SLAVE提供,主要根据s_addr判断为读写寄存器模式,根据s_wr判断是写/读。对于写,SLAVE提供写地址、写数据以及写使能信号。
    • RF内部根据waddr进行译码&写使能信号,选通寄存器,进行写入。
    • 读地址根据来源分为两种,一种为ALU_SLAVE发出的,读出数据直接输出ALU_TOP;另一种为ALU_EXEC发出的,读出数据作为ALU运算的操作数。
    • 前者,根据SLAVE的读写使能信号we以及raddr进行读取数据。至于为什么要设计直接读出寄存器值的功能,我认为是为了在系统工作异常时可以检测RF模块是否出错。
    • 后者,根据EXEC模块给的rd_en和rADDR1、rADDR2来取数据。

4. ALU_EXEC

  • 首先介绍3中的rd_en信号和rADDR1、rADDR2。
    • rd_en是由EXEC中next_state判断的,当下一状态是MUL/EXEC时,需要准备好运算的操作数,所以将RF的读使能信号拉高。
    • 两个操作数的地址是根据inst进行译码得到的,不仅可以得到操作数地址,还可以得到位移量shamt,以及执行那个运算的操作码opcode。主要映射关系如下代码所示:
    assign opcode = inst[13:10];
    assign opAaddr = inst[9:6];
    assign opBaddr = inst[5:2];
    assign shamt = inst[1:0];
    
  • ALU_EXEC中主要进行读取指令,运算,输出结果等操作,通过状态机来实现。
    • 首先判断FIFO的rd_err_result、wr_err_inst信号是否拉高,两者分别为SLAVE读FIFO的result和向FIFO中写inst信号,如果SLAVE与FIFO的交互发生问题,那么需要将状态拉到FAULT。
    • IDLE:起始状态,什么都不做;从该状态跳出进入INST_POP1需要使用ALU_SLAVE提供的op_start信号。
    • INST_POP1:该状态主要做的任务是处理从FIFO中读出指令。
      • (1)首先如果FIFO反馈的rd_err_inst信号拉高,即读指令发生问题,那么状态会跳到FAULT中,不继续操作。
      • (2)如果FIFO反馈的rd_ack_inst信号拉高,即从FIFO中读出新的指令,那么根据opcode分为三种情况:NOP:没有操作、MUL:乘法操作、EXEC:基础运算操作。
      • (3)如果rd_ack_inst信号如果为0,说明没有读到,便仍在INST_POP1阶段。
    • NOP: 没有操作停留一周期,直接跳到INST_POP2状态,再次读取指令。
    • EXEC:在此前一阶段,已经拿到了操作数A,B;在这阶段花一个周期计算,并跳转到RESULT_PUSH1状态。
    • MUL:直到mul_done信号被拉高,表示乘法运算结束后,跳到RESULT_PUSH1状态。否则没计算完成,保持MUL状态。
    • RESULT_PUSH1:判断向FIFO中写result是否出错,如果wr_err_result拉高,状态跳到FAULT。如果没有出错,则会跳到RESULT_PUSH2阶段。
    • RESULT_PUSH2:下一状态为INST_POP2.需要连续两个RESULT_PUSH阶段,我认为是因为EXEC和MUL阶段输出结果为64bit,需要分两个周期(每周期能传输32bit数据,所以划分了两个连续状态)传输一个完整的数据。
    • INST_POP2:
      • 这一步再次判断rd_err_inst是否被拉高,如果被拉高,说明FIFO中所有指令都被读取完毕,那么执行结束,进入EXEC_DONE状态。区分于INST_POP1中的(1),那次的rd_err_inst判断是最开始读指令时,防止FIFO设计错误,或ALU_SLAVE写入inst错误,导致FIFO中为空;若最开始检测没有问题,那么rd_err_inst的再次拉高就证明时FIFO中的指令都被读完,而不是设计错误.
      • 如果rd_err_inst未被拉高,则再次进入INST_POP1的步骤(2)(3)。
    • EXEC_DONE:如果opdone_clear信号被拉高,则状态机回到最开始的状态IDLE。如果未被拉高,则一直保持执行完毕的状态。
    • FAULT:错误阶段,停留在该阶段,还没有写跳出FAULT的处理,会导致死循环。
  • ALU_calculation
    • 首先需要对输入的两个操作数opA和opB做符号扩展,得到opA_s和opB_s,由32bits扩展为64bits。之后用opA_s和opB_s做EXEC中的操作。并根据op_code将计算后的值赋值给64bits的temp变量。
    • 对result的赋值,首先在RESULT_PUSH1阶段将temp的高32bit符号位给出,这个阶段会首先判断FIFO是否已经满了,如果满了还要往里面写,就会跳转到FAIL状态。如果FIFO没满,还可以继续写.高32bit和低32bit分两个周期给出。
  • ALU_multiplier
    • 具体的运算实现,后续文章介绍。

5. ALU_FIFO

  • 模块包括指令INST_FIFO和运算结果RESULT_FIFO。
  • INST_FIFO
    • 对于inst的读取,需要拿到ALU_EXEC的读使能信号rd_en_inst.
      • 在ALU_EXEC模块中,只有state为INST_POP1或INST_POP2时,rd_en_inst = 1.
    • 对于inst的写入,需要拿到ALU_SLAVE输出的写使能信号wr_en_inst.主要由s_addr决定是否为指令写模式。
  • RESULT_FIFO
    • 输入来自于ALU_EXEC的计算结果result输出;FIFO输出作为ALU_SLAVE的输入,内部mux选通输出到s_dout.
    • 对于FIFO中result的读取,需要拿到ALU_SLAVE的读使能信号rd_en_result.在s_addr[7:0]= 4时,拉高读使能信号。
    • 对于FIFO中result的写入,需要拿到ALU_EXEC的写使能信号wr_en_result,在state为RESULT_PUSH1和RESULT_PUSH2时,拉高写使能信号。
  • FIFO具体设计以及对满,空的处理后续文章介绍。