草庐IT

Verilog 乘法器

他不是混子QAQ 2023-04-14 原文

 30那个地方改仿真的时长,默认是10us(但实际上好像是1us) 这里改成30us


//加载被乘数,运算时每次左移一位  (这里把被乘数位拓展了)
    reg  [63:0] multiplicand

 //加载乘数,运算时每次右移一位,相当于y
    reg  [31:0] multiplier;

 // 部分积:乘数末位为1,由被乘数左移得到;乘数末位为0,部分积为0
    wire [63:0] partial_product;

//累加器
    reg [63:0] product_temp;    //临时结果   product_temp <= product_temp + partial_product;

这个部分积直接由被乘数得到,然后每一个时钟上跳沿直接加到temp上。

代码中always是并行的。这是理解乘法器的关键!然后每个always里仅对一个信号赋值!


mult_end 为1的时候     表示乘法运算结束 (故看仿真结果的时候得看end为1时候对应的结果)


mult_valid 为1 表示进行有效的运算      

 代码见Verilog编程之乘法器的实现_王森ouc的博客-CSDN博客_verilog乘法

人家总结:reg型变量必须通过过程赋值语句赋值!
不能通过assign语句赋值!
而wire型数据不能放在过程块内赋值。

multiply.v 与 testbench.v分别如下

`timescale 1ns / 1ps
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;
        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
`timescale 1ns / 1ps

module tb;

    // Inputs
    reg clk;
    reg mult_begin;
    reg [31:0] mult_op1;
    reg [31:0] mult_op2;

    // Outputs
    wire [63:0] product;
    wire mult_end;

    // Instantiate the Unit Under Test (UUT)
    multiply uut (
        .clk(clk), 
        .mult_begin(mult_begin), 
        .mult_op1(mult_op1), 
        .mult_op2(mult_op2), 
        .product(product), 
        .mult_end(mult_end)
    );

    initial begin
        // Initialize Inputs
        clk = 0;
        mult_begin = 0;
        mult_op1 = 0;
        mult_op2 = 0;

        // Wait 100 ns for global reset to finish
        #100;
        mult_begin = 1;
        mult_op1 = 32'H00001111;
        mult_op2 = 32'H00001111;
        #400;
        mult_begin = 0;
        #500;
        mult_begin = 1;
        mult_op1 = 32'H00001111;
        mult_op2 = 32'H00002222;
        #400;
        mult_begin = 0;
        #500;
        mult_begin = 1;
        mult_op1 = 32'H00000002;
        mult_op2 = 32'HFFFFFFFF;
        #400;
        mult_begin = 0;
        #500;
        mult_begin = 1;
        mult_op1 = 32'H00000002;
        mult_op2 = 32'H80000000;
        #400;
        mult_begin = 0;
        // Add stimulus here
    end
   always #5 clk = ~clk;
endmodule


尽量不要在一个always块里对多个信号赋值,不然可能造成多驱动的问题(在不同的always里若有对相同的赋值,那出大问题(听谁的呢))

wire只是一个线路,并不能把数存住,存住得放到寄存器里(reg)

multiplier multiplicand 为内部信号 可从仿真的左侧栏里Scope-uut模块里-objects-右键某个信号就可以加进去(新加进去的是空的,得刷新下Relaunch)

仿真技巧: shift +滚轮   在变量可以新建文件夹:把要看的变量扔进去

 赋初值的时候,常用非阻塞赋值。阻塞赋值即串行,非阻塞是执行完了一起赋值。(p58)


`timescale 1ns/1ns

写在所有仿真文件(.v)的代码首行,时间尺度、精度单位定义,时间尺度预编译指令,用来定义模块仿真时的时间单位和时间精度,不可被综合,但在可综合代码中也可以写,只是会在仿真时表达效果,而综合时会自动被综合器优化掉。格式如下:

`timescale   时间尺度/时间精度         或          `timescale timeunit / timeprecision

注意:仿真时间单位和时间精度的数字只能是1、10、100,不能为其它的数字。而且,时间精度不能比时间单位还要大。最多两者一样大。比如:下面定义都是对的:

`timescale  1ns/1ns

