在 VHDL 中编码时是否有必要将组合逻辑与时序逻辑分开,同时以综合为目标?

Is it necessary to seperate combinational logic from sequential logic while coding in VHDL, while aiming for synthesis?

我正在从事需要专门为 ASIC 开发合成 RTL 代码的项目。在这种情况下,在设计我的 RTL 时将时序逻辑与差分逻辑分开有多重要?如果它很重要,那么我在设计时应该采用什么方法,好像我应该如何区分顺序逻辑和组合逻辑的设计?

如果可以的话,我会 post 它作为评论,因为我没有写完整的答案,而是给你一个来源,而且我没有完全参考这个问题(我不知道关于 ASIC 的任何事情)。但是一般来说,关于这个问题有很好的 pdf here。通常,您不必将时序逻辑与差分逻辑完全分开,但有助于编写更具可读性和可维护性的代码。

我通常会尽可能地将顺序和组合分开,直到它导致代码过多(这通常很少见,并且可能表明设计不佳),或者当某些东西更有意义时(这又是罕见,但会发生)。

这种分离通常有助于初始设计、brain->rtl->synthesis(你认为你正在做的实际上是合成的)、多时钟设计的 CDC 评估、验证和其他事情。我很难举出一个很好的例子来说明坏事和我认为好的事,但这里是一个尝试。

假设我有一个计数器,我想将其重置为某个值。我可以做到这一点(我倾向于从具有强大软件 and/or FPGA 背景的人那里看到这一点)

always @(posedge clk or posedge reset) begin
    if(reset) begin
        count <= 0;
    end else begin
        if((count == reset_count_val) || (~enable)) count <= 0;
        else                                        count <= count + 1;
    end
end

或者我会这样做(这是我个人会做的):

//Combinational Path
assign count_in = enable ? ((count == reset_count_val) ? 4'd0 : count_in + 4'd1) : 4'd0;

//Sequential Path
always @(posedge clk or posedge reset) begin
    if(reset) count <= 4'd0;
    else      count <= count_in;
end

我同意上面的内容需要打字,对某些人来说,更难阅读。然而,它确实以一种让我更容易看到每个时钟边沿发生了什么的方式拆分了电路。我知道 "count_in" 是在 clk 的 posedge 之前设置的。我可以很容易地看到(以及任何其他查看代码的人)我期望一个 MUX 用于基于 reset_count_val 重置或添加计数,最终 MUX 用于基于启用信号门控计数.现在您可以看到与第一段代码相同的内容,但是 IMO 还不是很清楚。当您查看 sim 时,您可以看到 count_in 在 clk 上升沿之前的样子。如果 count_in 的条件语句相当复杂,这可能对您有所帮助。

假设您通过综合和布局布线发送了它,并且在计数寄存器的 Q 和计数寄存器的 D 之间出现了时序冲突(因为您有一个基于加法的环回)。在第二批代码中,通常 更容易看出是哪条路径导致了问题。这取决于工具(很可能是黄金时段)。 CDC 也可能更容易,因为假设 reset_count_val 来自另一个时钟域中的静态寄存器。工具 可能 尝试 synthesize/elaborate 第一批代码中的 OR 认为 reset_count_val 和启用在某种程度上相关,给你一个看起来很奇怪的 CDC 违规。同样,有时很难想出一个例子来演练所有 "why you shouldn't do this" 类型的案例。

作为一个关于拆分组合和顺序的例子,我继承了一个设计,其中某人有一个状态机,它在同一个块中用组合和顺序编写(总是@(posedge clk),其中 if/else会变得很深,并在其中包含下一个状态逻辑)。我不是天才,但在盯着那个东西和 运行 模拟人生看了好几天之后,我就是搞不懂它在做什么。它也很大。我只是重新设计,保留相同的算法,但按照我在此处描述的格式拆分逻辑。即使我添加了一些功能,大小也下降了约 15%。与其他设计有同样问题的其他工程师现在可以理解它发生了什么。情况并非总是如此,但经常如此。

TLDR; 在设计要在 ASIC 中使用的 RTL 时,我尽量做到描述性极强。代码越抽象,就越有可能构建您不想要的东西,或者比需要的更复杂。验证通常要容易得多,尤其是当您进入模拟人生大门时。虽然我在 "the less code the better" 阵营,但 verilog 并非总是如此,尤其是在 ASIC 上。

一般来说,我会毫不犹豫地将组合逻辑与时序逻辑混合使用。我来自 IC 设计背景,并且总是将组合逻辑与顺序逻辑混为一谈。我认为,如果您不这样做并且没有充分利用逻辑合成器的强大功能,那么您就是在过多地限制自己。

例如,我将如何在 VHDL 中设计一个简单的异步复位计数器:

  process (Clock, Reset)
  begin
    if Reset = '1' then
      Cnt <= (others => '0');
    elsif Rising_edge(Clock) then
      if Enable = '1' then
          Cnt <= Cnt + 1;
      end if;
    end if;
  end process;

这种在 VHDL 中编写计数器的风格无处不在。我个人认为将代码拆分为两个单独的进程没有任何好处,一个是顺序的,另一个是组合的。我刚刚教了一屋子的工程师用这种方法设计了一个计数器。

这里有一些例外情况。 如果:

,我会把组合逻辑从顺序逻辑中分离出来

i) 我在设计一个状态机:

我认为这是一种非常优雅的状态机编码方式,您可以将组合逻辑与顺序逻辑分开:

  Registers: process (Clock, Reset)
  begin
    if Reset = '1' then
      State <= Idle;
    elsif Rising_edge(Clock) then
      State <= NextState;
    end if;
  end process Registers;

  Combinational: process (State, Inputs)
  begin
    NextState <= State;
    Output1 <= '0';
    Output2 <= '0';
    -- etc
    case State is
      when Idle =>
        if Inputs(1) = '1' then
          NextState <= State2;
        end if;
      when State2=>
        Output1 <= '1';
        if Inputs = "00" then
          NextState <= State3;
        end if;
      -- etc
    end case;
  end process Combinational;

像这样编写状态机的优点是组合过程看起来很像状态图。写入更不易出错,修改更不易出错,读取更不易出错。

ii) 组合逻辑很复杂:

对于非常大的组合逻辑块,我会分开。 "really big" 的确切定义是判断问题。

iii) 组合逻辑位于触发器的 Q 输出端:

顺序过程中驱动的任何信号都可以推断出触发器。因此,如果您希望实现驱动实体输出的组合逻辑*,那么此组合逻辑必须在一个单独的过程中。

*通常不是一个好主意 - 小心。

混合顺序和组合可以压缩代码,这几乎总是更容易理解。

分离使 ECO 更容易。

您选择哪种取决于个人风格和组织编码惯例和标准。