在这里使用伪代码。这些风格是否有利有弊:
假设你有一个alu可以做添加,和,或者xor。是否有更好的代码可以一直计算可能的答案,然后根据操作码选择答案(在这种情况下是一个热门):
alu_add = a + b;
alu_and = a & b;
alu_or = a | b;
alu_xor = a ^ b;
...
if(opcode[0]) alu_out = alu_add;
else if(opcode[1]) alu_out = alu_and;
else if(opcode[2]) alu_out = alu_or;
else if(opcode[3]) alu_out = alu_xor;
另一种方法是像这样编码:
if(opcode[0]) alu_out = a + b;
else if(opcode[1]) alu_out = a & b;
else if(opcode[2]) alu_out = a | b;
else if(opcode[3]) alu_out = a ^ b;
我也把它看作:
alu_add = a + b;
alu_and = a & b;
alu_or = a | b;
alu_xor = a ^ b;
...
alu_out =
( 8{opcode[0]} & alu_add ) |
( 8{opcode[1]} & alu_and ) |
( 8{opcode[2]} & alu_or ) |
( 8{opcode[3]} & alu_xor );
这两种方法都有利弊吗,或者它们最终会出现同样的问题?
答案 0 :(得分:8)
从逻辑和可读性的角度考虑这一点。前两种形式在可读性方面很好,但两者都不必要地体现优先级,并将导致更多的逻辑层次。第三种形式也不是这些指标中的任何一种。最后,没有明显的理由在这里使用单热编码而不是二进制编码。以下是我对此进行编码的方式:
parameter ALU_ADD = 2'b00;
parameter ALU_AND = 2'b01;
parameter ALU_OR = 2'b10;
parameter ALU_XOR = 2'b11;
reg [1:0] opcode; // 2 bits for binary coding vs. 4 for one-hot
//及以后,在你的总是阻止:
case (opcode) // synopsys parallel_case
ALU_ADD: alu_out = a + b;
ALU_AND: alu_out = a & b;
ALU_OR: alu_out = a | b;
ALU_XOR: alu_out = a ^ b;
endcase
这里我明确地为ALU操作码指定了值,避免了“魔术数字”,让其他人更容易理解正在发生的事情。我还使用了case语句并应用了一个指令,该指令告诉我的综合工具只能匹配一个表达式,因此不会推断出优先级编码器。我没有命名中间信号(alu_add等),因为这些操作很简单,但是当我想要方便地访问这些信号时(例如在波形查看器中模拟后看到它们的值),我经常这样做。
您可以在优秀的this article网站(没有任何关系,只是前学生)的Sunburst Design中了解有关使用案例陈述的更多信息。
最后,关于你的问题,“让代码一直计算可能的答案然后根据操作码选择答案是否更好” - 请记住,Verilog是一种硬件描述语言。无论如何,这个页面上的所有实现都在计算所有内容。它们的不同之处在于逻辑和可读性水平。看一下this page,它表明我的实现有超出操作本身的1级逻辑,其中if-else实现有3个额外的逻辑级别。
答案 1 :(得分:2)
前两个会给出相同的逻辑,但是你会在alu_out
上得到一个锁存器,因为你的if else
块(你的优先级编码器)没有最终else
。 (无论如何,这对于verilog都是如此)。如果您的时间紧张,您可能会遇到优先级编码器所暗示的长路径问题。
在第3版中,您将获得更“平行”的结构,这可能更适合计时。它不会锁定。
在所有三个版本中,四个操作中的每个操作都会使操作码无关紧要。这会产生影响力。
我认为第一个版本为了清晰而获胜,并且您可以在调试时获得波形查看器中的每个单独操作。除非时间,面积或功率限制受到伤害,否则编写一些不易读取的内容是毫无意义的。