`timescale  100ns/100ns

下面的定义是错的

`timescale  1ps/1ns

时间的进位关系如下: s ms us ns ps  毫秒 微秒 纳秒 皮秒 10的三次方

时间精度就是模块仿真时间和延时的精确程序,比如:定义时间精度为10ns,那么时序中所有的延时至多能精确到10ns,而8ns或者18ns是不可能做到的。
 


上板验证:

约束文件 与 display文件  变量名字对应上哦,可根据之前加法器的display和约束文件对着看。

set_property PACKAGE_PIN AC19 [get_ports clk]
set_property PACKAGE_PIN U26  [get_ports led_end]
set_property PACKAGE_PIN AB21 [get_ports resetn]
set_property PACKAGE_PIN AE17 [get_ports input_sel]
set_property PACKAGE_PIN AF17 [get_ports sw_begin]

set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports led_end]
set_property IOSTANDARD LVCMOS33 [get_ports resetn]
set_property IOSTANDARD LVCMOS33 [get_ports input_sel]
set_property IOSTANDARD LVCMOS33 [get_ports sw_begin]

#lcd
set_property PACKAGE_PIN E5  [get_ports lcd_rst]
set_property PACKAGE_PIN G7  [get_ports lcd_cs]
set_property PACKAGE_PIN H7  [get_ports lcd_rs]
set_property PACKAGE_PIN E6  [get_ports lcd_wr]
set_property PACKAGE_PIN D5  [get_ports lcd_rd]
set_property PACKAGE_PIN J5  [get_ports lcd_bl_ctr]
set_property PACKAGE_PIN C4  [get_ports {lcd_data_io[0]}]
set_property PACKAGE_PIN C3  [get_ports {lcd_data_io[1]}]
set_property PACKAGE_PIN D4  [get_ports {lcd_data_io[2]}]
set_property PACKAGE_PIN D3  [get_ports {lcd_data_io[3]}]
set_property PACKAGE_PIN F5  [get_ports {lcd_data_io[4]}]
set_property PACKAGE_PIN G6  [get_ports {lcd_data_io[5]}]
set_property PACKAGE_PIN F4  [get_ports {lcd_data_io[6]}]
set_property PACKAGE_PIN E3  [get_ports {lcd_data_io[7]}]
set_property PACKAGE_PIN G5  [get_ports {lcd_data_io[8]}]
set_property PACKAGE_PIN H6  [get_ports {lcd_data_io[9]}]
set_property PACKAGE_PIN F2  [get_ports {lcd_data_io[10]}]
set_property PACKAGE_PIN F3  [get_ports {lcd_data_io[11]}]
set_property PACKAGE_PIN G4  [get_ports {lcd_data_io[12]}]
set_property PACKAGE_PIN G2  [get_ports {lcd_data_io[13]}]
set_property PACKAGE_PIN H4  [get_ports {lcd_data_io[14]}]
set_property PACKAGE_PIN H3  [get_ports {lcd_data_io[15]}]
set_property PACKAGE_PIN K6  [get_ports ct_int]
set_property PACKAGE_PIN J6  [get_ports ct_sda]
set_property PACKAGE_PIN L8  [get_ports ct_scl]
set_property PACKAGE_PIN K7  [get_ports ct_rstn]

set_property IOSTANDARD LVCMOS33 [get_ports lcd_rst]
set_property IOSTANDARD LVCMOS33 [get_ports lcd_cs]
set_property IOSTANDARD LVCMOS33 [get_ports lcd_rs]
set_property IOSTANDARD LVCMOS33 [get_ports lcd_wr]
set_property IOSTANDARD LVCMOS33 [get_ports lcd_rd]
set_property IOSTANDARD LVCMOS33 [get_ports lcd_bl_ctr]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[8]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[9]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[10]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[11]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[12]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[13]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[14]}]
set_property IOSTANDARD LVCMOS33 [get_ports {lcd_data_io[15]}]
set_property IOSTANDARD LVCMOS33 [get_ports ct_int]
set_property IOSTANDARD LVCMOS33 [get_ports ct_sda]
set_property IOSTANDARD LVCMOS33 [get_ports ct_scl]
set_property IOSTANDARD LVCMOS33 [get_ports ct_rstn]



