✎ 编者按
最近用一个实际体验不怎么样的片子,就跑版本时时序优化颇费功夫,在优化过程中一个关于加法器的优化,颇具特点,记录一下。
加法器引入的reset的复位扇出问题
对于复位信号,对于FPGA设计而言基本仅用在寄存器上,而在一个工程里发现reset接到了大量的LUT上,导致reset扇出过大,其中有一大类就是关于加法器的设计上。
加法器设计很简单,正如下面的电路:
always @(posedge clk) begin
if(rst) begin
cnt <= 8'd0;
end else if(clear) begin
cnt <= 8'd0;
end else begin
cnt <= cnt + 8'(inc);
end
end
一个很简单的8比特加法器。cnt在复位时为0,clear信号时cnt清零,否则当inc为1时进行加1。
这个电路在实际综合的时候,会将rst与clear接入到一个LUT2上面,然后送往寄存器的复位引脚上。
这个看起来是编译工具傻掉了,我们的理想情况是将复位直接送到寄存器的复位引脚上,然而工具对于代码结构的识别似乎没有那么智能。
从代码层面如何解
既然工具太笨,那么就要给他指导。可以对rst信号添加约束来解决这个问题。针对加法器这种情况,那么此处最佳做法就是从代码层面优化,使其看起来更符合在FPGA里面的结构样式。
对于一个寄存器,其最理想的模型大体是:
always_ff @(posedge clk ) begin
if(rst) begin
Q <= 1'b0;
end else begin
Q <= D;
end
end
这里同样对加法器电路进行相似的改造:
always_ff @(posedge clk ) begin
if(rst) begin
cnt <= 8'd0;
end else begin
cnt <= (cnt&{8{~clear}}) + 8'((~clear) & inc ) ;
end
end
在这里,在clear为1时 cnt将会被清除为0,否则将执行加法。如此,通过这种方式可以避免将复位与clear信号先经过一个LUT2后再送往寄存器复位引脚引发复位信号扇出过大。
扩展一下
基于上面的思路,在SpinalHDL中不妨做一个更通用的加法器FPGACounter:
/**
* FPGA加法器
* @param width 加法器位宽
* @param initValue 加法器初始值
* @param incEn 标识是否使能加操作
* @param decEn 标识是否使能减操作
* @param withClearValue 是否有清除信号
* @param withSetValue 是否有置位信号
* @param clearValue 清除时的清除值
* @param setValue 置位时的置位值
*/
class FpgaCounter(width: Int, initValue: BigInt = 0, incEn: Boolean, decEn: Boolean, withClearValue: Boolean, withSetValue: Boolean, clearValue: BigInt, setValue: BigInt) extends Component {
val io = new Bundle {
val clear = (withClearValue) generate in Bool()
val set = (withSetValue) generate in Bool()
val inc = (incEn) generate in UInt (width bits)
val dec = (decEn) generate in UInt (width bits)
val count = out UInt (width bits)
}
noIoPrefix()
io.count.setAsReg() init (initValue)
val clear_value = (withClearValue) generate (U(clearValue, width bits))
val set_value = (withSetValue) generate (U(setValue, width bits))
val inc_mask_value = (incEn) generate UInt(width bits)
val dec_mask_value = (decEn) generate UInt(width bits)
val count_mask = UInt(width bits)
val clear_mask = (withClearValue) generate Repeat(io.clear, width).asUInt
val set_mask = (withSetValue) generate Repeat(io.set, width).asUInt
if (withClearValue && withSetValue) {
count_mask := (io.count & (~(clear_mask | set_mask))) | (set_value & set_mask) | (clear_value & clear_mask)
if (incEn) {
inc_mask_value := io.inc & (~(clear_mask | set_mask))
}
if (decEn) {
dec_mask_value := io.dec & (~(clear_mask | set_mask))
}
} else if (withClearValue) {
count_mask := (io.count & (~clear_mask)) | (clear_value & clear_mask)
if (incEn) {
inc_mask_value := io.inc & (~clear_mask)
}
if (decEn) {
dec_mask_value := io.dec & (~clear_mask)
}
} else if (withSetValue) {
count_mask := (io.count & (~set_mask)) | (set_value & set_mask)
if (incEn) {
inc_mask_value := io.inc & (~set_mask)
}
if (decEn) {
dec_mask_value := io.dec & (~set_mask)
}
} else {
count_mask := io.count
inc_mask_value := io.inc
dec_mask_value := io.dec
}
if (decEn && incEn) {
io.count := count_mask + inc_mask_value - dec_mask_value
} else if (decEn) {
io.count := count_mask - dec_mask_value
} else if (incEn) {
io.count := count_mask + inc_mask_value
}
}
这个加法器支持加、减、置位、清除这四个动作,根据需要的功能可以填入不同的参数(如果清除和置位均使能情况下,默认不会在同一个时钟周期下即拉清除,又拉复位功能)。同时为了使用时的方便,定义FpgaCounter Object:
object FpgaCounter {
/**
*
* @param bitCount :计数器位宽
* @param inc : 累加条件
* @param dec : 减1条件
* @param clear :计数器清零
* @param set :计数器置位
* @param initValue :count初始值
* @param clearValue :clear后值
* @param setValue :set后值
* @return Count计数器
*/
def BoolCount(bitCount: BitCount, inc: Bool=null,dec:Bool=null,clear:Bool=null,set:Bool=null,initValue:BigInt=0,clearValue:BigInt = 0, setValue:BigInt= -1): UInt = {
require(inc!=null || dec!=null)
val defaultSetValue= ((BigInt(1)<<bitCount.value)-1)&setValue
val count_inst= new FpgaCounter(width = bitCount.value, initValue = initValue, incEn = inc!=null, decEn = dec!=null, withClearValue = clear!=null, withSetValue = set!=null, clearValue = clearValue, setValue = defaultSetValue)
if(clear!=null){
count_inst.io.clear:=clear
}else{
count_inst.io.clear.clear()
}
if(set!=null){
count_inst.io.set:=set
}else{
count_inst.io.set.clear()
}
if(inc!=null){
count_inst.io.inc:=U(inc,bitCount)
}else{
count_inst.io.inc.clearAll()
}
if(dec!=null){
count_inst.io.dec:=U(dec,bitCount)
}else{
count_inst.io.dec.clearAll()
}
count_inst.io.count
}
/**
*
* @param bitCount :计数器位宽
* @param inc : 累加条件
* @param dec : 减1条件
* @param clear :计数器清零
* @param set :计数器置位
* @param initValue :count初始值
* @param clearValue :clear后值
* @param setValue :set后值
* @return Count计数器
*/
def UIntCount(bitCount: BitCount, inc: UInt=null,dec:UInt=null,clear:Bool=null,set:Bool=null,initValue:BigInt=0,clearValue:BigInt = 0, setValue:BigInt= -1): UInt = {
require(inc!=null || dec!=null)
val defaultSetValue= ((BigInt(1)<<bitCount.value)-1)&setValue
val count_inst= new FpgaCounter(width = bitCount.value, initValue = initValue, incEn = inc!=null, decEn = dec!=null, withClearValue = clear!=null, withSetValue = set!=null, clearValue = clearValue, setValue = defaultSetValue)
if(clear!=null){
count_inst.io.clear:=clear
}else{
count_inst.io.clear.clear()
}
if(set!=null){
count_inst.io.set:=set
}else{
count_inst.io.set.clear()
}
if(inc!=null){
count_inst.io.inc:=inc
}else{
count_inst.io.inc.clearAll()
}
if(dec!=null){
count_inst.io.dec:=dec
}else{
count_inst.io.dec.clearAll()
}
count_inst.io.count
}
}
这里定义了BoolCount和UIntCount,分别对应于加1或者加n场景。在使用时可采用如下方式:
case class CounterTest() extends Component {
val io=new Bundle{
val inc=in UInt(8 bits)
val dec=in UInt(8 bits)
val set= in Bool()
val clear=in Bool()
val cnt=out UInt(8 bits)
}
noIoPrefix()
io.cnt:=FpgaCounter.UIntCount(bitCount = 8 bits, inc = io.inc, dec = io.dec, clear = io.clear, set = io.set, initValue = 0, clearValue = 0, setValue = 0xff)
}
写在最后
用FPGA越来越觉得最终设计电路时要考虑结合FPGA期间特性这个金玉良言。太真实了。
☆ END ☆
作者:玉骐
文章来源:Spinal FPGA
推荐阅读
- 从Verilog PLI到SystemVerilog DPI的演变
- CDC11:复位信号跨时钟--异步复位同步释放
- for循环+fork-join_none结构的坑,你有注意到吗?
- ICer的文本处理利器 sed
- PCIe物理层:链路初始化和训练:EIOS(Electrical Idle Ordered Set)
更多IC设计干货请关注IC设计专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。