Using pseudo code here. Are there pros and cons to these styles:
Say you have an alu that can do add, and, or and xor. Is it better to have code that computes the possible answers all the time then select the answer based on the opcode (in this case a one hot):
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;
An alternative would be to code it like this:
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;
I have also seen it as:
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 );
Are there pros and cons to either method or do they come out about the same in the end?
Think about this in terms of levels of logic and readability. The first two forms are fine in terms of readability, but both embody priority unnecessarily and will result in more levels of logic. The third form is also not so wonderful by either of these metrics. Lastly, there's no apparent reason to use one-hot coding here over binary coding. Here's how I'd code this:
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
// and later, in your always block:
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
Here I've explicitly assigned values to the ALU opcodes, avoiding "magic numbers" and making it easier for others to understand what's happening. I've also used the case statement and applied a directive that tells my synthesis tool that no more than one expression can be matched, so no priority encoder will be inferred. I didn't name the intermediate signals (alu_add etc.) because these are trivial operations, but I frequently do so when I want convenient access to those signals (seeing their values after simulation in a waveform viewer for example).
You can learn more about using case statements effectively in this article from the excellent Sunburst Design site (no affiliation, just a former student).
Finally, about your question, "Is it better to have code that computes the possible answers all the time then select the answer based on the opcode" -- remember that Verilog is a hardware description language. All the implementations on this page are computing everything all the time anyway. Where they differ is in levels of logic and readability. Take a look at this page, which shows that my implementation has 1 level of logic beyond the operations themselves, where the if-else implementation has 3 additional levels of logic.
The first two will give the same logic, but you'll get a latch on alu_out
because your if else
block (your priority encoder) has no final else
. (This is true for verilog anyway). If your timing is tight, you may have issues with the long paths that a priority encoder implies.
On the 3rd version, you'll get a more 'parallel' structure which may be better for timing. It won't latch.
In all three versions, each of the four operations will be clattering away nomatter what the opcode. This will have power implications.
It's my opinion that the first version wins for clarity, and you can get at each of the separate operations in your waveform viewer when debugging. There's no point in coding up something that is not as easy to read unless timing, area or power constraints are hurting.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With