module multiply_display(
    //时钟与复位信号
    input clk,
    input resetn,    //后缀"n"代表低电平有效

    //拨码开关,用于选择输入数
    input input_sel, //0:输入为乘数1;1:输入为乘数2
    input sw_begin,
    
    //乘法结束信号
    output led_end,

    //触摸屏相关接口,不需要更改
    output lcd_rst,
    output lcd_cs,
    output lcd_rs,
    output lcd_wr,
    output lcd_rd,
    inout[15:0] lcd_data_io,
    output lcd_bl_ctr,
    inout ct_int,
    inout ct_sda,
    output ct_scl,
    output ct_rstn
);
//-----{调用乘法器模块}begin
    wire        mult_begin;
    reg  [31:0] mult_op1; 
    reg  [31:0] mult_op2;  
    wire [63:0] product; 
    wire        mult_end;  
    assign mult_begin = sw_begin;
    assign led_end = mult_end;
    multiply multiply_module (
        .clk       (clk       ),
        .mult_begin(mult_begin),
        .mult_op1  (mult_op1  ), 
        .mult_op2  (mult_op2  ),
        .product   (product   ),
        .mult_end  (mult_end  )
    );
    reg [63:0] product_r;
    always @(posedge clk)
    begin
        if (!resetn)
        begin
            product_r <= 64'd0;
        end
        else if (mult_end)
        begin
            product_r <= product;
        end
    end
//-----{调用乘法器模块}end

//---------------------{调用触摸屏模块}begin--------------------//
//-----{实例化触摸屏}begin
//此小节不需要更改
    reg         display_valid;
    reg  [39:0] display_name;
    reg  [31:0] display_value;
    wire [5 :0] display_number;
    wire        input_valid;
    wire [31:0] input_value;

    lcd_module lcd_module(
        .clk            (clk           ),   //10Mhz
        .resetn         (resetn        ),

        //调用触摸屏的接口
        .display_valid  (display_valid ),
        .display_name   (display_name  ),
        .display_value  (display_value ),
        .display_number (display_number),
        .input_valid    (input_valid   ),
        .input_value    (input_value   ),

        //lcd触摸屏相关接口,不需要更改
        .lcd_rst        (lcd_rst       ),
        .lcd_cs         (lcd_cs        ),
        .lcd_rs         (lcd_rs        ),
        .lcd_wr         (lcd_wr        ),
        .lcd_rd         (lcd_rd        ),
        .lcd_data_io    (lcd_data_io   ),
        .lcd_bl_ctr     (lcd_bl_ctr    ),
        .ct_int         (ct_int        ),
        .ct_sda         (ct_sda        ),
        .ct_scl         (ct_scl        ),
        .ct_rstn        (ct_rstn       )
    ); 
//-----{实例化触摸屏}end

//-----{从触摸屏获取输入}begin
//根据实际需要输入的数修改此小节,
//建议对每一个数的输入,编写单独一个always块
    //当input_sel为0时,表示输入数为乘数1
    always @(posedge clk)
    begin
        if (!resetn)
        begin
            mult_op1 <= 32'd0;
        end
        else if (input_valid && !input_sel)
        begin
            mult_op1 <= input_value;
        end
    end
    
    //当input_sel为1时,表示输入数为乘数2
    always @(posedge clk)
    begin
        if (!resetn)
        begin
            mult_op2 <= 32'd0;
        end
        else if (input_valid && input_sel)
        begin
            mult_op2 <= input_value;
        end
    end
//-----{从触摸屏获取输入}end

