赞
踩
@(verilog)
问题:输入一个16bit的数,现在要求它除以3得到的商和余数,如何优化?
来源:@笑着刻印在那一张泛黄 提供,面试真题。
一开始联想到之前写过的另一篇博文序列模三检测器,但是这只能解决余数的问题,没法得到商。
后面的想法是直接使用任意整数除法器来实现,由于除数是3,比较特殊,实际上除3只需要考虑三个序列,也就是11,100,101。因为在手算除三的过程中,如果用二进制算,从高位依次往低位计算,就可以发现这一规律,例如:
0 0 1 1 0 0 1 1 0 (7d'102) ------------------ 11 | 1 0 0 1 1 0 1 0 0 (8d'308) 1 1 -------- 1 1 1 1 ---------------- 0 1 0 1 1 1 ----------- 1 0 0 1 1 ---------- 1 0 (余数)
整理一下,就是说把被除数从高位到低位排列,从前到后依次找11,100,101这三个序列,遇到这三中序列商就写1,否则商就写0,并且写完后左移一位。做完之后移除这个序列。不同的是遇到11,不做其他操作,遇到100,将3’b100-2’b11=1’b1 插入原序列最高位;遇到101则插入2’b10。
如果写成状态机,则可列出状态转移表:
State\Input | 0 | 1 |
---|---|---|
0 | 0/+0/0 | 1/+0/1 |
1 | 10/+0/10 | 0/+1/0 |
10 | 1/+1/1 | 10/+1/10 |
表中的值表示next_state/商操作/余数,其中商操作+1就表示商序列加入一个1,+0就表示加入一个0.
此外,需要一个计数器来控制状态转移次数,理论上只要计数到被除数的位宽-1即可,但我在实际代码中为了规避最后返回IDLE使得余数不正确的问题,将它计数到了被除数位宽(可能有其他写法)。最后得到商和余数,余数就是计数器结束时的下一个状态表示的二进制数。
`timescale 1ns/1ps module divide_by_three #( parameter DATAWIDTH = 16 )( input clk, input rst_n, input vld_in, input [DATAWIDTH-1:0] data_in, output reg [DATAWIDTH-1:0] quotient, output reg [1:0] reminder, output reg vld_out ); reg [1:0]current_state; reg [1:0]next_state; reg [$clog2(DATAWIDTH):0] cnt; reg [DATAWIDTH-1:0] data_reg; parameter IDLE = 2'b11; always @(posedge clk or negedge rst_n) if(!rst_n) current_state <= IDLE; else current_state <= next_state; always @(*) case(current_state) IDLE: if(vld_in) next_state = 2'b0; else next_state = IDLE; 2'b00: if (cnt == DATAWIDTH) next_state = IDLE; // cnt = 16 not 15, for the calc of remainder else if(data_reg[DATAWIDTH-1]) next_state = 2'b1; else next_state = 2'b0; 2'b01: if (cnt == DATAWIDTH) next_state = IDLE; else if(data_reg[DATAWIDTH-1]) next_state = 2'b0; else next_state = 2'b10; 2'b10: if (cnt == DATAWIDTH) next_state = IDLE; else if(data_reg[DATAWIDTH-1]) next_state = 2'b10; else next_state = 2'b1; default: next_state = IDLE; endcase always @(posedge clk or negedge rst_n) if(!rst_n) begin {cnt,data_reg,reminder,quotient,vld_out} <= 0; end else begin case(current_state) IDLE: begin {vld_out,cnt} <= 0; if(vld_in) data_reg <= data_in; else data_reg <= data_reg; end 2'b00,2'b01,2'b10: begin if(cnt == DATAWIDTH-1) begin cnt <= cnt + 1; // without this,remainder will be next_state=IDLE=2'b11' reminder <= next_state; vld_out <= 1; end else begin cnt <= cnt + 1; vld_out <= 0; data_reg <= {data_reg[DATAWIDTH-2:0],1'b0}; end if(data_reg[DATAWIDTH-1]) quotient <= {quotient[DATAWIDTH-2:0],current_state[1]|current_state[0]}; else quotient <= {quotient[DATAWIDTH-2:0],current_state[1]}; end endcase end endmodule
testbench:
`timescale 1ns/1ps module divide_by_three_tb(); parameter DATAWIDTH = 16; reg clk; reg rst_n; reg vld_in; reg [DATAWIDTH-1:0] data_in; wire [DATAWIDTH-1:0] quotient; wire [1:0] reminder; wire [1:0] vld_out; reg [DATAWIDTH-1:0] quotient_ref; reg [1:0] reminder_ref; always #1 clk = ~clk; initial begin clk = 0; vld_in = 0; data_in = 0; rst_n = 1; #4 rst_n = 0; #2 rst_n = 1; repeat(10) begin @(posedge clk); vld_in <= 1; data_in = $urandom()%100; quotient_ref = data_in/3; reminder_ref = data_in%3; @(posedge clk); vld_in <= 0; wait(vld_out==1); end end divide_by_three #( .DATAWIDTH ( DATAWIDTH )) U_DIVIDE_BY_THREE_0 ( .clk ( clk ), .rst_n ( rst_n ), .vld_in ( vld_in ), .data_in ( data_in ), .quotient ( quotient ), .reminder ( reminder ), .vld_out ( vld_out )); initial begin $fsdbDumpvars(); $fsdbDumpMDA(); $dumpvars(); #1000 $finish; end endmodule
仿真波形:
对于16位的被除数,需要18个周期得到计算结果。
相比于直接使用任意整数除法器,节省了减法操作,通过找规律化简了状态机。
其实对于输入也可以是之前的序列输入的形式,只需要修改输入的形式,调整下内部的数据存储即可。并且这种写法可以实现每输入一个bit,可以直接同时输出当前序列的商和余数。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。