一、除法器
基于减法和移位的除法器的算法:
对于32的无符号除法,被除数a除以除数b,他们的商和余数一定不会超过32位。首先将a转换成高32位为0,低32位为a的temp_a。把b转换成高32位为b,低32位为0的temp_b。在每个周期开始时,先将temp_a左移一位,末尾补0,然后与b比较,是否大于b,是则temp_a减去temp_b将且加上1,否则继续往下执行。上面的移位、比较和减法(视具体情况而定)要执行32次,执行结束后temp_a的高32位即为余数,低32位即为商。
verilog代码:
module divide(
input [31:0] a,
input [31:0] b,
output [31:0] quotient ,
output [31:0] remainder, )
reg [31:0] tempa;
reg [31:0] tempb;
reg [63:0] temp_a;
reg [63:0] temp_b;
integer i;
always@(a or b) begin
tmepa <= a;
tempb <= b;
end
always@(tempa or tempb) begin
temp_a <= {32'h00000000,tempa};
temp_b <= {tempb,32'h00000000};
for (i=0;i<32;i=i+1) begin
temp_a <= {temp_a[62:0],1'b0};
if(temp_a[63:32]>=tempb)
temp_a <= temp_a - temp_b + 1'b1;
else
temp_a <= temp_a;
end
quotient <= temp_a[31:0];
remainder<= temp_b[63:0];
end
改进:1.将组合逻辑改为时序逻辑
2.计算数据改为动态位宽,加入使能,异步复位等控制逻辑
parameter state_init = 4'b0000;
parameter state_idle = 4'b0001;
parameter state_cal1 = 4'b0010;
parameter state_cal2 = 4'b0011;
parameter state_done = 4'b0100;
module divider(clk, rst_n, A,B,C,D,ov);
input clk;
input rst_n;
input [15:0]A;
input [15:0]B;
output [15:0]C;
output [15:0]D;
output ov;
/*寄存器需要初始化*/
reg output_valid = 0;
reg [31:0] temp_a = 32'b0 ,temp_b = 32'b0;
reg [15:0] bufa = 16'b0,bufb = 16'b0;
reg [15:0] TMPA = 16'b0,TMPB = 16'b0;
reg [4:0] state = state_idle;
reg [3:0] cnt = 4'b0000;
assign ov = output_valid;
assign C = temp_a[15:0] & {16{output_valid}};
assign D = temp_a[31:16] & {16{output_valid}};
always@(A or B)
begin
bufa <= A;
bufb <= B;
end
always@(posedge clk)
begin
if(rst_n)
begin
case(state)
state_idle:
begin
if(bufa===TMPA&&bufb===TMPB)
begin
state <= state_idle;
end
else
begin
TMPA <= bufa;
TMPB <= bufb;
state <= state_init;
output_valid <= 0;
end
end
state_init:
begin
temp_a[15:0] <= bufa;
temp_b[31:16] <= bufb;
state <= state_cal1;
cnt <= 4'b0;
end
state_cal1:
begin
temp_a <= temp_a<<1;
state <= state_cal2;
end
state_cal2:
begin
if(temp_a[31:16]>=temp_b[31:16])
begin
temp_a <= temp_a - temp_b + 1;
end
if(cnt === 4'b1111)
begin
state <= state_done;
end
else
begin
cnt <= cnt + 1;
state <= state_cal1;
end
end
state_done:
begin
output_valid <= 1;
end
default: state <= state_idle;
endcase
end
else
output_valid <= 0;
end
endmodule
2.乘法器
首先来回顾一下乘法是如何在计算机中实现的。
假设现在有两个32位带符号定点整数x和y,我们现在要让x和y相乘,然后把乘积存放在z中,大家知道,两个32位数相乘,结果不会超过64位,因此z的长度应该为64位。
z = x * y中,x是被乘数,在Verilog代码中 multiplicand表示,y是乘数,在代码中用multiplier表示。因为x和y都是带符号数,所以应该是用补码乘法,但是如果对x和y求绝对值,让两个绝对值相乘,然后再判断正负,效果和补码乘法是相同。后面给出的Verilog代码就是基于这种思路编写的。两个32位整数相乘,实际上是进行了32次加法操作。下面以两个4位二进制数相乘来说明乘法实现的过程。
从上图中可以看到,被乘数x为1000,乘数y为1001,上面的乘法过程是手工运算的一个步骤,而计算机在做乘法时就是模拟上述手工运算的执行过程。因为是两个4位数相乘,所以结果应该是四个数加和得到的。先判断y的最低位是0还是1,如果是1,则需要把x加到部分积上,若为0,则需要把0加到部分积上(实际上加0的这个过程计算机并不执行,因为加0对部分积没有任何影响),x左移一位,之后再让y右移一位,若y为0,则循环结束,否则继续此循环过程。流程图如下。
流程图中,x因为需要左移,所以32位长度的x应该用一个64位寄存器来存储,这样才能保证x左移后不会发生高位丧失。
代码实现与分析
multiply.v文件如下:
`timescale 1ns / 1ps
//*************************************************************************
// > 文件名: multiply.v
// > 描述 :乘法器模块,低效率的迭代乘法算法,使用两个乘数绝对值参与运算
// > 作者 : LOONGSON
// > 日期 : 2016-04-14
//*************************************************************************
module multiply( // 乘法器
input clk, // 时钟
input mult_begin, // 乘法开始信号
input [31:0] mult_op1, // 乘法源操作数1
input [31:0] mult_op2, // 乘法源操作数2
output [63:0] product, // 乘积
output mult_end // 乘法结束信号
);
//乘法正在运算信号和结束信号
reg mult_valid;
assign mult_end = mult_valid & ~(|multiplier); //乘法结束信号:乘数全0
always @(posedge clk) //①
begin
if (!mult_begin || mult_end) //如果没有开始或者已经结束了
begin
mult_valid <= 1'b0; //mult_valid 赋值成0,说明现在没有进行有效的乘法运算
end
else
begin
mult_valid <= 1'b1;
// test <= 1'b1;
end
end
//两个源操作取绝对值,正数的绝对值为其本身,负数的绝对值为取反加1
wire op1_sign; //操作数1的符号位
wire op2_sign; //操作数2的符号位
wire [31:0] op1_absolute; //操作数1的绝对值
wire [31:0] op2_absolute; //操作数2的绝对值
assign op1_sign = mult_op1[31];
assign op2_sign = mult_op2[31];
assign op1_absolute = op1_sign ? (~mult_op1+1) : mult_op1;
assign op2_absolute = op2_sign ? (~mult_op2+1) : mult_op2;
//加载被乘数,运算时每次左移一位
reg [63:0] multiplicand;
always @ (posedge clk) //②
begin
if (mult_valid)
begin // 如果正在进行乘法,则被乘数每时钟左移一位
multiplicand <= {multiplicand[62:0],1'b0}; //被乘数x每次左移一位。
end
else if (mult_begin)
begin // 乘法开始,加载被乘数,为乘数1的绝对值
multiplicand <= {32'd0,op1_absolute};
end
end
//加载乘数,运算时每次右移一位,相当于y
reg [31:0] multiplier;
always @ (posedge clk) //③
begin
if(mult_valid)
begin //如果正在进行乘法,则乘数每时钟右移一位
multiplier <= {1'b0,multiplier[31:1]}; //相当于乘数y右移一位
end
else if(mult_begin)
begin //乘法开始,加载乘数,为乘数2的绝对值
multiplier <= op2_absolute;
end
end
// 部分积:乘数末位为1,由被乘数左移得到;乘数末位为0,部分积为0
wire [63:0] partial_product;
assign partial_product = multiplier[0] ? multiplicand:64'd0; //若此时y的最低位为1,则把x赋值给部分积partial_product,否则把0赋值给partial_product
//累加器
reg [63:0] product_temp; //临时结果
always @ (posedge clk) //④//clk信号从0变为1时,激发此段语句的执行,但语句的执行需要时间
begin
if (mult_valid)
begin
product_temp <= product_temp + partial_product;
end
else if (mult_begin)
begin
product_temp <= 64'd0;
end
end
//乘法结果的符号位和乘法结果
reg product_sign; //乘积结果的符号
always @ (posedge clk) // 乘积⑤
begin
if (mult_valid)
begin
product_sign <= op1_sign ^ op2_sign;
end
end
//若乘法结果为负数,则需要对结果取反+1
assign product = product_sign ? (~product_temp+1) : product_temp;
endmodule
负整数,32位,全部按位取反,末尾加一,不需要将符号位单独提取出来
3.状态机
“硬件设计很讲究并行设计思想,虽然用Verilog描述的电路大都是并行实现的,但是对于实际的工程应用,往往需要让硬件来实现一些具有一定顺序的工作,这就要用到状态机思想。什么是状态机呢?简单的说,就是通过不同的状态迁移来完成一些特定的顺序逻辑。硬件的并行性决定了用Verilog描述的硬件实现(臂如不同的always语句)都是并行执行的,那么如果希望分多个时间完成一个任务,怎么办?也许可以用多个使能信号来衔接多个不同的模块,但是这样做多少显得繁琐。状态机的提出会大大简化这一工作。”
——特权同学《深入浅出玩转FPGA》
一、状态机分类:
1.Moore型:状态机的状态变化仅和当前状态有关(特权同学《深入浅出玩转FPGA》);时序逻辑电路的输出只取决于当前状态(夏宇闻《Verilog数字系统设计》)。设计高速电路时常用此类状态机,把状态变化直接用作输出。
2.Mealy型:状态机的状态变化不仅与当前的状态有关,还取决于当前的输入条件(特权同学《深入浅出玩转FPGA》);时序逻辑的输出不但取决于状态还取决于输入(夏宇闻《Verilog数字系统设计》)。平常使用较多的是此类状态机。
“其实这几种状态机之间,只要做一些改变,便可以从一种形式转变为另一种形式。把状态机精确的分为这类或那类,其实并不重要,重要的是设计者如何把握输出的结构能满足设计的整体目标,包括定时的准确性和灵活性。”
——夏宇闻《Verilog数字系统设计》
二、状态机编码:
状态机的参数定义采用的都是独热码,和格雷码相比,虽然独热码多用了触发器,但所用组合电路可以省一些,因而使电路的速度和可靠性有显著提高,而总的单元数并无显著增加。采用独热编码后有了多余的状态,就有一些不可达到的状态。为此在case语句的最后需要增加default分支向。这可以用默认项表示该项,也可以用确定项表示,以确保回到初始状态。一般综合器都可以通过综合指令的控制来合理地处理默认项。
三、状态机编写
状态机分好多写法,可以有一段式、二段式和三段式,在目前,我一般会使用三段式,即将状态定义,状态转移,状态执行三部分分开,好看一点,当然这主要针对的是Mealy型。在写之前,画出状态转移图最好。
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2022/02/10 09:55:53
// Design Name: panweihao
// Module Name: rffe_apb_master
// Project Name:
// Target Devices: apb_read_write_mipi
// Tool Versions: 0.1
// Description:
//
// Dependencies: apb读mipi的模块,并支持回读,spi指令16+8,24位的最高位为1时是spi回读
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module rffe_apb_master(
input wire i_clk ,
input wire i_rst_n ,
input wire i_irq ,
output wire [8:0] o_apb_addr ,
output wire [31:0] o_apb_wdata ,
input wire [31:0] i_apb_rdata ,
output wire o_apb_sel ,
output wire o_apb_enable ,
output wire o_apb_write ,
input wire i_apb_ready ,
input wire i_apb_err ,
input wire spi_wr_valid ,
input wire [7:0] spi_wr_data ,
output wire spi_rd_valid ,
output wire [31:0] spi_rd_data
);
localparam RESET = 4'b0000 ;
localparam IDLE = 4'b0001 ;
localparam READY = 4'b0010 ;
localparam READ = 4'b0100 ;
localparam WRITE = 4'b1000 ;
wire[3:0] cur_state ;
reg [3:0] next_state ;
reg [8:0] o_apb_mst_addr ;
reg [31:0] o_apb_mst_wdata ;
reg [31:0] i_apb_mst_rdata ;
reg o_apb_mst_sel ;
reg o_apb_mst_enable ;
reg o_apb_mst_write ;
reg i_apb_slv_ready ;
reg i_apb_slv_err ;
reg rd_valid ;
reg [31:0] rd_data ;
reg wr_valid ;
reg [7:0] wr_data ;
reg [15:0] i_rst_n_p ;
wire i_rst_n_end ;
reg [1:0] i_irq_p ;
wire i_irq_begin ;
reg rst_done ;
reg [2:0] read_back ;
assign o_apb_addr = o_apb_mst_addr ;
assign o_apb_wdata = o_apb_mst_wdata ;
assign o_apb_sel = o_apb_mst_sel ;
assign o_apb_enable = o_apb_mst_enable ;
assign o_apb_write = o_apb_mst_write ;
assign spi_rd_valid = rd_valid ;
assign spi_rd_data = rd_data ;
/// output latch ///
always@ (posedge i_clk) begin
i_apb_mst_rdata <= i_apb_rdata ;
wr_data <= spi_wr_data ;
wr_valid <= spi_wr_valid ;
i_apb_slv_ready <= i_apb_ready ;
i_apb_slv_err <= i_apb_err ;
end
/ begin signal/
assign i_rst_n_end = (!i_rst_n_p[15])&(&i_rst_n_p[14:0]) ;
always@(posedge i_clk or negedge i_rst_n) begin
if(~i_rst_n) begin
i_rst_n_p <= 16'd0;
end else begin
i_rst_n_p[15:0] <= { i_rst_n_p[14:0],i_rst_n } ;
end
end
assign i_irq_begin = i_irq_p[0]&(~i_irq_p[1]) ;
always@(posedge i_clk or negedge i_rst_n) begin
if(~i_rst_n) begin
i_irq_p <= 2'd0;
end else begin
i_irq_p[0] <= i_irq ;
i_irq_p[1]<= i_irq_p[0] ;
end
end
//
//always@(posedge i_clk or negedge i_rst_n) begin
// if(~i_rst_n) begin
// cur_state <= RESET;
// end else begin
// cur_state <= next_state;
// end
//end
assign cur_state = next_state ;
状态转换
always@(posedge i_clk or negedge i_rst_n) begin
if(~i_rst_n) begin
next_state <= RESET ;
end else case(cur_state)
RESET:begin if(rst_done)
next_state <= IDLE ;
else next_state <= RESET ;
end
IDLE:begin
if(i_irq_begin)begin
next_state <= READY ;
end else begin
next_state <= IDLE ;
end
end
READY:begin //获取008的状态,是00000040就正常,否则报错
if(i_apb_ready)begin
next_state <= READ ;
end else if(i_apb_slv_err)begin
next_state <= IDLE ;
end else
next_state <= READY ;
end
READ:begin ///读100寄存器的值
if(i_apb_ready)begin
next_state <= WRITE ;
end else if(i_apb_slv_err)begin
next_state <= IDLE ;
end else begin
next_state <= READ ;
end
end
WRITE:begin
if(i_apb_ready & o_apb_mst_addr[3])begin
next_state <= IDLE ;
end else if(i_apb_slv_err)begin
next_state <= IDLE ;
end else begin
next_state <= WRITE ;
end
end
default:begin
next_state <= IDLE ;
end
endcase
end
状态执行/
always@(posedge i_clk or negedge i_rst_n) begin
if(~i_rst_n) begin
o_apb_mst_addr <= 9'd0 ;
o_apb_mst_wdata <= 32'd0;
o_apb_mst_sel <= 1'b0 ;
o_apb_mst_enable <= 1'b0 ;
o_apb_mst_write <= 1'b0 ;
rd_valid <= 1'b0 ;
rd_data <= 32'd0;
rst_done <= 1'b0 ;
read_back <= 3'b000 ;
end else case(cur_state)
RESET:begin
if(i_rst_n_end)begin
o_apb_mst_addr <= 9'h000 ;
o_apb_mst_wdata <= 32'h21e04002;
o_apb_mst_sel <= 1'b1 ;
o_apb_mst_enable <= 1'b1 ;
o_apb_mst_write <= 1'b1 ;
end else if(i_apb_ready & (!o_apb_mst_addr[2]) )begin
o_apb_mst_addr <= 9'h004 ;
o_apb_mst_wdata <= 32'h0000ffff;
o_apb_mst_sel <= 1'b1 ;
o_apb_mst_enable <= 1'b1 ;
o_apb_mst_write <= 1'b1 ;
end else if(i_apb_ready & o_apb_mst_addr[2] )begin
o_apb_mst_addr <= 9'h000 ;
o_apb_mst_wdata <= 32'h00000000;
o_apb_mst_sel <= 1'b0 ;
o_apb_mst_enable <= 1'b0 ;
o_apb_mst_write <= 1'b0 ;
rst_done <= 1'b1 ;
end
end
IDLE:begin
o_apb_mst_addr <= 9'h000 ;
o_apb_mst_wdata <= 32'h00000000;
o_apb_mst_sel <= 1'd0 ;
o_apb_mst_enable <= 1'd0 ;
o_apb_mst_write <= 1'd0 ;
rd_valid <= 1'd0 ;
rd_data <= 32'h00000000;
rst_done <= 1'b0 ;
read_back <= 3'b000 ;
end
READY:begin
if(i_apb_ready)begin
o_apb_mst_addr <= 9'h000 ;
o_apb_mst_wdata <= 32'h00000000;
o_apb_mst_sel <= 1'd0 ;
o_apb_mst_enable <= 1'd0 ;
o_apb_mst_write <= 1'd0 ;
end else begin
o_apb_mst_addr <= 9'h008 ;
o_apb_mst_wdata <= 32'h00000000;
o_apb_mst_sel <= 1'd1 ;
o_apb_mst_enable <= 1'd1 ;
o_apb_mst_write <= 1'd0 ;
rd_valid <= 1'd0 ;
rd_data <= 32'h00000000;
end
end
READ:begin
if(i_apb_ready)begin
o_apb_mst_addr <= 9'h000 ;
o_apb_mst_wdata <= 32'h00000000;
o_apb_mst_sel <= 1'd0 ;
o_apb_mst_enable <= 1'd0 ;
o_apb_mst_write <= 1'd0 ;
rd_valid <= i_apb_ready & i_apb_rdata[24];
rd_data <= i_apb_rdata ;
end else begin
o_apb_mst_addr <= 9'h100 ;
o_apb_mst_wdata <= 32'h00000000;
o_apb_mst_sel <= 1'd1 ;
o_apb_mst_enable <= 1'd1 ;
o_apb_mst_write <= 1'd0 ;
end
end
WRITE:begin
rd_valid <= 1'd0 ;
rd_data <= 32'h00000000;
if((!i_apb_mst_rdata[24]))begin
if(i_apb_ready)begin
o_apb_mst_addr <= 9'h000 ;
o_apb_mst_wdata <= 32'h00000000;
o_apb_mst_sel <= 1'd0 ;
o_apb_mst_enable <= 1'd0 ;
o_apb_mst_write <= 1'd0 ;
end else begin
o_apb_mst_addr <= 9'h008 ;
o_apb_mst_wdata <= 32'h0000ffff;
o_apb_mst_sel <= 1'd1 ;
o_apb_mst_enable <= 1'd1 ;
o_apb_mst_write <= 1'd1 ;
end
end else if(i_apb_mst_rdata[24])begin
if(i_apb_ready)begin
o_apb_mst_addr <= 9'h000 ;
o_apb_mst_wdata <= 32'h00000000;
o_apb_mst_sel <= 1'd0 ;
o_apb_mst_enable <= 1'd0 ;
o_apb_mst_write <= 1'd0 ;
read_back <= 3'b001 ;
end else if(wr_valid) begin
o_apb_mst_addr <= 9'h100 ;
o_apb_mst_wdata <= {24'h0000,wr_data};
o_apb_mst_sel <= 1'd1 ;
o_apb_mst_enable <= 1'd1 ;
o_apb_mst_write <= 1'd1 ;
end else if(read_back == 3'b001)begin
o_apb_mst_addr <= 9'h008 ;
o_apb_mst_wdata <= 32'h0000ffff;
o_apb_mst_sel <= 1'd1 ;
o_apb_mst_enable <= 1'd1 ;
o_apb_mst_write <= 1'd1 ;
end
end
end
default:begin
o_apb_mst_addr <= 9'h000 ;
o_apb_mst_wdata <= 32'h00000000;
o_apb_mst_sel <= 1'd0 ;
o_apb_mst_enable <= 1'd0 ;
o_apb_mst_write <= 1'd0 ;
rd_valid <= 1'd0 ;
rd_data <= 32'h00000000;
end
endcase
end
endmodule
4.移位操作
Verilog中的移位操作有两类:逻辑移位和算术移位。
逻辑右移(>>):1个操作数向右移位,产生的空位用0填充;x>>1,x左移一位
逻辑左移(<<):1个操作数向左移位,产生的空位用0填充;
算术右移(>>>):1个操作数向右移位。如果是无符号数,则产生的空位用0填充;有符号数则用其符号位填充;
算数左移(<<<):1个操作数向左移位,产生的空位用0填充;
算术右移符号位要一起移动,并且在左边补上符号位,也就是如果符号位是1就补1符号位是0就补0。比如:11100算术右移一位为11110(符号位1跟着一起移动并且左边补了1)对于二进制的数值来说右移n位等于原来的数值除以2的n次方。比如10110100十进制是76(需要先将这个补码转换成原码之后再转换成十进制),右移两位后是11101101转成十进制是19恰好是76的4倍。
ps:这种倍数关系只适用于右移后被舍弃的低位不含1的情况,否则每舍一次1则代表余数被舍去,保留整数部分。
还可以用这种方法:
reg [31:0] a,b; ~~~b <={a[30:0],1'b0};或者~~~b<={1'b0,a[31:1]};
5.反压操作
当入口流量大于出口流量,这时候就需要反压,或者,当后级未准备好时,如果本级进行数据传递,那么它就需要反压前级,所以此时前级需要将数据保持不动,直到握手成功才能更新数据。而反压在多级流水线中就变得稍显复杂,原因在于,比如我们采用三级流水设计,如果我们收到后级反压信号,我们理所当然想反压本级输出信号的寄存器,但是如果只反压最后一级寄存器,那么会面临一个问题,就是最后一级寄存器数据会被前两级流水冲毁,导致数据丢失,引出数据安全问题,所以我们此时需要考虑反压设计。
分类:三种,不带存储体的反7.压,带存储体的逐级反压,带存储体的跨级反压
在途,水线waterline
6.异步处理
打拍,握手,FIFO
7.基本运算
多看书,多写代码
8.低功耗理解
能量是功耗在时间上的累计
动态(开关)功耗:是设备运行时或者说信号改变时所消耗的功耗(翻转功耗+短路功耗)
静态(内部)功耗:是设备上电但是信号没有改变时所消耗的功耗;
简化后,Switching power,Pdyn = Ceff(p_trans·Cl)··VDD2·fclock
RTL级低功耗设计可以看一下这个: 数字芯片设计之RTL级低功耗设计 - 知乎 (zhihu.com)
静态:多阈值工艺,电源门控,体偏置
动态:多电压域,预计算,门控时钟(毛刺问题)
9.ECO电路修改方法
ECO:engineering change order 工程改变命令,手动修改集成电路(netlist)
搁置
10.资源与面积评估