//-----{输出到触摸屏显示}begin
//根据需要显示的数修改此小节,
//触摸屏上共有44块显示区域,可显示44组32位数据
//44块显示区域从1开始编号,编号为1~44,
    always @(posedge clk)
    begin
        case(display_number)
            6'd1 :
            begin
                display_valid <= 1'b1;
                display_name  <= "M_OP1";
                display_value <= mult_op1;
            end
            6'd2 :
            begin
                display_valid <= 1'b1;
                display_name  <= "M_OP2";
                display_value <= mult_op2;
            end
            6'd3 :
            begin
                display_valid <= 1'b1;
                display_name  <= "PRO_H";
                display_value <= product_r[63:32];
            end
            6'd4 :
            begin
                display_valid <= 1'b1;
                display_name  <= "PRO_L";
                display_value <= product_r[31: 0];
            end
            default :
            begin
                display_valid <= 1'b0;
                display_name  <= 48'd0;
                display_value <= 32'd0;
            end
        endcase
    end
//-----{输出到触摸屏显示}end
//----------------------{调用触摸屏模块}end---------------------//
endmodule

有关Verilog 乘法器的更多相关文章

  1. ruby - ruby 乘法语句中星号中断语法前的空格 - 2

    在添加一些空格以使代码更具可读性时(与上面的代码对齐),我遇到了这个:classCdefx42endendm=C.new现在这将给出“错误数量的参数”:m.x*m.x这将给出“语法错误,意外的tSTAR,期待$end”:2/m.x*m.x这里的解析器到底发生了什么?我使用Ruby1.9.2和2.1.5进行了测试。 最佳答案 *用于运算符(42*42)和参数解包(myfun*[42,42])。当你这样做时:m.x*m.x2/m.x*m.xRuby将此解释为参数解包,而不是*运算符(即乘法)。如果您不熟悉它,参数解包(有时也称为“spl

  2. ruby-on-rails - 浮点乘法的 Ruby 奇怪问题 - 2

    有没有人用ruby​​解决这个问题:假设我们有:a=8.1999999我们想将它四舍五入为2位小数,即8.20,然后乘以1,000,000得到8,200,000我们是这样做的;(a.round(2)*1000000).to_i但是我们得到的是8199999,为什么?奇怪的是,如果我们乘以1000、100000或10000000而不是1000000,我们会得到正确的结果。有人知道为什么吗?我们正在使用ruby​​1.9.2并尝试使用1.9.3。谢谢! 最佳答案 每当你在计算中得到时髦的数字时使用bigdecimalrequire'bi

  3. Verilog使用inout信号的方法 - 2

    目录一、inout在设计文件中的使用方法1.1、inout的第一种使用方法1.2、inout实现的第二种使用方法1.3、inout使用总结 二、inout在仿真测试中的使用方法一、inout在设计文件中的使用方法在FPGA的设计过程中,有时候会遇到双向信号(既能作为输出,也能作为输入的信号叫双向信号)。比如,IIC总线中的SDA信号就是一个双向信号,QSPIFlash的四线操作的时候四根信号线均为双向信号。在Verilog中用关键字inout定义双向信号,这里总结一下双向信号的处理方法。1.1、inout的第一种使用方法  实际上,双向信号的本质是由一个三态门组成的,三态门可以输出高电平,低电

  4. ruby - 简化字符串乘法连接 - 2

    s是一个字符串,这看起来很啰嗦——我该如何简化呢?:ifx===2z=selsifx===3z=s+selsifx===4z=s+s+selsifx===5z=s+s+s+selsifx===6z=s+s+s+s+s谢谢 最佳答案 像这样的东西是最简单且有效的(asseenonideone.com):puts'Hello'*3#HelloHelloHellos='Go'x=4z=s*(x-1)putsz#GoGoGoAPI链接ruby-doc.org-String:str*integer=>new_strCopy—Returnsan

  5. javascript - NaN 乘法 - 2

    我正在尝试这段代码,但我得到的是NaNa=unidade.val();b=unitario.val();//alert(a);5//alert(b);50,00$(total).val(a*b);//NaN为什么?因为是int*float吗? 最佳答案 在乘以val之前,您必须解析字符串总是返回一个字符串和"50,00"无法自动转换为数字。parseFloat("50,1")给你50.如果此处的逗号是小数点分隔符,则必须将其替换为点。所以你可能需要a=parseFloat(unidade.val().replace(",",".")

  6. vscode搭建Verilog HDL开发环境 - 2

      工欲善其事,必先利其器。应该没有多少人会使用Quartus和vivado这些软件自带的编辑器吧,原因在于这些编辑器效率很低,VerilogHDL代码格式比较固定,通常可以利用代码片段补全加快书写。基本上代码写完之后才会打开Quartus或者vivado建立工程,这其实要求编辑器需要有代码检错的功能,否则可能编译时一直报错,什么信号没定义,信号定义错误之类的。Vscode利用插件可以实现此功能,可以达到一次设计就通过编译和仿真。1、vscode安装及解决下载速度慢  首先通过VisualStudioCode-CodeEditing.Redefined安装vscode软件,如图1下载64位vs

  7. MCDF实验4:魔龙的狂舞(从verilog到SV的入门lab4) - 2

    前言:验证结构与实验3是相同的,但需要验证的对象是完整的mcdf。对比之前新添加了reg寄存器模块(选择数据),formatter模块(数据打包)。种一棵树最好的时间是十年前,其次是现在。不是吗?实验3结构包含moinitor、checker、generator、initiator、test,这已经是一个完整的仿真结构,实验4可以说是实验3结构的复制粘贴。实验4将设计变得更复杂,添加了reg寄存器模块,formatter模块。验证过程完全相同,需要像实验3的验证过程一样对这两个模块也做仿真验证。设计中reg的功能是可以选择从哪个fifo接收数据,并且可以判断fifo余量(之前是margin),

  8. JavaScript 性能 - 除法还是乘法?/vs * - 2

    我正在编写一个非常依赖JavaScript的应用程序(几乎全是JavaScript),它确实有大量数据需要迭代(JSON),因此它必须执行特定的算术任务。性能是应用程序的主要考虑因素。我已经引入了Webworkers来帮助解决这个问题,并且我试图不依赖于jQuery等库提供的方法(例如.each()而不是for循环)。无论如何,这是一个简单的问题...在应用程序中,我必须应用价格变化,这将涉及许多涉及除法的数字过程。请记住,这会发生成千上万次,对我来说,始终通过乘法或乘法和除法的混合来应用变化会更好吗?例如,我可以通过乘以0.5或除以2来应用50%的折扣。我总是被告知除法比乘法慢,但我

  9. javascript - 移位与乘法 - 2

    我正在尝试将字节数组转换为数字,对于大数字,我看到位移给出了-ve结果。你们中的一个人可以请问为什么我们会看到这个问题吗?您是否发现使用“乘法”而不是“移位”有任何缺点?例如,varmyVar=1000000;document.write("BitshiftResult:"+(myVar");document.write("MultiplicationResult:"+parseInt(myVar*256));Output:BitshiftResult:256000000MultiplicationResult:256000000在myVar中再添加一个零,您就会看到我在说的问题var

  10. 数字电路——加法器 - 2

    数字电路——加法器组合电路(可略过)加法器半加器全加器我们之前了解了门,电路就是门的组合。电路又可以分为两大类,组合电路和时序电路。但作为计算机和编程的知识铺垫,我们要了解的是组合电路的使用方法。所以本篇讲的是组合电路的运算(电路的布尔运算。)推导过程略看也行。建议还是看看,了解一下。组合电路(可略过)输出仅由输入值确定的电路把一个门的输出作为另一个门的输入,就可以把门组合电路.如上图,两个与门的输出被用作或门的输入。注意:A同时是两个与门的输入。两条交叉的连接线的交汇处没有连接点,应该看做是一条连接线跨做了一条,他们互不影响电路分析我们倒着看,按照门来分析如果X=1则说明,D和E至少有一个是

随机推荐