【问题标题】:How can I write a large VHDL module and keep it readable?如何编写大型 VHDL 模块并保持其可读性?
【发布时间】:2016-08-29 12:05:39
【问题描述】:

我正在尝试为玩具处理器编写控制逻辑模块。它循环读取/解码/执行状态,从内存的各个位读取和写入,并设置一堆控制信号。它有点大,据我所知,它不能真正细分为更小的模块。

我不想将所有状态的逻辑放在一个进程中——它很难阅读,而且大量的中间别名和信号在使用模拟器时是一种痛苦。

我尝试将每个状态的逻辑拆分到自己的进程中,但后来我遇到了多个驱动程序的问题。

我还尝试在一个主进程的头部为每个状态的逻辑声明单独的过程,并让进程根据当前状态调用正确的过程。这非常好用,具有模块化的“功能”和更易读的结构......但是每个过程的中间信号在模拟器中不可见(并且可能无法访问测试台?我之前放弃了尝试那个。)。我正在使用 ISim,以防万一。

我是不是做错了什么?我可以使用一些技巧来避免拥有一个庞大的单体进程吗?

编辑:模块的代码是here

【问题讨论】:

  • 证明在抽象信号中描述问题的风险不是子程序声明项(IEEE Std 1076-2008, 4.3 Subprogram body),您指的是变量(“...中间信号不可见......”)。你能提供一个specific programming problem吗?如果没有细节或描述,如何最好地用 VHDL 表示硬件可能无法回答。
  • 您的链接 control.vhd 代码无法正常工作,原因是其解释不适合多个 cmets 所允许的空间。我预计如果您要提供功能代码,那么您在可读性方面可能遇到的问题类别可能会大不相同。例如,您应该在单个进程中使用 case 语句,而不是为状态使用单独的进程。每个分配信号的进程都有该信号的驱动程序。对于已解析信号,已解析值是有效值。 is_uop 将始终为 false,is_call 和 is_direct 也是如此。

标签: vhdl xilinx-ise


【解决方案1】:

可能只是您需要使用更适合阅读大型 VHDL 文件的编辑器。我经常处理 3000 多行 VHDL 文件,其中大部分空间是单个进程的逻辑,并且由于支持代码折叠的编辑器,阅读它们没有困难。

我使用 Notepad++,但我确信还有其他可以支持折叠 VHDL 语法的编辑器。当我打开一个文件时,我按 alt+0 折叠每个可能的语法折叠点,然后根据需要展开到我正在处理的部分。您还可以使用行隐藏来折叠文件的任意部分,尽管使用起来有点尴尬。

如果您有大量相关的并发语句,您可以使用name : if true generate 轻松地将它们分组到一个折叠点,这还允许您声明主架构范围之外的中间信号(block 语句有效,但是并非所有工具都支持)。要在进程中强制折叠点,我使用if true then

【讨论】:

  • IEEE Std 1076-2008 14.5.3 生成语句“生成语句的详细说明包括用块语句的零个或多个副本替换生成语句,其声明部分由包含在生成语句,其语句部分由生成语句中包含的并发语句组成。”还有 11.8 生成语句。 IEEE Std 1076.6-2004 8.9.1(撤回)不需要支持块头(端口、泛型、映射)。生成语句不允许块头。 XST 匹配。
  • 我曾尝试使用块语句。我不记得是合成器还是模拟器卡住了,但不管官方规范怎么说,我都必须使用生成器才能让一切正常工作。
【解决方案2】:

如果您正在设计一个在巨大的案例语句中实现不同操作的处理器,那么您真正描述的是一系列并行功能单元,为输出多路复用器供电。根据运算模式的不同,您的输出可能由乘法、加法、减法、某些逻辑运算、移位等的输出驱动。

