I am working on an AES encryptor/decryptor project that can handled AES-128, AES-192, and AES-256. The cipher key length is chosen at compile time. The module I am working on generates the key schedule. The algorithm to do so varies slightly for AES 256 vs the other two, so I have two different functions to handle them. My key expansion module has only one combinational logic block:
always_comb
begin
expandedKey.expBlocks[0] = key;
for (int i=1; i<=`NUM_KEY_EXP_ROUNDS; ++i)
expandedKey.expBlocks[i] = expandBlock(i, expandedKey.expBlocks[i-1]);
end
These are the functions the previous code may call:
`ifndef AES_256 // expanding a 128- or 192-bit key
function automatic expandBlock(input integer round, expKeyBlock_t prevBlock);
// ... function body
endfunction
`else // Expanding a 256-bit key
function automatic expandBlock(input integer round, expKeyBlock_t prevBlock);
// ... function body
endfunction
`endif // `ifndef AES_256
The problem with this configuration is that it makes testing difficult. In my testbench, I want to be able to instantiate this module in each AES_XXX configuration and simulate all three without recompiling to make regression testing easier.
I've considered using parameters instead:
`ifndef AES_256 // expanding a 128- or 192-bit key
parameter logic SELECT_AES_256 = 1'b0;
`else // Expanding a 256-bit key
parameter logic SELECT_AES_256 = 1'b1;
`endif // `ifndef AES_256
always_comb
begin
expandedKey.expBlocks[0] = key;
// I chose to put the SELECT_AES_256 check outside the loop so it isn't
// checked on every loop iteration, potentially generating even more extra
// hardware
if (SELECT_AES_256)
for (int i=1; i<=`NUM_KEY_EXP_ROUNDS; ++i)
expandedKey.expBlocks[i] = expandBlock_long(i, expandedKey.expBlocks[i-1]);
else
for (int i=1; i<=`NUM_KEY_EXP_ROUNDS; ++i)
expandedKey.expBlocks[i] = expandBlock_short(i, expandedKey.expBlocks[i-1]);
end
function automatic expandBlock_short(input integer expRound, expKeyBlock_t prevBlock);
// ... function body
endfunction
function automatic expandBlock_long(input integer expRound, expKeyBlock_t prevBlock);
// ... function body
endfunction
Using a parameter would allow me to instantiate the module in whatever configuration I want, but it has duplicated code, and I'll eventually be relying on synthesizer optimizations to eliminate unused hardware. I don't like either of those attributes.
The Question
Is there a way I can refactor this code so that I can choose which expandBlock
function is called when the module is instantiated, without the duplicated code and potentially duplicated hardware?