Files
retroSoC/rtl/ip/security/crypto_aes_sram_engine.sv
T
maksyuki 3ac9e73ab9 feat: refactor tests for mailbox and environment validation
- Updated mailbox purpose tests to reflect new write order and sequence handling.
- Added tests to ensure Linux-ready values match LP contract and constants match Linux helper.
- Introduced tests to verify that offset changes do not hide stale Linux helpers.
- Enhanced environment tests to report current locked launcher and runtime versions.
- Added checks for SBT launcher presence in environment labels.
- Improved full diagram tests to validate storage geometry and RSA vector configurations.
- Expanded register tests to ensure crypto interrupt registers publish all events and lifecycle annotations preserve security boundaries.
- Modified script tools tests to include Vexii generation validation and updated workflow assertions.
2026-09-30 10:51:04 +08:00

373 lines
14 KiB
Systemverilog

// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
// SPDX-License-Identifier: MulanPSL-2.0
module crypto_aes_sram_engine (
input logic clk_i,
rst_n_i,
zeroize_i,
fault_i,
abort_i,
input logic key_commit_i,
key_dirty_i,
input logic [ 1:0] key_size_i,
output logic key_valid_o,
key_busy_o,
input logic start_i,
input logic [ 1:0] mode_i,
input logic decrypt_i,
input logic [31:0] length_i,
input logic input_valid_i,
output logic input_ready_o,
input logic [31:0] input_data_i,
input logic [ 3:0] input_keep_i,
input logic input_last_i,
output logic output_valid_o,
input logic output_ready_i,
output logic [31:0] output_data_o,
output logic [ 3:0] output_keep_o,
output logic output_last_o,
output logic busy_o,
output logic done_o,
error_o,
output logic [31:0] bytes_in_o,
bytes_out_o,
cycles_o,
output logic fifo_busy_o,
fifo_error_o,
scrub_error_o,
output logic [ 9:0] scrub_error_row_o,
output crypto_mem_pkg::crypto_mem_req_t constant_req_o,
input crypto_mem_pkg::crypto_mem_resp_t constant_resp_i,
output crypto_mem_pkg::crypto_mem_req_t work_req_o,
input crypto_mem_pkg::crypto_mem_resp_t work_resp_i
);
import crypto_pkg::*;
import crypto_mem_pkg::*;
typedef enum logic [4:0] {
EngineIdle,
EngineKey,
EngineIvRead,
EngineIvWait,
EngineIvWrite,
EngineInput,
EnginePad,
EngineStart,
EngineCore,
EngineOutputRead,
EngineOutputWait,
EngineOutputPush,
EngineLast,
EngineDrain,
EngineClear,
EngineScrub,
EngineFaultDrain,
EngineFault
} engine_state_e;
typedef struct packed {
engine_state_e state;
logic [1:0] mode;
logic decrypt;
logic [31:0] length, bytes_in, bytes_out, cycles;
logic [31:0] word;
logic [4:0] block_bytes;
logic [1:0] lane, word_index;
logic [4:0] input_index;
logic last_block, done, error;
logic [11:0] scrub_cycles;
logic scrub_timeout;
} engine_registers_t;
engine_registers_t s_regs_d, s_regs_q;
crypto_mem_req_t s_core_req;
crypto_mem_req_t [0:0] s_scrub_req;
crypto_mem_resp_t [0:0] s_scrub_resp;
logic s_core_busy, s_core_done, s_core_start;
logic [36:0] s_input_data, s_output_data, s_output_payload;
logic s_input_empty, s_input_full, s_output_empty, s_output_full;
logic s_input_pop, s_input_push, s_output_pop, s_output_push;
logic [1:0] s_fifo_busy, s_fifo_err;
logic s_fifo_clear, s_scrub_busy, s_scrub_done, s_scrub_err;
logic [2:0] s_input_bytes, s_head_bytes;
logic [32:0] s_next_bytes;
logic [31:0] s_assembled_word;
logic [ 2:0] s_output_bytes;
function automatic logic [2:0] count_keep(input logic [3:0] keep);
unique case (keep)
4'h1: return 3'd1;
4'h3: return 3'd2;
4'h7: return 3'd3;
4'hf: return 3'd4;
default: return 3'd0;
endcase
endfunction
dffr #(
.DATA_WIDTH($bits(engine_registers_t))
) u_state (
.clk_i (clk_i),
.rst_n_i(rst_n_i),
.dat_i (s_regs_d),
.dat_o (s_regs_q)
);
crypto_aes_sram_core u_core (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.zeroize_i (zeroize_i || fault_i),
.abort_i (abort_i || s_regs_q.state == EngineClear),
.key_dirty_i (key_dirty_i),
.key_commit_i (key_commit_i && !busy_o),
.key_size_i (key_size_i),
.start_i (s_core_start),
.mode_i (s_regs_q.mode),
.decrypt_i (s_regs_q.decrypt),
.key_valid_o (key_valid_o),
.busy_o (s_core_busy),
.done_o (s_core_done),
.constant_req_o (constant_req_o),
.constant_resp_i(constant_resp_i),
.work_req_o (s_core_req),
.work_resp_i (work_resp_i)
);
assign s_fifo_clear = zeroize_i || (s_regs_q.state == EngineClear);
crypto_clearable_fifo #(
.Depth(8)
) u_input_fifo (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.clear_i (s_fifo_clear || fault_i),
.flush_i (start_i),
.push_i (s_input_push),
.data_i ({input_last_i, input_keep_i, input_data_i}),
.pop_i (s_input_pop),
.data_o (s_input_data),
.empty_o (s_input_empty),
.full_o (s_input_full),
.count_o (),
.clear_busy_o (s_fifo_busy[0]),
.clear_done_o (),
.clear_error_o(s_fifo_err[0])
);
crypto_clearable_fifo #(
.Depth(8)
) u_output_fifo (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.clear_i (s_fifo_clear || s_regs_q.state == EngineFault),
.flush_i (start_i),
.push_i (s_output_push),
.data_i (s_output_payload),
.pop_i (s_output_pop),
.data_o (s_output_data),
.empty_o (s_output_empty),
.full_o (s_output_full),
.count_o (),
.clear_busy_o (s_fifo_busy[1]),
.clear_done_o (),
.clear_error_o(s_fifo_err[1])
);
assign s_scrub_resp[0] = work_resp_i;
crypto_scrubber #(
.NumBanks(1)
) u_scrub (
.clk_i (clk_i),
.rst_n_i (rst_n_i),
.start_i (s_regs_q.state == EngineClear),
.cancel_i (zeroize_i || fault_i),
.first_row_i (10'd76),
.busy_o (s_scrub_busy),
.done_o (s_scrub_done),
.error_o (s_scrub_err),
.error_bank_o(),
.error_row_o (scrub_error_row_o),
.req_o (s_scrub_req),
.resp_i (s_scrub_resp)
);
assign fifo_busy_o = |s_fifo_busy;
assign fifo_error_o = |s_fifo_err;
assign scrub_error_o = s_scrub_err || s_regs_q.scrub_timeout;
assign busy_o = (s_regs_q.state != EngineIdle) && (s_regs_q.state != EngineFault);
assign key_busy_o = s_regs_q.state == EngineKey;
assign done_o = s_regs_q.done;
assign error_o = s_regs_q.error;
assign bytes_in_o = s_regs_q.bytes_in;
assign bytes_out_o = s_regs_q.bytes_out;
assign cycles_o = s_regs_q.cycles;
assign s_input_bytes = count_keep(input_keep_i);
assign s_head_bytes = count_keep(s_input_data[35:32]);
assign s_next_bytes = {1'b0, s_regs_q.bytes_in} + 33'(s_input_bytes);
assign input_ready_o = busy_o && !key_busy_o && (s_regs_q.state < EngineDrain) && !s_input_full &&
(s_regs_q.bytes_in < s_regs_q.length) && !abort_i && !fault_i;
assign s_input_push = input_valid_i && input_ready_o;
assign output_valid_o = !s_output_empty &&
((s_regs_q.state < EngineClear) || (s_regs_q.state == EngineFaultDrain));
assign output_data_o = s_output_data[31:0];
assign output_keep_o = s_output_data[35:32];
assign output_last_o = s_output_data[36];
assign s_output_pop = output_valid_o && output_ready_i;
assign s_core_start = s_regs_q.state == EngineStart;
assign s_output_bytes = (s_regs_q.block_bytes - {1'b0, s_regs_q.word_index, 2'b00}) >= 5'd4 ?
3'd4 : 3'(s_regs_q.block_bytes - {1'b0, s_regs_q.word_index, 2'b00});
assign s_output_payload = {
s_regs_q.last_block && (({1'b0, s_regs_q.word_index, 2'b00} + 5'd4) >= s_regs_q.block_bytes),
4'((5'd1 << s_output_bytes) - 1'b1),
byte_reverse(s_regs_q.word)
};
always_comb begin
s_regs_d = s_regs_q;
s_regs_d.done = 1'b0;
work_req_o = s_core_req;
s_input_pop = 1'b0;
s_output_push = 1'b0;
s_assembled_word = s_regs_q.word;
s_assembled_word[31-s_regs_q.input_index[1:0]*8-:8] = s_input_data[s_regs_q.lane*8+:8];
if (busy_o) s_regs_d.cycles = s_regs_q.cycles + 1'b1;
if (s_input_push) s_regs_d.bytes_in = s_next_bytes[31:0];
if (s_output_pop && (s_regs_q.state != EngineDrain)) begin
s_regs_d.bytes_out = s_regs_q.bytes_out + 32'(count_keep(output_keep_o));
if (output_last_o) begin
s_regs_d.done = 1'b1;
s_regs_d.state = EngineIdle;
end
end
unique case (s_regs_q.state)
EngineIdle: begin
if (key_commit_i) begin
s_regs_d.state = EngineKey;
s_regs_d.cycles = '0;
end else if (start_i) begin
s_regs_d = '0;
if (!key_valid_o || (length_i == 0) || (mode_i == 2'd3) ||
((mode_i != AES_MODE_CTR) && (length_i[3:0] != 4'd0))) begin
s_regs_d.error = 1'b1;
s_regs_d.done = 1'b1;
end else begin
s_regs_d.state = EngineIvRead;
s_regs_d.mode = mode_i;
s_regs_d.decrypt = decrypt_i;
s_regs_d.length = length_i;
end
end
end
EngineKey: if (s_core_done) s_regs_d.state = EngineIdle;
EngineIvRead: begin
work_req_o = mem_read(10'd80 + 10'(s_regs_q.word_index));
s_regs_d.state = EngineIvWait;
end
EngineIvWait:
if (work_resp_i.valid) begin
s_regs_d.word = byte_reverse(work_resp_i.data);
s_regs_d.state = EngineIvWrite;
end
EngineIvWrite: begin
work_req_o = mem_write(10'd84 + 10'(s_regs_q.word_index), s_regs_q.word);
s_regs_d.word = '0;
s_regs_d.word_index = s_regs_q.word_index + 1'b1;
s_regs_d.state = s_regs_q.word_index == 2'd3 ? EngineInput : EngineIvRead;
end
EngineInput:
if (!s_input_empty) begin
s_regs_d.word = s_assembled_word;
s_regs_d.input_index = s_regs_q.input_index + 1'b1;
if ((s_regs_q.input_index[1:0] == 2'd3) ||
(s_input_data[36] && (3'(s_regs_q.lane) == (s_head_bytes - 1'b1)))) begin
work_req_o = mem_write(10'd88 + 10'(s_regs_q.input_index[3:2]), s_assembled_word);
s_regs_d.word = '0;
end
if (3'(s_regs_q.lane) == (s_head_bytes - 1'b1)) begin
s_input_pop = 1'b1;
s_regs_d.lane = '0;
if (s_input_data[36]) begin
s_regs_d.last_block = 1'b1;
s_regs_d.block_bytes = s_regs_q.input_index + 1'b1;
s_regs_d.word_index = s_regs_q.input_index[3:2] + 1'b1;
s_regs_d.state = s_regs_q.input_index[3:2] == 2'd3 ? EngineStart : EnginePad;
end
end else s_regs_d.lane = s_regs_q.lane + 1'b1;
if (s_regs_q.input_index == 5'd15) begin
s_regs_d.block_bytes = 5'd16;
s_regs_d.state = EngineStart;
end
end
EnginePad: begin
work_req_o = mem_write(10'd88 + 10'(s_regs_q.word_index), 32'd0);
s_regs_d.word_index = s_regs_q.word_index + 1'b1;
if (s_regs_q.word_index == 2'd3) s_regs_d.state = EngineStart;
end
EngineStart: s_regs_d.state = EngineCore;
EngineCore:
if (s_core_done) begin
s_regs_d.word_index = '0;
s_regs_d.state = EngineOutputRead;
end
EngineOutputRead: begin
work_req_o = mem_read(10'd92 + 10'(s_regs_q.word_index));
s_regs_d.state = EngineOutputWait;
end
EngineOutputWait:
if (work_resp_i.valid) begin
s_regs_d.word = work_resp_i.data;
s_regs_d.state = EngineOutputPush;
end
EngineOutputPush:
if (!s_output_full) begin
s_output_push = 1'b1;
if (({1'b0, s_regs_q.word_index, 2'b00} + 5'd4) >= s_regs_q.block_bytes) begin
s_regs_d.word = '0;
s_regs_d.input_index = '0;
s_regs_d.state = s_regs_q.last_block ? EngineLast : EngineInput;
end else begin
s_regs_d.word_index = s_regs_q.word_index + 1'b1;
s_regs_d.state = EngineOutputRead;
end
end
EngineLast: begin
end
EngineDrain: if (s_output_pop || !output_valid_o) s_regs_d.state = EngineClear;
EngineClear: begin
s_regs_d.word = '0;
s_regs_d.input_index = '0;
s_regs_d.lane = '0;
s_regs_d.bytes_in = '0;
s_regs_d.bytes_out = '0;
s_regs_d.scrub_cycles = '0;
s_regs_d.state = EngineScrub;
end
EngineScrub: begin
work_req_o = s_scrub_req[0];
s_regs_d.scrub_cycles = s_regs_q.scrub_cycles + 1'b1;
if (s_scrub_done && !fifo_busy_o) s_regs_d.state = EngineIdle;
if (s_regs_q.scrub_cycles >= 12'd2055) s_regs_d.scrub_timeout = 1'b1;
end
EngineFaultDrain, EngineFault: begin
// Fatal handling below owns these states until coordinated reset.
end
default: s_regs_d.state = EngineClear;
endcase
if ((abort_i ||
(s_input_push && ((s_input_bytes == 0) || (s_next_bytes > {1'b0, s_regs_q.length}) ||
(input_last_i != (s_next_bytes == {1'b0, s_regs_q.length}))))) &&
(s_regs_q.state < EngineDrain)) begin
s_regs_d.state = output_valid_o && !output_ready_i ? EngineDrain : EngineClear;
s_regs_d.error = !abort_i;
s_regs_d.done = 1'b0;
work_req_o = '0;
s_input_pop = 1'b0;
s_output_push = 1'b0;
end
if (fault_i) begin
// Revoke scalar/key state immediately. Keep only the already-presented
// FIFO head until its handshake; a fatal fault cannot retract AXIS valid.
s_regs_d = '0;
s_regs_d.state = output_valid_o && !output_ready_i ? EngineFaultDrain : EngineFault;
work_req_o = '0;
s_input_pop = 1'b0;
s_output_push = 1'b0;
end
if (zeroize_i) begin
s_regs_d = '0;
work_req_o = '0;
s_input_pop = 1'b0;
s_output_push = 1'b0;
end
end
endmodule