您可以通过在自己的实体中实现每个功能单元来轻松地以模块化方式设计它,其中一些可能非常简单。在第一种情况下,这些模块将无条件运行,并且它们的输出将馈送到输出多路复用器。您可以稍后添加由指令解码逻辑驱动的启用信号,该信号仅启用将在特定操作中使用的块,以节省电力。听起来你可能会使用这种方法最终得到很多控制信号,但如果你把它们都放在一个记录中,它会使代码非常紧凑,同时在控制的地方允许冗长和可读性使用信号,例如:

AddSub : entity work.AdderSubtractor
port map (
  clk => clk,
  enable => decoded_instruction.addsub_enable,
  a => a,
  b => b,
  mode => decoded_instruction.addsub_mode, -- This might be an enumerated type
  output => addsub_output
);

会有其他_output 信号,最后你会得到类似的东西

OutputMux : process (all)
begin
  case decoded_instruction.output_mux_select is
    when ADD_SUB => output <= addsub_output;
    when MULT => output <= mult_output;
    when LOGIC => output <= logic_output;
  end case;
end process;

这样做的一个好处是,您可能会发现在 FPGA 的 DSP 模块中实现的几个功能很有效;您可以轻松地设计一个用于加法、减法、乘法的功能块,并写入目标设备中的 DSP 块。它的输出只是“输出”多路复用器的另一个输入。根据我的经验,您应该能够使用单个 DSP 模块(或描述几个级联 DSP 模块的单个实体,具体取决于您的数据路径宽度)有效地实现许多处理功能。

我个人更喜欢这种使设计非常模块化的方法。在最近的一个多核 DSP 项目中,我只有几个文件有大约 500 行,大多数有 200 或更少。这意味着当我回到设计的某个部分时,它通常适合一页,并且很容易在很短的时间内被拾起和理解。我还发现,在实施繁重的流水线以提高设计性能时,在一个流程或实体中进行过多操作会使这项工作变得更加困难。

最后,如果功能元素包含在小实体中,您可以更轻松地模拟、测试和验证只是孤立的那段代码,根据我的经验,这允许块被签署更快,同时对代码更有信心。如果一切都在一个过程中,那么很难有信心做出改变来修复或改进一件事,不会破坏另一件事。同样,在一个流水线化的设计中,我发现很容易改变一些无意中导致设计失败的积极时序约束,所以实体越简单,发生这种情况的可能性就越小。

【讨论】:

  • 我在考虑提升一级,但您的回答仍然有用且相关(现在和以后)。我想我的获取/解码/执行阶段也是并行功能单元——我的多个信号驱动器的问题通过将这些阶段放在一个进程中得到解决,我猜这在幕后有一个隐含的多路复用器。如果我手动混合来自正确阶段的控制信号,我应该能够返回到单独的进程。
【解决方案3】:

如前所述,您的问题很难回答。我们在谈论多少行?

不过,您可以查找良好的 VHDL 代码实践: - 应避免使用别名(并非所有工具甚至都支持 AFAIK) - 给信号/变量一个清晰的名字 - 尝试对功能进行分组 - 尽量不要在由 500 行分隔的 2 个地方更改信号/变量,通常有办法 - 如果真的需要,您可以考虑使用 VHDL93 中引入的共享变量。 (但是,这不会解决您的多个驱动程序问题) - 不要忘记记录分组信号的可用性

关于让你的“中间信号可见”,你可以写

junk_proc: process(clk, rst) is
variable a,b,c: of_some_types;
begin
if rst then
//do reset stuff
elsif rising_edge(clk)
b:=func1(a);
c:=func2(b);
end if;
end process;

变量 a、b 和 c(在这种情况下为普通线)显然可以在任何模拟工具中可视化。

但是,如果您编写 b=func1(func2(func3(func4(a)))),请不要忘记将所有这些描述为在单个时钟周期内发生。考虑到您的描述,我敢打赌您会遇到问题,但也许这是一种很好的学习方式。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-04-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-04
    • 1970-01-01
    相关资源
    最近更新 更多