diff --git a/crt/include/retrosoc/hal/apu_regs.h b/crt/include/retrosoc/hal/apu_regs.h index 965db8e4..2b7176fe 100644 --- a/crt/include/retrosoc/hal/apu_regs.h +++ b/crt/include/retrosoc/hal/apu_regs.h @@ -287,6 +287,15 @@ #define RS_APU_ABI_APUMC_ENTRY_WORDS 8U #define RS_APU_ABI_APUMC_ENTRY_COUNT 3U #define RS_APU_ABI_APUMC_MAX_INSTRUCTIONS 2048U +#define RS_APU_ABI_APUMC_P3_PRIMITIVE_MASK UINT32_C(0x00000000) +#define RS_APU_ABI_APUMC_P4_PRIMITIVE_MASK UINT32_C(0x0000FFFF) +#define RS_APU_ABI_LOCAL_DATA_BYTES 114688U +#define RS_APU_ABI_LOCAL_CODEC_BYTES 40960U +#define RS_APU_ABI_LOCAL_TABLE_SCRATCH_BYTES 24576U +#define RS_APU_ABI_LOCAL_INPUT_BASE 24576U +#define RS_APU_ABI_LOCAL_OUTPUT_BASE 32768U +#define RS_APU_ABI_LOCAL_KWS_BASE 40960U +#define RS_APU_ABI_LOCAL_INTERNAL_BASE 106496U #define RS_APU_ABI_APUMC_HEADER_MAGIC 0U #define RS_APU_ABI_APUMC_HEADER_ABI 1U #define RS_APU_ABI_APUMC_HEADER_TOTAL_BYTES 2U @@ -439,8 +448,8 @@ #define RS_APU_IP_ID_VALUE UINT32_C(0x41505530) #define RS_APU_IP_VERSION_VALUE UINT32_C(0x00010000) #define RS_APU_IP_VERSION_MAJOR_MASK UINT32_C(0xFFFF0000) -#define RS_APU_CAPABILITY0_IMPLEMENTED UINT32_C(0x00000098) -#define RS_APU_CAPABILITY1_IMPLEMENTED UINT32_C(0x00000010) +#define RS_APU_CAPABILITY0_IMPLEMENTED UINT32_C(0x00000198) +#define RS_APU_CAPABILITY1_IMPLEMENTED UINT32_C(0x01827010) #define RS_APU_DIGEST_IMPLEMENTED UINT32_C(0x00000000) #define RS_APU_IRQ_ALL UINT32_C(0x000007FF) diff --git a/rtl/README.md b/rtl/README.md index 4c39aa1d..673d6bf8 100644 --- a/rtl/README.md +++ b/rtl/README.md @@ -119,9 +119,10 @@ ownership. Its implemented limits, handwritten ABI, measured performance, and commercial release gates are documented in [Baseline JPEG Codec](../docs/ip/jpeg.md). -The Mini Audio Processing Unit has a frozen coreless architecture. APU-P2 adds -the private AXI4 DMA, descriptor scheduler, fair Gateway A arbitration, and -production stream router/FIFOs to the fail-closed APB4 shell at `APB4_APU`. +The Mini Audio Processing Unit has a frozen coreless architecture. APU-P4 adds +the private AXI4 DMA and scheduler, microcode loader/sequencer, 112 KiB local +store, primitive FIFOs, and class-2 through class-5 bitstream, entropy, local, +and fixed-point DSP engines to the fail-closed APB4 shell at `APB4_APU`. Resource Controller index 7 and exclusive LP IRQ31/HP PLIC source10 routing remain fixed. Public jobs and APU stream routes remain disabled until their later-phase engines exist. The complete ABI, phase order, and evidence gates diff --git a/rtl/ip/multimedia/apb4_apu.sv b/rtl/ip/multimedia/apb4_apu.sv index b546568b..2b3c3b03 100644 --- a/rtl/ip/multimedia/apb4_apu.sv +++ b/rtl/ip/multimedia/apb4_apu.sv @@ -47,11 +47,15 @@ module apb4_apu ( logic [2:0][10:0] s_mc_entry_pc, s_mc_entry_first, s_mc_entry_last; logic [2:0][15:0] s_mc_entry_max_loop; logic [2:0][23:0] s_mc_entry_max_retired; + logic [2:0][16:0] s_mc_entry_scratch_base, s_mc_entry_scratch_bytes; + logic [2:0][31:0] s_mc_entry_primitive_mask; + logic [2:0][15:0] s_mc_entry_table_offset, s_mc_entry_table_bytes; + logic [15:0] s_mc_table_bytes; logic s_store_active, s_store_read, s_store_write, s_store_valid; logic [10:0] s_store_addr; logic [63:0] s_store_write_data, s_store_read_data; logic s_seq_fetch, s_seq_fetch_valid, s_seq_trapped, s_seq_trap_event; - logic s_seq_abort_done, s_seq_fault_valid, s_seq_idle; + logic s_seq_abort_done, s_seq_fault_valid, s_seq_launch_epoch, s_seq_idle; logic [10:0] s_seq_fetch_addr; logic [63:0] s_seq_fetch_data, s_seq_perf_retired; logic [31:0] s_seq_status, s_seq_retired; @@ -61,6 +65,35 @@ module apb4_apu ( logic [ 1:0] s_seq_fault_resp; logic [ 7:0] s_seq_fault_index; logic [31:0] s_seq_fault_addr, s_seq_fault_detail; + logic s_primitive_req_valid, s_primitive_req_ready, s_primitive_result_valid; + logic [63:0] s_primitive_instruction; + logic [31:0] s_primitive_source0, s_primitive_source1, s_primitive_destination; + logic [3:0] s_primitive_result_dst; + logic [3:0][31:0] s_primitive_result_data; + logic [2:0] s_primitive_result_words; + logic s_primitive_result_kernel, s_primitive_err, s_primitive_kernel_done; + logic [5:0] s_primitive_err_code; + logic [3:0] s_primitive_err_stage; + logic [7:0] s_primitive_err_reason; + logic [16:0] s_active_scratch_base, s_active_scratch_bytes; + logic [31:0] s_active_primitive_mask; + logic [15:0] s_active_table_offset, s_active_table_bytes; + logic s_primitive_busy, s_primitive_input_exhausted; + logic s_primitive_input_ready, s_primitive_output_ready; + logic s_primitive_input_accept_unused, s_primitive_output_valid_unused; + logic [6:0] s_primitive_input_count_unused, s_primitive_output_count_unused; + logic s_primitive_kernel_busy_unused; + logic [31:0] s_primitive_cycles_unused; + logic [40:0] s_primitive_output_data_unused; + logic s_local_loader_write, s_local_loader_ready; + logic [16:0] s_local_loader_addr; + logic [31:0] s_local_loader_data; + logic [ 3:0] s_local_loader_strb; + logic s_local_codec_req, s_local_codec_write, s_local_codec_ready; + logic [16:0] s_local_codec_addr; + logic [31:0] s_local_codec_write_data, s_local_codec_read_data; + logic [3:0] s_local_codec_strb; + logic s_local_codec_valid, s_local_codec_err; logic s_input_watermark_evt, s_output_watermark_evt, s_xrun_evt; logic s_ring_event, s_ring_err, s_abort_done; logic [ 5:0] s_ring_err_code; @@ -164,7 +197,7 @@ module apb4_apu ( assign s_tx_route_apu = s_stream_route[1:0] == 2'd1; assign s_rx_route_apu = s_stream_route[3:2] == 2'd1; assign s_transport_idle = s_scheduler_idle && !s_dma_busy && s_stream_idle && s_mc_idle && - s_seq_idle; + s_seq_idle && !s_primitive_busy; assign idle_o = s_transport_idle; assign s_resource_reset_request = s_resource_reset_pending_q || (resource_reset_i && !s_resource_reset_seen_q); @@ -323,59 +356,71 @@ module apb4_apu ( .axi4 (axi4) ); - apu_microcode_loader u_microcode_loader ( - .clk_i (clk_i), - .rst_n_i (rst_n_i), - .start_i (s_microcode_load), - .abort_i (s_abort), - .resource_reset_i (s_resource_reset_request), - .soft_reset_i (s_soft_reset), - .counter_clear_i (s_cnt_clear), - .image_addr_i (s_mc_image_addr), - .image_size_i (s_mc_image_size), - .expected_crc_i (s_mc_expected_crc), - .dma_request_valid_o(s_mc_dma_req_valid), - .dma_request_ready_i(s_mc_dma_req_ready), - .dma_request_addr_o (s_mc_dma_req_addr), - .dma_request_bytes_o(s_mc_dma_req_bytes), - .dma_data_i (u_dma_read_axis.tdata), - .dma_keep_i (u_dma_read_axis.tkeep), - .dma_last_i (u_dma_read_axis.tlast), - .dma_valid_i (u_dma_read_axis.tvalid && !s_mc_idle), - .dma_ready_o (s_mc_dma_ready), - .dma_done_i (s_dma_done), - .dma_err_i (s_dma_err), - .dma_err_code_i (s_dma_err_code), - .dma_err_stage_i (s_dma_err_stage), - .dma_err_resp_i (s_dma_err_resp), - .dma_err_addr_i (s_dma_err_addr), - .store_active_o (s_store_active), - .store_read_o (s_store_read), - .store_write_o (s_store_write), - .store_addr_o (s_store_addr), - .store_data_o (s_store_write_data), - .store_data_i (s_store_read_data), - .store_valid_i (s_store_valid), - .stat_o (s_mc_status), - .abi_o (s_mc_abi), - .build_id_o (s_mc_build_id), - .lock_o (s_mc_lock), - .actual_crc_o (s_mc_actual_crc), - .load_count_o (s_mc_load_count), - .entry_pc_o (s_mc_entry_pc), - .entry_first_o (s_mc_entry_first), - .entry_last_o (s_mc_entry_last), - .entry_max_loop_o (s_mc_entry_max_loop), - .entry_max_retired_o(s_mc_entry_max_retired), - .load_done_o (s_mc_load_done), - .abort_done_o (s_mc_abort_done), - .fault_valid_o (s_mc_fault_valid), - .fault_code_o (s_mc_fault_code), - .fault_stage_o (s_mc_fault_stage), - .fault_resp_o (s_mc_fault_resp), - .fault_addr_o (s_mc_fault_addr), - .fault_detail_o (s_mc_fault_detail), - .idle_o (s_mc_idle) + apu_microcode_loader #( + .EnableP4(1'b1) + ) u_microcode_loader ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .start_i (s_microcode_load), + .abort_i (s_abort), + .resource_reset_i (s_resource_reset_request), + .soft_reset_i (s_soft_reset), + .counter_clear_i (s_cnt_clear), + .image_addr_i (s_mc_image_addr), + .image_size_i (s_mc_image_size), + .expected_crc_i (s_mc_expected_crc), + .dma_request_valid_o (s_mc_dma_req_valid), + .dma_request_ready_i (s_mc_dma_req_ready), + .dma_request_addr_o (s_mc_dma_req_addr), + .dma_request_bytes_o (s_mc_dma_req_bytes), + .dma_data_i (u_dma_read_axis.tdata), + .dma_keep_i (u_dma_read_axis.tkeep), + .dma_last_i (u_dma_read_axis.tlast), + .dma_valid_i (u_dma_read_axis.tvalid && !s_mc_idle), + .dma_ready_o (s_mc_dma_ready), + .dma_done_i (s_dma_done), + .dma_err_i (s_dma_err), + .dma_err_code_i (s_dma_err_code), + .dma_err_stage_i (s_dma_err_stage), + .dma_err_resp_i (s_dma_err_resp), + .dma_err_addr_i (s_dma_err_addr), + .store_active_o (s_store_active), + .store_read_o (s_store_read), + .store_write_o (s_store_write), + .store_addr_o (s_store_addr), + .store_data_o (s_store_write_data), + .store_data_i (s_store_read_data), + .store_valid_i (s_store_valid), + .local_write_o (s_local_loader_write), + .local_addr_o (s_local_loader_addr), + .local_data_o (s_local_loader_data), + .local_strb_o (s_local_loader_strb), + .table_bytes_o (s_mc_table_bytes), + .stat_o (s_mc_status), + .abi_o (s_mc_abi), + .build_id_o (s_mc_build_id), + .lock_o (s_mc_lock), + .actual_crc_o (s_mc_actual_crc), + .load_count_o (s_mc_load_count), + .entry_pc_o (s_mc_entry_pc), + .entry_first_o (s_mc_entry_first), + .entry_last_o (s_mc_entry_last), + .entry_max_loop_o (s_mc_entry_max_loop), + .entry_max_retired_o (s_mc_entry_max_retired), + .entry_scratch_base_o (s_mc_entry_scratch_base), + .entry_scratch_bytes_o (s_mc_entry_scratch_bytes), + .entry_primitive_mask_o(s_mc_entry_primitive_mask), + .entry_table_offset_o (s_mc_entry_table_offset), + .entry_table_bytes_o (s_mc_entry_table_bytes), + .load_done_o (s_mc_load_done), + .abort_done_o (s_mc_abort_done), + .fault_valid_o (s_mc_fault_valid), + .fault_code_o (s_mc_fault_code), + .fault_stage_o (s_mc_fault_stage), + .fault_resp_o (s_mc_fault_resp), + .fault_addr_o (s_mc_fault_addr), + .fault_detail_o (s_mc_fault_detail), + .idle_o (s_mc_idle) ); apu_control_store u_control_store ( @@ -395,54 +440,153 @@ module apb4_apu ( .fetch_valid_o (s_seq_fetch_valid) ); - apu_codec_sequencer u_codec_sequencer ( - .clk_i (clk_i), - .rst_n_i (rst_n_i), - .soft_reset_i (s_soft_reset), - .resource_reset_i (s_resource_reset_apply_q), - .counter_clear_i (s_cnt_clear), - .abort_i (s_abort), - .launch_i (1'b0), - .launch_entry_i (2'd0), - .image_valid_i (s_mc_status[`APB4_APU__MC_STATUS_VALID]), - .timeout_i (s_sequencer_timeout), - .entry_pc_i (s_mc_entry_pc), - .entry_first_i (s_mc_entry_first), - .entry_last_i (s_mc_entry_last), - .entry_max_loop_i (s_mc_entry_max_loop), - .entry_max_retired_i (s_mc_entry_max_retired), - .input_exhausted_i (1'b1), - .input_ready_i (1'b0), - .output_ready_i (1'b1), - .kernel_done_i (1'b0), - .transport_idle_success_i(!s_dma_busy && !s_dma_err), - .stall_i (1'b0), - .cause_valid_i (1'b0), - .cause_code_i (6'd0), - .cause_stage_i (4'd0), - .cause_resp_i (2'd0), - .cause_index_i (8'd0), - .cause_addr_i (32'd0), - .cause_detail_i (32'd0), - .fetch_o (s_seq_fetch), - .fetch_addr_o (s_seq_fetch_addr), - .fetch_data_i (s_seq_fetch_data), - .fetch_valid_i (s_seq_fetch_valid), - .stat_o (s_seq_status), - .retired_o (s_seq_retired), - .gpr_o (s_seq_gpr), - .trapped_o (s_seq_trapped), - .trap_event_o (s_seq_trap_event), - .abort_done_o (s_seq_abort_done), - .fault_valid_o (s_seq_fault_valid), - .fault_code_o (s_seq_fault_code), - .fault_stage_o (s_seq_fault_stage), - .fault_resp_o (s_seq_fault_resp), - .fault_index_o (s_seq_fault_index), - .fault_addr_o (s_seq_fault_addr), - .fault_detail_o (s_seq_fault_detail), - .perf_retired_o (s_seq_perf_retired), - .idle_o (s_seq_idle) + apu_local_sram u_local_sram ( + .clk_i(clk_i), + .rst_n_i(rst_n_i), + .image_valid_i(s_mc_status[`APB4_APU__MC_STATUS_VALID]), + .table_bytes_i(s_mc_table_bytes), + .epoch_clear_i(s_seq_launch_epoch || s_soft_reset || s_resource_reset_apply_q || s_abort), + .loader_active_i(!s_mc_idle), + .loader_req_i(s_local_loader_write), + .loader_addr_i(s_local_loader_addr), + .loader_data_i(s_local_loader_data), + .loader_strb_i(s_local_loader_strb), + .loader_ready_o(s_local_loader_ready), + .codec_req_i(s_local_codec_req), + .codec_write_i(s_local_codec_write), + .codec_addr_i(s_local_codec_addr), + .codec_data_i(s_local_codec_write_data), + .codec_strb_i(s_local_codec_strb), + .codec_ready_o(s_local_codec_ready), + .codec_data_o(s_local_codec_read_data), + .codec_valid_o(s_local_codec_valid), + .codec_access_err_o(s_local_codec_err) + ); + + apu_primitive_dispatcher u_primitive_dispatcher ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .flush_i (s_seq_launch_epoch || s_soft_reset || s_resource_reset_apply_q || s_abort), + .req_valid_i (s_primitive_req_valid), + .req_ready_o (s_primitive_req_ready), + .instruction_i (s_primitive_instruction), + .source0_i (s_primitive_source0), + .source1_i (s_primitive_source1), + .destination_i (s_primitive_destination), + .scratch_base_i (s_active_scratch_base), + .scratch_bytes_i (s_active_scratch_bytes), + .table_offset_i (s_active_table_offset), + .table_bytes_i (s_active_table_bytes), + .result_valid_o (s_primitive_result_valid), + .result_dst_o (s_primitive_result_dst), + .result_data_o (s_primitive_result_data), + .result_words_o (s_primitive_result_words), + .result_kernel_o (s_primitive_result_kernel), + .error_o (s_primitive_err), + .error_code_o (s_primitive_err_code), + .error_stage_o (s_primitive_err_stage), + .error_reason_o (s_primitive_err_reason), + .cycles_o (s_primitive_cycles_unused), + .kernel_done_o (s_primitive_kernel_done), + .busy_o (s_primitive_busy), + .input_exhausted_o(s_primitive_input_exhausted), + .input_ready_o (s_primitive_input_ready), + .output_ready_o (s_primitive_output_ready), + .input_count_o (s_primitive_input_count_unused), + .output_count_o (s_primitive_output_count_unused), + .kernel_busy_o (s_primitive_kernel_busy_unused), + .memory_req_o (s_local_codec_req), + .memory_write_o (s_local_codec_write), + .memory_addr_o (s_local_codec_addr), + .memory_data_o (s_local_codec_write_data), + .memory_strb_o (s_local_codec_strb), + .memory_valid_i (s_local_codec_valid), + .memory_data_i (s_local_codec_read_data), + .memory_error_i (s_local_codec_err), + .input_valid_i (1'b0), + .input_data_i (41'd0), + .input_accept_o (s_primitive_input_accept_unused), + .output_valid_o (s_primitive_output_valid_unused), + .output_data_o (s_primitive_output_data_unused), + .output_accept_i (1'b0) + ); + + apu_codec_sequencer #( + .EnableP4(1'b1) + ) u_codec_sequencer ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .soft_reset_i (s_soft_reset), + .resource_reset_i (s_resource_reset_apply_q), + .counter_clear_i (s_cnt_clear), + .abort_i (s_abort), + .launch_i (1'b0), + .launch_entry_i (2'd0), + .image_valid_i (s_mc_status[`APB4_APU__MC_STATUS_VALID]), + .timeout_i (s_sequencer_timeout), + .entry_pc_i (s_mc_entry_pc), + .entry_first_i (s_mc_entry_first), + .entry_last_i (s_mc_entry_last), + .entry_max_loop_i (s_mc_entry_max_loop), + .entry_max_retired_i (s_mc_entry_max_retired), + .entry_scratch_base_i (s_mc_entry_scratch_base), + .entry_scratch_bytes_i (s_mc_entry_scratch_bytes), + .entry_primitive_mask_i (s_mc_entry_primitive_mask), + .entry_table_offset_i (s_mc_entry_table_offset), + .entry_table_bytes_i (s_mc_entry_table_bytes), + .input_exhausted_i (s_primitive_input_exhausted), + .input_ready_i (s_primitive_input_ready), + .output_ready_i (s_primitive_output_ready), + .kernel_done_i (s_primitive_kernel_done), + .transport_idle_success_i (!s_dma_busy && !s_dma_err), + .stall_i (1'b0), + .cause_valid_i (1'b0), + .cause_code_i (6'd0), + .cause_stage_i (4'd0), + .cause_resp_i (2'd0), + .cause_index_i (8'd0), + .cause_addr_i (32'd0), + .cause_detail_i (32'd0), + .primitive_req_valid_o (s_primitive_req_valid), + .primitive_req_ready_i (s_primitive_req_ready), + .primitive_instruction_o (s_primitive_instruction), + .primitive_source0_o (s_primitive_source0), + .primitive_source1_o (s_primitive_source1), + .primitive_destination_o (s_primitive_destination), + .primitive_result_valid_i (s_primitive_result_valid), + .primitive_result_dst_i (s_primitive_result_dst), + .primitive_result_data_i (s_primitive_result_data), + .primitive_result_words_i (s_primitive_result_words), + .primitive_result_kernel_i(s_primitive_result_kernel), + .primitive_error_i (s_primitive_err), + .primitive_error_code_i (s_primitive_err_code), + .primitive_error_stage_i (s_primitive_err_stage), + .primitive_error_reason_i (s_primitive_err_reason), + .fetch_o (s_seq_fetch), + .fetch_addr_o (s_seq_fetch_addr), + .fetch_data_i (s_seq_fetch_data), + .fetch_valid_i (s_seq_fetch_valid), + .stat_o (s_seq_status), + .retired_o (s_seq_retired), + .gpr_o (s_seq_gpr), + .trapped_o (s_seq_trapped), + .trap_event_o (s_seq_trap_event), + .abort_done_o (s_seq_abort_done), + .fault_valid_o (s_seq_fault_valid), + .fault_code_o (s_seq_fault_code), + .fault_stage_o (s_seq_fault_stage), + .fault_resp_o (s_seq_fault_resp), + .fault_index_o (s_seq_fault_index), + .fault_addr_o (s_seq_fault_addr), + .fault_detail_o (s_seq_fault_detail), + .perf_retired_o (s_seq_perf_retired), + .active_scratch_base_o (s_active_scratch_base), + .active_scratch_bytes_o (s_active_scratch_bytes), + .active_primitive_mask_o (s_active_primitive_mask), + .active_table_offset_o (s_active_table_offset), + .active_table_bytes_o (s_active_table_bytes), + .launch_epoch_o (s_seq_launch_epoch), + .idle_o (s_seq_idle) ); apu_ring_scheduler u_ring_scheduler ( @@ -576,10 +720,14 @@ module apb4_apu ( end end - logic s_unused_pending; + logic s_unused_pending, s_unused_p3, s_unused_p4; assign s_unused_pending = s_dma_input_pending ^ s_dma_output_pending ^ s_dma_aborted ^ s_reg_idle_unused ^ s_backend_job_valid_unused ^ s_backend_resp_ready_unused ^ ^s_backend_descriptor_unused ^ ^s_backend_index_unused ^ ^s_ring_coalesce[15:8]; - logic s_unused_p3; assign s_unused_p3 = ^s_seq_gpr; + assign s_unused_p4 = ^s_active_primitive_mask ^ s_local_loader_ready ^ s_local_codec_ready ^ + s_primitive_input_accept_unused ^ s_primitive_output_valid_unused ^ + ^s_primitive_cycles_unused ^ ^s_primitive_output_data_unused ^ + ^s_primitive_input_count_unused ^ ^s_primitive_output_count_unused ^ + s_primitive_kernel_busy_unused; endmodule diff --git a/rtl/ip/multimedia/apu_bitstream_engine.sv b/rtl/ip/multimedia/apu_bitstream_engine.sv new file mode 100644 index 00000000..f706bda4 --- /dev/null +++ b/rtl/ip/multimedia/apu_bitstream_engine.sv @@ -0,0 +1,210 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +module apu_bitstream_engine ( + // verilog_format: off -- preserve request and FIFO columns + input logic clk_i, + input logic rst_n_i, + input logic flush_i, + input logic req_valid_i, + output logic req_ready_o, + input logic [ 2:0] req_op_i, + input logic [ 5:0] req_width_i, + output logic result_valid_o, + output logic [31:0] result_data_o, + output logic [ 6:0] available_bits_o, + output logic eof_o, + output logic error_o, + input logic fifo_empty_i, + input logic [40:0] fifo_data_i, + output logic fifo_pop_o + // verilog_format: on +); + typedef enum logic [1:0] { + Idle, + Refill, + Check, + Execute + } state_e; + + state_e s_state_q; + logic [127:0] s_bits_q; + logic [ 6:0] s_count_q; + logic [ 2:0] s_op_q; + logic [ 5:0] s_width_q; + logic [ 2:0] s_bit_position_q; + logic s_eof_q; + logic [ 6:0] s_append_bits; + logic [127:0] s_append_value; + logic [ 31:0] s_peek_value; + logic [ 5:0] s_effective_width; + logic [ 31:0] s_req_peek_value; + logic [ 5:0] s_req_effective_width; + logic [127:0] s_req_buffer; + logic [ 6:0] s_req_buffer_count; + + function automatic logic [7:0] reverse_byte(input logic [7:0] value_i); + logic [7:0] s_value; + begin + for (int bit_index = 0; bit_index < 8; bit_index++) begin + s_value[bit_index] = value_i[7-bit_index]; + end + return s_value; + end + endfunction + + always_comb begin + s_append_bits = 7'(fifo_data_i[34:32] * 8); + s_append_value = 128'd0; + for (int byte_index = 0; byte_index < 4; byte_index++) begin + if (byte_index < fifo_data_i[34:32]) begin + s_append_value[(byte_index*8)+:8] = reverse_byte(fifo_data_i[(byte_index*8)+:8]); + end + end + s_peek_value = 32'd0; + s_req_peek_value = 32'd0; + s_req_buffer = s_bits_q | (s_append_value << s_count_q); + s_req_buffer_count = s_count_q + s_append_bits; + for (int bit_index = 0; bit_index < 32; bit_index++) begin + if (bit_index < s_width_q) begin + s_peek_value[32'(s_width_q)-1-bit_index] = s_bits_q[bit_index]; + end + if (bit_index < req_width_i) begin + s_req_peek_value[32'(req_width_i)-1-bit_index] = s_req_buffer[bit_index]; + end + end + s_effective_width = (s_op_q == 3'd4) ? {3'd0, ((3'd0 - s_bit_position_q) & 3'd7)} : s_width_q; + s_req_effective_width = (req_op_i == 3'd4) ? + {3'd0, ((3'd0 - s_bit_position_q) & 3'd7)} : req_width_i; + end + + assign req_ready_o = s_state_q == Idle; + assign available_bits_o = s_count_q; + assign eof_o = s_eof_q && fifo_empty_i && (s_count_q == 7'd0); + assign fifo_pop_o = ((s_state_q == Refill) && !fifo_empty_i) || + ((s_state_q == Idle) && req_valid_i && req_ready_o && + (req_op_i != 3'd4) && (s_count_q < {1'b0, req_width_i}) && !fifo_empty_i); + + always_ff @(posedge clk_i or negedge rst_n_i) begin + if (!rst_n_i) begin + s_state_q <= Idle; + s_bits_q <= '0; + s_count_q <= 7'd0; + s_op_q <= 3'd0; + s_width_q <= 6'd0; + s_bit_position_q <= 3'd0; + s_eof_q <= 1'b0; + result_valid_o <= 1'b0; + result_data_o <= 32'd0; + error_o <= 1'b0; + end else if (flush_i) begin + s_state_q <= Idle; + s_bits_q <= '0; + s_count_q <= 7'd0; + s_bit_position_q <= 3'd0; + s_eof_q <= 1'b0; + result_valid_o <= 1'b0; + result_data_o <= 32'd0; + error_o <= 1'b0; + end else begin + result_valid_o <= 1'b0; + error_o <= 1'b0; + unique case (s_state_q) + Idle: begin + if (req_valid_i) begin + s_op_q <= req_op_i; + s_width_q <= req_width_i; + if ((req_op_i == 3'd4) || (s_count_q >= {1'b0, req_width_i})) begin + result_data_o <= (req_op_i == 3'd0) ? {25'd0, s_count_q} : s_req_peek_value; + if (req_op_i inside {3'd2, 3'd3, 3'd4}) begin + s_bits_q <= s_bits_q >> s_req_effective_width; + s_count_q <= s_count_q - s_req_effective_width; + s_bit_position_q <= s_bit_position_q + s_req_effective_width[2:0]; + end + result_valid_o <= 1'b1; + s_state_q <= Idle; + end else if (!fifo_empty_i) begin + s_bits_q <= s_req_buffer; + s_count_q <= s_req_buffer_count; + if (fifo_data_i[40]) s_eof_q <= 1'b1; + if (fifo_data_i[40] && (req_op_i != 3'd0) && + (s_req_buffer_count < {1'b0, req_width_i})) begin + error_o <= 1'b1; + result_valid_o <= 1'b1; + s_state_q <= Idle; + end else if ((req_op_i != 3'd0) && (s_req_buffer_count >= {1'b0, req_width_i})) begin + result_data_o <= s_req_peek_value; + if (req_op_i inside {3'd2, 3'd3}) begin + s_bits_q <= s_req_buffer >> req_width_i; + s_count_q <= s_req_buffer_count - req_width_i; + s_bit_position_q <= s_bit_position_q + req_width_i[2:0]; + end + result_valid_o <= 1'b1; + s_state_q <= Idle; + end else begin + s_state_q <= Check; + end + end else begin + s_state_q <= Refill; + end + end + end + Refill: begin + if (!fifo_empty_i) begin + s_bits_q <= s_bits_q | (s_append_value << s_count_q); + s_count_q <= s_count_q + s_append_bits; + if (fifo_data_i[40]) s_eof_q <= 1'b1; + s_state_q <= Check; + end else if (s_eof_q) begin + if (s_op_q == 3'd0) begin + s_state_q <= Execute; + end else begin + error_o <= 1'b1; + result_valid_o <= 1'b1; + s_state_q <= Idle; + end + end + end + Check: begin + if (s_count_q >= {1'b0, s_width_q}) begin + result_data_o <= (s_op_q == 3'd0) ? {25'd0, s_count_q} : s_peek_value; + if (s_op_q inside {3'd2, 3'd3, 3'd4}) begin + s_bits_q <= s_bits_q >> s_effective_width; + s_count_q <= s_count_q - s_effective_width; + s_bit_position_q <= s_bit_position_q + s_effective_width[2:0]; + end + result_valid_o <= 1'b1; + s_state_q <= Idle; + end else if (s_eof_q && fifo_empty_i) begin + if (s_op_q == 3'd0) begin + result_data_o <= {25'd0, s_count_q}; + result_valid_o <= 1'b1; + s_state_q <= Idle; + end else begin + error_o <= 1'b1; + result_valid_o <= 1'b1; + s_state_q <= Idle; + end + end else begin + s_state_q <= Refill; + end + end + Execute: begin + if ((s_op_q != 3'd0) && (s_count_q < {1'b0, s_effective_width})) begin + s_state_q <= Refill; + end else begin + result_data_o <= (s_op_q == 3'd0) ? {25'd0, s_count_q} : s_peek_value; + if (s_op_q inside {3'd2, 3'd3, 3'd4}) begin + s_bits_q <= s_bits_q >> s_effective_width; + s_count_q <= s_count_q - s_effective_width; + s_bit_position_q <= s_bit_position_q + s_effective_width[2:0]; + end + result_valid_o <= 1'b1; + s_state_q <= Idle; + end + end + default: s_state_q <= Idle; + endcase + end + end +endmodule diff --git a/rtl/ip/multimedia/apu_codec_sequencer.sv b/rtl/ip/multimedia/apu_codec_sequencer.sv index 7bf7860a..effdacad 100644 --- a/rtl/ip/multimedia/apu_codec_sequencer.sv +++ b/rtl/ip/multimedia/apu_codec_sequencer.sv @@ -3,7 +3,9 @@ `include "apu_define.svh" -module apu_codec_sequencer ( +module apu_codec_sequencer #( + parameter bit EnableP4 = 1'b0 +) ( // verilog_format: off -- preserve launch, predicate, store, and result columns input logic clk_i, input logic rst_n_i, @@ -20,6 +22,11 @@ module apu_codec_sequencer ( input logic [2:0][10:0] entry_last_i, input logic [2:0][15:0] entry_max_loop_i, input logic [2:0][23:0] entry_max_retired_i, + input logic [2:0][16:0] entry_scratch_base_i, + input logic [2:0][16:0] entry_scratch_bytes_i, + input logic [2:0][31:0] entry_primitive_mask_i, + input logic [2:0][15:0] entry_table_offset_i, + input logic [2:0][15:0] entry_table_bytes_i, input logic input_exhausted_i, input logic input_ready_i, input logic output_ready_i, @@ -33,6 +40,21 @@ module apu_codec_sequencer ( input logic [7:0] cause_index_i, input logic [31:0] cause_addr_i, input logic [31:0] cause_detail_i, + output logic primitive_req_valid_o, + input logic primitive_req_ready_i, + output logic [63:0] primitive_instruction_o, + output logic [31:0] primitive_source0_o, + output logic [31:0] primitive_source1_o, + output logic [31:0] primitive_destination_o, + input logic primitive_result_valid_i, + input logic [ 3:0] primitive_result_dst_i, + input logic [ 3:0][31:0] primitive_result_data_i, + input logic [ 2:0] primitive_result_words_i, + input logic primitive_result_kernel_i, + input logic primitive_error_i, + input logic [ 5:0] primitive_error_code_i, + input logic [ 3:0] primitive_error_stage_i, + input logic [ 7:0] primitive_error_reason_i, output logic fetch_o, output logic [10:0] fetch_addr_o, input logic [63:0] fetch_data_i, @@ -51,16 +73,23 @@ module apu_codec_sequencer ( output logic [31:0] fault_addr_o, output logic [31:0] fault_detail_o, output logic [63:0] perf_retired_o, + output logic [16:0] active_scratch_base_o, + output logic [16:0] active_scratch_bytes_o, + output logic [31:0] active_primitive_mask_o, + output logic [15:0] active_table_offset_o, + output logic [15:0] active_table_bytes_o, + output logic launch_epoch_o, output logic idle_o // verilog_format: on ); import apu_microcode_pkg::*; - typedef enum logic [1:0] { + typedef enum logic [2:0] { Idle, FetchRequest, FetchWait, - Execute + Execute, + PrimitiveWait } sequencer_state_e; sequencer_state_e s_state_q; @@ -76,12 +105,21 @@ module apu_codec_sequencer ( logic [23:0] s_max_retired_q, s_frame_retired_q; logic [31:0] s_watchdog_q; logic [63:0] s_instruction_q; + logic [16:0] s_scratch_base_q, s_scratch_bytes_q; + logic [31:0] s_primitive_mask_q; + logic [15:0] s_table_offset_q, s_table_bytes_q; + logic s_kernel_pending_q, s_kernel_done_q; + logic [ 3:0] s_kernel_dst_q; + logic s_kernel_err_q; + logic [ 5:0] s_kernel_err_code_q; + logic [ 3:0] s_kernel_err_stage_q; + logic [ 7:0] s_kernel_err_reason_q; logic [31:0] s_stat_q; logic s_trapped_q; logic [63:0] s_perf_retired_q; logic [3:0] s_class, s_opcode, s_predicate, s_dst, s_src0, s_src1; - logic [5:0] s_aux; + logic [7:0] s_aux; logic [31:0] s_immediate, s_source0, s_source1; logic s_predicate_true; logic [11:0] s_forward_target, s_loop_target; @@ -89,27 +127,43 @@ module apu_codec_sequencer ( logic s_trap_now; logic [ 7:0] s_trap_reason; logic [31:0] s_trap_detail; + logic s_pending_dependency, s_wait_stall, s_primitive_stall, s_execute_stall; + logic s_primitive_class; - assign s_class = s_instruction_q[63:60]; - assign s_opcode = s_instruction_q[59:56]; - assign s_predicate = s_instruction_q[55:52]; - assign s_dst = s_instruction_q[51:48]; - assign s_src0 = s_instruction_q[47:44]; - assign s_src1 = s_instruction_q[43:40]; - assign s_aux = s_instruction_q[37:32]; - assign s_immediate = s_instruction_q[31:0]; - assign s_source0 = s_gpr_q[s_src0]; - assign s_source1 = s_gpr_q[s_src1]; + assign s_class = s_instruction_q[63:60]; + assign s_opcode = s_instruction_q[59:56]; + assign s_predicate = s_instruction_q[55:52]; + assign s_dst = s_instruction_q[51:48]; + assign s_src0 = s_instruction_q[47:44]; + assign s_src1 = s_instruction_q[43:40]; + assign s_aux = s_instruction_q[39:32]; + assign s_immediate = s_instruction_q[31:0]; + assign s_source0 = s_gpr_q[s_src0]; + assign s_source1 = s_gpr_q[s_src1]; assign s_forward_target = {1'b0, s_pc_q} + 1'b1 + {1'b0, s_immediate[10:0]}; - assign s_loop_target = {1'b0, s_pc_q} + 1'b1 - {1'b0, s_immediate[10:0]}; - assign fetch_o = s_state_q == FetchRequest; - assign fetch_addr_o = s_pc_q; - assign stat_o = s_stat_q; - assign retired_o = {8'd0, s_frame_retired_q}; - assign gpr_o = s_gpr_q; - assign trapped_o = s_trapped_q; - assign perf_retired_o = s_perf_retired_q; - assign idle_o = s_state_q == Idle; + assign s_loop_target = {1'b0, s_pc_q} + 1'b1 - {1'b0, s_immediate[10:0]}; + assign fetch_o = s_state_q == FetchRequest; + assign fetch_addr_o = s_pc_q; + assign stat_o = s_stat_q; + assign retired_o = {8'd0, s_frame_retired_q}; + assign gpr_o = s_gpr_q; + assign trapped_o = s_trapped_q; + assign perf_retired_o = s_perf_retired_q; + assign idle_o = s_state_q == Idle; + assign active_scratch_base_o = s_scratch_base_q; + assign active_scratch_bytes_o = s_scratch_bytes_q; + assign active_primitive_mask_o = s_primitive_mask_q; + assign active_table_offset_o = s_table_offset_q; + assign active_table_bytes_o = s_table_bytes_q; + assign launch_epoch_o = (s_state_q == Idle) && launch_i && image_valid_i && + (launch_entry_i < 2'd3); + assign primitive_instruction_o = s_instruction_q; + assign primitive_source0_o = s_source0; + assign primitive_source1_o = s_source1; + assign primitive_destination_o = s_gpr_q[s_dst]; + assign s_primitive_class = EnableP4 && (s_class >= 4'd2) && (s_class <= 4'd5); + assign primitive_req_valid_o = (s_state_q == Execute) && s_predicate_true && + s_primitive_class && !s_pending_dependency && !s_kernel_pending_q; always_comb begin unique case (s_predicate) @@ -123,23 +177,87 @@ module apu_codec_sequencer ( 4'd7: s_predicate_true = input_exhausted_i; 4'd8: s_predicate_true = input_ready_i; 4'd9: s_predicate_true = output_ready_i; - 4'd10: s_predicate_true = kernel_done_i; + 4'd10: s_predicate_true = s_kernel_done_q; 4'd11: s_predicate_true = transport_idle_success_i; default: s_predicate_true = 1'b0; endcase end + always_comb begin + s_pending_dependency = 1'b0; + if (s_kernel_pending_q && s_predicate_true) begin + unique case (s_class) + `APB4_APU__MC_CLASS_CONTROL: begin + s_pending_dependency = (s_opcode == `APB4_APU__MC_CONTROL_LOOP_SETUP) && + (s_src0 == s_kernel_dst_q); + end + `APB4_APU__MC_CLASS_SCALAR: begin + unique case (s_opcode) + `APB4_APU__MC_SCALAR_MOV: + s_pending_dependency = (s_src0 == s_kernel_dst_q) || (s_dst == s_kernel_dst_q); + `APB4_APU__MC_SCALAR_MOVI: s_pending_dependency = s_dst == s_kernel_dst_q; + `APB4_APU__MC_SCALAR_CMP: + s_pending_dependency = (s_src0 == s_kernel_dst_q) || (s_src1 == s_kernel_dst_q); + `APB4_APU__MC_SCALAR_SAT: + s_pending_dependency = (s_src0 == s_kernel_dst_q) || (s_dst == s_kernel_dst_q); + default: + s_pending_dependency = (s_src0 == s_kernel_dst_q) || + (s_src1 == s_kernel_dst_q) || (s_dst == s_kernel_dst_q); + endcase + end + `APB4_APU__MC_CLASS_BITSTREAM: begin + s_pending_dependency = (s_dst == s_kernel_dst_q) || + ((s_opcode >= `APB4_APU__MC_BITSTREAM_FRAME_SYNC) && + ((s_src0 == s_kernel_dst_q) || (s_src1 == s_kernel_dst_q))); + end + `APB4_APU__MC_CLASS_ENTROPY, `APB4_APU__MC_CLASS_LOCAL: begin + s_pending_dependency = (s_src0 == s_kernel_dst_q) || + (s_src1 == s_kernel_dst_q) || (s_dst == s_kernel_dst_q) || + ((s_class == `APB4_APU__MC_CLASS_ENTROPY) && + (s_opcode inside {`APB4_APU__MC_ENTROPY_HUFF_PAIR, + `APB4_APU__MC_ENTROPY_HUFF_QUAD}) && + ((s_dst + 1'b1) == s_kernel_dst_q)) || + ((s_class == `APB4_APU__MC_CLASS_ENTROPY) && + (s_opcode == `APB4_APU__MC_ENTROPY_HUFF_QUAD) && + (((s_dst + 4'd2) == s_kernel_dst_q) || + ((s_dst + 4'd3) == s_kernel_dst_q))) || + ((s_class == `APB4_APU__MC_CLASS_LOCAL) && + (s_opcode == `APB4_APU__MC_LOCAL_FIFO_POP) && + ((s_dst + 1'b1) == s_kernel_dst_q)); + end + `APB4_APU__MC_CLASS_KERNEL: s_pending_dependency = 1'b1; + default: s_pending_dependency = 1'b0; + endcase + end + s_wait_stall = EnableP4 && s_predicate_true && + (s_class == `APB4_APU__MC_CLASS_CONTROL) && + (s_opcode == `APB4_APU__MC_CONTROL_WAIT) && + (((s_aux == `APB4_APU__MC_WAIT_KERNEL) && !s_kernel_done_q) || + ((s_aux == `APB4_APU__MC_WAIT_INPUT_FIFO) && !input_ready_i) || + ((s_aux == `APB4_APU__MC_WAIT_OUTPUT_FIFO) && !output_ready_i)); + s_primitive_stall = s_primitive_class && + (s_kernel_pending_q || s_pending_dependency || !primitive_req_ready_i); + s_execute_stall = stall_i || s_wait_stall || s_primitive_stall || + (s_kernel_pending_q && s_predicate_true && + (s_class == `APB4_APU__MC_CLASS_CONTROL) && + (s_opcode == `APB4_APU__MC_CONTROL_END)); + end + always_comb begin s_trap_now = 1'b0; s_trap_reason = 8'd0; s_trap_detail = 32'd0; - if (stall_i && (s_watchdog_q + 1'b1 >= timeout_i)) begin + if (s_execute_stall && (s_watchdog_q + 1'b1 >= timeout_i)) begin s_trap_now = 1'b1; s_trap_reason = 8'd7; end else if (!instruction_encoding_valid(s_instruction_q)) begin s_trap_now = 1'b1; s_trap_reason = 8'd1; - end else if ((s_class >= 4'd2) || ((s_class == 4'd0) && (s_opcode == 4'd8))) begin + end else if ((!EnableP4 && ((s_class >= 4'd2) || + ((s_class == 4'd0) && (s_opcode == 4'd8)))) || + (EnableP4 && ((s_class == 4'd6) || + ((s_class == 4'd0) && (s_opcode == 4'd8) && + !(s_aux inside {8'd1, 8'd2, 8'd3}))))) begin s_trap_now = 1'b1; s_trap_reason = 8'd6; end else if (s_frame_retired_q >= s_max_retired_q) begin @@ -186,6 +304,8 @@ module apu_codec_sequencer ( (s_loop_target[10:0] < s_first_pc_q); s_trap_reason = 8'd4; end + 4'd8: begin + end default: begin s_trap_now = 1'b1; s_trap_reason = 8'd1; @@ -237,7 +357,7 @@ module apu_codec_sequencer ( fault_index_o <= 8'd0; \ fault_addr_o <= {18'd0, s_pc_q, 3'd0}; \ fault_detail_o <= DETAIL; \ - s_stat_q <= {11'd0, |s_loop_active_q, stall_i, s_opcode, s_class, s_pc_q}; \ + s_stat_q <= {11'd0, |s_loop_active_q, s_execute_stall, s_opcode, s_class, s_pc_q}; \ end `define RETROSOC_APU__SET_FETCH_WATCHDOG_TRAP \ @@ -257,75 +377,112 @@ module apu_codec_sequencer ( always_ff @(posedge clk_i or negedge rst_n_i) begin if (!rst_n_i) begin - s_state_q <= Idle; - s_gpr_q <= '0; - s_eq_q <= 1'b0; - s_signed_lt_q <= 1'b0; - s_unsigned_lt_q <= 1'b0; - s_return_pc_q <= '0; - s_return_depth_q <= 3'd0; - s_loop_active_q <= 4'd0; - s_loop_count_q <= '0; - s_loop_start_q <= '0; - s_pc_q <= 11'd0; - s_first_pc_q <= 11'd0; - s_last_pc_q <= 11'd0; - s_max_loop_q <= 16'd0; - s_max_retired_q <= 24'd0; - s_frame_retired_q <= 24'd0; - s_watchdog_q <= 32'd0; - s_instruction_q <= 64'd0; - s_stat_q <= 32'd0; - s_trapped_q <= 1'b0; - s_perf_retired_q <= 64'd0; - trap_event_o <= 1'b0; - abort_done_o <= 1'b0; - fault_valid_o <= 1'b0; - fault_code_o <= 6'd0; - fault_stage_o <= 4'd0; - fault_resp_o <= 2'd0; - fault_index_o <= 8'd0; - fault_addr_o <= 32'd0; - fault_detail_o <= 32'd0; + s_state_q <= Idle; + s_gpr_q <= '0; + s_eq_q <= 1'b0; + s_signed_lt_q <= 1'b0; + s_unsigned_lt_q <= 1'b0; + s_return_pc_q <= '0; + s_return_depth_q <= 3'd0; + s_loop_active_q <= 4'd0; + s_loop_count_q <= '0; + s_loop_start_q <= '0; + s_pc_q <= 11'd0; + s_first_pc_q <= 11'd0; + s_last_pc_q <= 11'd0; + s_max_loop_q <= 16'd0; + s_max_retired_q <= 24'd0; + s_frame_retired_q <= 24'd0; + s_watchdog_q <= 32'd0; + s_instruction_q <= 64'd0; + s_scratch_base_q <= 17'd0; + s_scratch_bytes_q <= 17'd0; + s_primitive_mask_q <= 32'd0; + s_table_offset_q <= 16'd0; + s_table_bytes_q <= 16'd0; + s_kernel_pending_q <= 1'b0; + s_kernel_done_q <= 1'b0; + s_kernel_dst_q <= 4'd0; + s_kernel_err_q <= 1'b0; + s_kernel_err_code_q <= 6'd0; + s_kernel_err_stage_q <= 4'd0; + s_kernel_err_reason_q <= 8'd0; + s_stat_q <= 32'd0; + s_trapped_q <= 1'b0; + s_perf_retired_q <= 64'd0; + trap_event_o <= 1'b0; + abort_done_o <= 1'b0; + fault_valid_o <= 1'b0; + fault_code_o <= 6'd0; + fault_stage_o <= 4'd0; + fault_resp_o <= 2'd0; + fault_index_o <= 8'd0; + fault_addr_o <= 32'd0; + fault_detail_o <= 32'd0; end else begin trap_event_o <= 1'b0; abort_done_o <= 1'b0; fault_valid_o <= 1'b0; + if (primitive_result_valid_i && primitive_result_kernel_i) begin + s_kernel_pending_q <= 1'b0; + s_kernel_done_q <= !primitive_error_i; + if (primitive_error_i) begin + s_kernel_err_q <= 1'b1; + s_kernel_err_code_q <= primitive_error_code_i; + s_kernel_err_stage_q <= primitive_error_stage_i; + s_kernel_err_reason_q <= primitive_error_reason_i; + end else begin + s_gpr_q[primitive_result_dst_i] <= primitive_result_data_i[0]; + end + end else if (kernel_done_i) begin + s_kernel_done_q <= 1'b1; + end if (counter_clear_i) s_perf_retired_q <= 64'd0; if (soft_reset_i || resource_reset_i) begin - s_state_q <= Idle; - s_gpr_q <= '0; - s_eq_q <= 1'b0; - s_signed_lt_q <= 1'b0; - s_unsigned_lt_q <= 1'b0; - s_return_depth_q <= 3'd0; - s_loop_active_q <= 4'd0; - s_pc_q <= 11'd0; - s_frame_retired_q <= 24'd0; - s_watchdog_q <= 32'd0; - s_stat_q <= 32'd0; - s_trapped_q <= 1'b0; - s_perf_retired_q <= 64'd0; + s_state_q <= Idle; + s_gpr_q <= '0; + s_eq_q <= 1'b0; + s_signed_lt_q <= 1'b0; + s_unsigned_lt_q <= 1'b0; + s_return_depth_q <= 3'd0; + s_loop_active_q <= 4'd0; + s_pc_q <= 11'd0; + s_frame_retired_q <= 24'd0; + s_watchdog_q <= 32'd0; + s_stat_q <= 32'd0; + s_trapped_q <= 1'b0; + s_perf_retired_q <= 64'd0; + s_kernel_pending_q <= 1'b0; + s_kernel_done_q <= 1'b0; + s_kernel_err_q <= 1'b0; end else begin unique case (s_state_q) Idle: begin if (launch_i && image_valid_i && (launch_entry_i < 2'd3)) begin - s_state_q <= FetchRequest; - s_gpr_q <= '0; - s_eq_q <= 1'b0; - s_signed_lt_q <= 1'b0; - s_unsigned_lt_q <= 1'b0; - s_return_depth_q <= 3'd0; - s_loop_active_q <= 4'd0; - s_pc_q <= entry_pc_i[launch_entry_i]; - s_first_pc_q <= entry_first_i[launch_entry_i]; - s_last_pc_q <= entry_last_i[launch_entry_i]; - s_max_loop_q <= entry_max_loop_i[launch_entry_i]; - s_max_retired_q <= entry_max_retired_i[launch_entry_i]; - s_frame_retired_q <= 24'd0; - s_watchdog_q <= 32'd0; - s_stat_q <= {21'd0, entry_pc_i[launch_entry_i]}; - s_trapped_q <= 1'b0; + s_state_q <= FetchRequest; + s_gpr_q <= '0; + s_eq_q <= 1'b0; + s_signed_lt_q <= 1'b0; + s_unsigned_lt_q <= 1'b0; + s_return_depth_q <= 3'd0; + s_loop_active_q <= 4'd0; + s_pc_q <= entry_pc_i[launch_entry_i]; + s_first_pc_q <= entry_first_i[launch_entry_i]; + s_last_pc_q <= entry_last_i[launch_entry_i]; + s_max_loop_q <= entry_max_loop_i[launch_entry_i]; + s_max_retired_q <= entry_max_retired_i[launch_entry_i]; + s_scratch_base_q <= entry_scratch_base_i[launch_entry_i]; + s_scratch_bytes_q <= entry_scratch_bytes_i[launch_entry_i]; + s_primitive_mask_q <= entry_primitive_mask_i[launch_entry_i]; + s_table_offset_q <= entry_table_offset_i[launch_entry_i]; + s_table_bytes_q <= entry_table_bytes_i[launch_entry_i]; + s_frame_retired_q <= 24'd0; + s_watchdog_q <= 32'd0; + s_stat_q <= {21'd0, entry_pc_i[launch_entry_i]}; + s_trapped_q <= 1'b0; + s_kernel_pending_q <= 1'b0; + s_kernel_done_q <= 1'b0; + s_kernel_err_q <= 1'b0; end end FetchRequest: begin @@ -333,8 +490,11 @@ module apu_codec_sequencer ( if (s_watchdog_q + 1'b1 >= timeout_i) begin `RETROSOC_APU__SET_FETCH_WATCHDOG_TRAP; end else if (abort_i) begin - s_state_q <= Idle; - abort_done_o <= 1'b1; + s_state_q <= Idle; + s_kernel_pending_q <= 1'b0; + s_kernel_done_q <= 1'b0; + s_kernel_err_q <= 1'b0; + abort_done_o <= 1'b1; end else begin s_watchdog_q <= s_watchdog_q + 1'b1; s_state_q <= FetchWait; @@ -344,8 +504,11 @@ module apu_codec_sequencer ( if (s_watchdog_q + 1'b1 >= timeout_i) begin `RETROSOC_APU__SET_FETCH_WATCHDOG_TRAP; end else if (abort_i) begin - s_state_q <= Idle; - abort_done_o <= 1'b1; + s_state_q <= Idle; + s_kernel_pending_q <= 1'b0; + s_kernel_done_q <= 1'b0; + s_kernel_err_q <= 1'b0; + abort_done_o <= 1'b1; end else begin s_watchdog_q <= s_watchdog_q + 1'b1; if (fetch_valid_i) begin @@ -359,23 +522,50 @@ module apu_codec_sequencer ( end Execute: begin if (cause_valid_i) begin + s_state_q <= Idle; + s_trapped_q <= 1'b1; + trap_event_o <= 1'b1; + fault_valid_o <= 1'b1; + fault_code_o <= cause_code_i; + fault_stage_o <= cause_stage_i; + fault_resp_o <= cause_resp_i; + fault_index_o <= cause_index_i; + fault_addr_o <= cause_addr_i; + fault_detail_o <= cause_detail_i; + s_stat_q <= {11'd0, |s_loop_active_q, s_execute_stall, s_opcode, s_class, s_pc_q}; + end else if (primitive_result_valid_i && primitive_error_i) begin + s_state_q <= Idle; + s_trapped_q <= 1'b1; + trap_event_o <= 1'b1; + fault_valid_o <= 1'b1; + fault_code_o <= primitive_error_code_i; + fault_stage_o <= primitive_error_stage_i; + fault_resp_o <= 2'd0; + fault_index_o <= 8'd0; + fault_addr_o <= {18'd0, s_pc_q, 3'd0}; + fault_detail_o <= trap_detail(primitive_error_reason_i, s_pc_q, s_instruction_q); + s_stat_q <= {11'd0, |s_loop_active_q, s_execute_stall, s_opcode, s_class, s_pc_q}; + end else if (s_kernel_err_q) begin s_state_q <= Idle; s_trapped_q <= 1'b1; trap_event_o <= 1'b1; fault_valid_o <= 1'b1; - fault_code_o <= cause_code_i; - fault_stage_o <= cause_stage_i; - fault_resp_o <= cause_resp_i; - fault_index_o <= cause_index_i; - fault_addr_o <= cause_addr_i; - fault_detail_o <= cause_detail_i; - s_stat_q <= {11'd0, |s_loop_active_q, stall_i, s_opcode, s_class, s_pc_q}; + fault_code_o <= s_kernel_err_code_q; + fault_stage_o <= s_kernel_err_stage_q; + fault_resp_o <= 2'd0; + fault_index_o <= 8'd0; + fault_addr_o <= {18'd0, s_pc_q, 3'd0}; + fault_detail_o <= trap_detail(s_kernel_err_reason_q, s_pc_q, s_instruction_q); + s_kernel_err_q <= 1'b0; end else if (s_trap_now) begin `RETROSOC_APU__SET_TRAP(s_trap_detail); end else if (abort_i) begin - s_state_q <= Idle; - abort_done_o <= 1'b1; - end else if (stall_i) begin + s_state_q <= Idle; + s_kernel_pending_q <= 1'b0; + s_kernel_done_q <= 1'b0; + s_kernel_err_q <= 1'b0; + abort_done_o <= 1'b1; + end else if (s_execute_stall) begin s_stat_q[19] <= 1'b1; if (s_watchdog_q + 1'b1 >= timeout_i) begin `RETROSOC_APU__SET_TRAP(trap_detail(8'd7, s_pc_q, s_instruction_q)); @@ -384,10 +574,19 @@ module apu_codec_sequencer ( end end else if (!instruction_encoding_valid(s_instruction_q)) begin `RETROSOC_APU__SET_TRAP(trap_detail(8'd1, s_pc_q, s_instruction_q)); - end else if ((s_class >= 4'd2) || ((s_class == 4'd0) && (s_opcode == 4'd8))) begin + end else if ((!EnableP4 && ((s_class >= 4'd2) || + ((s_class == 4'd0) && (s_opcode == 4'd8)))) || + (EnableP4 && ((s_class == 4'd6) || + ((s_class == 4'd0) && (s_opcode == 4'd8) && + !(s_aux inside {8'd1, 8'd2, 8'd3}))))) begin `RETROSOC_APU__SET_TRAP(trap_detail(8'd6, s_pc_q, s_instruction_q)); end else if (s_frame_retired_q >= s_max_retired_q) begin `RETROSOC_APU__SET_TRAP(trap_detail(8'd8, s_pc_q, s_instruction_q)); + end else if (s_predicate_true && s_primitive_class && + (s_class != `APB4_APU__MC_CLASS_KERNEL)) begin + s_watchdog_q <= s_watchdog_q + 1'b1; + s_stat_q[19] <= 1'b1; + s_state_q <= PrimitiveWait; end else begin s_watchdog_q <= 32'd0; s_stat_q[19] <= 1'b0; @@ -487,12 +686,35 @@ module apu_codec_sequencer ( s_state_q <= FetchRequest; end end + 4'd8: begin + if ((s_pc_q == s_last_pc_q) || (s_pc_q == 11'd2047)) begin + `RETROSOC_APU__SET_TRAP(trap_detail(8'd2, s_pc_q, s_instruction_q)); + s_frame_retired_q <= s_frame_retired_q; + s_perf_retired_q <= s_perf_retired_q; + end else begin + s_pc_q <= s_pc_q + 1'b1; + s_state_q <= FetchRequest; + end + end default: begin `RETROSOC_APU__SET_TRAP(trap_detail(8'd1, s_pc_q, s_instruction_q)); s_frame_retired_q <= s_frame_retired_q; s_perf_retired_q <= s_perf_retired_q; end endcase + end else if (s_class == `APB4_APU__MC_CLASS_KERNEL) begin + s_kernel_pending_q <= 1'b1; + s_kernel_done_q <= 1'b0; + s_kernel_dst_q <= s_dst; + if ((s_pc_q == s_last_pc_q) || (s_pc_q == 11'd2047)) begin + `RETROSOC_APU__SET_TRAP(trap_detail(8'd2, s_pc_q, s_instruction_q)); + s_frame_retired_q <= s_frame_retired_q; + s_perf_retired_q <= s_perf_retired_q; + s_kernel_pending_q <= 1'b0; + end else begin + s_pc_q <= s_pc_q + 1'b1; + s_state_q <= FetchRequest; + end end else begin unique case (s_opcode) 4'd0: s_gpr_q[s_dst] <= s_source0; @@ -537,6 +759,54 @@ module apu_codec_sequencer ( end end end + PrimitiveWait: begin + s_stat_q[19] <= 1'b1; + if (primitive_result_valid_i) begin + if (primitive_error_i) begin + s_state_q <= Idle; + s_trapped_q <= 1'b1; + trap_event_o <= 1'b1; + fault_valid_o <= 1'b1; + fault_code_o <= primitive_error_code_i; + fault_stage_o <= primitive_error_stage_i; + fault_resp_o <= 2'd0; + fault_index_o <= 8'd0; + fault_addr_o <= {18'd0, s_pc_q, 3'd0}; + fault_detail_o <= trap_detail(primitive_error_reason_i, s_pc_q, s_instruction_q); + s_stat_q <= {11'd0, |s_loop_active_q, 1'b1, s_opcode, s_class, s_pc_q}; + end else begin + for (int index = 0; index < 4; index++) begin + if (index < primitive_result_words_i) begin + s_gpr_q[primitive_result_dst_i+4'(index)] <= primitive_result_data_i[index]; + end + end + s_watchdog_q <= 32'd0; + s_stat_q[19] <= 1'b0; + s_frame_retired_q <= s_frame_retired_q + 1'b1; + if (!counter_clear_i && !(&s_perf_retired_q)) begin + s_perf_retired_q <= s_perf_retired_q + 1'b1; + end + if ((s_pc_q == s_last_pc_q) || (s_pc_q == 11'd2047)) begin + `RETROSOC_APU__SET_TRAP(trap_detail(8'd2, s_pc_q, s_instruction_q)); + s_frame_retired_q <= s_frame_retired_q; + s_perf_retired_q <= s_perf_retired_q; + end else begin + s_pc_q <= s_pc_q + 1'b1; + s_state_q <= FetchRequest; + end + end + end else if (abort_i) begin + s_state_q <= Idle; + s_kernel_pending_q <= 1'b0; + s_kernel_done_q <= 1'b0; + s_kernel_err_q <= 1'b0; + abort_done_o <= 1'b1; + end else if (s_watchdog_q + 1'b1 >= timeout_i) begin + `RETROSOC_APU__SET_TRAP(trap_detail(8'd7, s_pc_q, s_instruction_q)); + end else begin + s_watchdog_q <= s_watchdog_q + 1'b1; + end + end default: s_state_q <= Idle; endcase end diff --git a/rtl/ip/multimedia/apu_define.svh b/rtl/ip/multimedia/apu_define.svh index bbc6471d..b01fdaf0 100644 --- a/rtl/ip/multimedia/apu_define.svh +++ b/rtl/ip/multimedia/apu_define.svh @@ -307,6 +307,15 @@ `define APB4_APU__APUMC_ENTRY_WORDS 8 `define APB4_APU__APUMC_ENTRY_COUNT 3 `define APB4_APU__APUMC_MAX_INSTRUCTIONS 2048 +`define APB4_APU__APUMC_P3_PRIMITIVE_MASK 32'h0000_0000 +`define APB4_APU__APUMC_P4_PRIMITIVE_MASK 32'h0000_ffff +`define APB4_APU__LOCAL_DATA_BYTES 114688 +`define APB4_APU__LOCAL_CODEC_BYTES 40960 +`define APB4_APU__LOCAL_TABLE_SCRATCH_BYTES 24576 +`define APB4_APU__LOCAL_INPUT_BASE 24576 +`define APB4_APU__LOCAL_OUTPUT_BASE 32768 +`define APB4_APU__LOCAL_KWS_BASE 40960 +`define APB4_APU__LOCAL_INTERNAL_BASE 106496 `define APB4_APU__APUMC_HEADER_MAGIC 0 `define APB4_APU__APUMC_HEADER_ABI 1 `define APB4_APU__APUMC_HEADER_TOTAL_BYTES 2 diff --git a/rtl/ip/multimedia/apu_entropy_engine.sv b/rtl/ip/multimedia/apu_entropy_engine.sv new file mode 100644 index 00000000..7c0af16c --- /dev/null +++ b/rtl/ip/multimedia/apu_entropy_engine.sv @@ -0,0 +1,454 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +`include "apu_define.svh" + +module apu_entropy_engine ( + // verilog_format: off -- preserve command, memory, and reservoir columns + input logic clk_i, + input logic rst_n_i, + input logic flush_i, + input logic req_valid_i, + output logic req_ready_o, + input logic [ 3:0] opcode_i, + input logic [ 3:0] dst_i, + input logic [ 7:0] aux_i, + input logic [31:0] immediate_i, + input logic [31:0] source0_i, + input logic [31:0] source1_i, + input logic [15:0] table_offset_i, + output logic memory_req_o, + output logic [16:0] memory_addr_o, + input logic memory_valid_i, + input logic [31:0] memory_data_i, + input logic memory_error_i, + output logic bit_req_valid_o, + input logic bit_req_ready_i, + output logic [ 2:0] bit_req_op_o, + output logic [ 5:0] bit_req_width_o, + input logic bit_result_valid_i, + input logic [31:0] bit_result_data_i, + input logic bit_error_i, + output logic result_valid_o, + output logic [ 3:0] result_dst_o, + output logic [ 3:0][31:0] result_data_o, + output logic [ 2:0] result_words_o, + output logic error_o, + output logic [ 5:0] error_code_o, + output logic [ 3:0] error_stage_o, + output logic [31:0] cycles_o + // verilog_format: on +); + typedef enum logic [3:0] { + Idle, + TableRequest, + TableWait, + BitRequest, + BitWait, + ConsumeRequest, + ConsumeWait, + UnaryRequest, + UnaryWait, + RiceRequest, + RiceWait, + ValueRequest, + ValueWait, + SignRequest, + SignWait + } state_e; + + state_e s_state_q; + logic [3:0] s_opcode_q, s_dst_q; + logic [4:0] s_aux_q; + logic [31:0] s_immediate_q, s_source0_q; + logic [15:0] s_table_offset_q; + logic [31:0] s_entry_index_q, s_entry_count_q, s_entry_issue_q; + logic [24:0] s_code_q, s_candidate_code_q; + logic [4:0] s_previous_len_q, s_len_q; + logic [31:0] s_symbol_q, s_run_q, s_quotient_q, s_magnitude_q; + logic [5:0] s_value_width_q; + logic s_unused; + logic s_direct_bit_req, s_huff_peek_req, s_huff_consume_req; + logic s_unary_continue_req, s_rice_continue_req, s_rice_value_req, s_sign_req; + logic [5:0] s_rice_width; + logic s_direct_huff_bit_req, s_huff_entry_valid, s_huff_entry_match; + logic [5:0] s_huff_width_q; + logic [31:0] s_huff_bits_q, s_huff_prefix; + logic s_huff_peek_pending_q; + + assign req_ready_o = s_state_q == Idle; + assign s_rice_width = (s_opcode_q == `APB4_APU__MC_ENTROPY_RICE4) ? + {2'd0, s_source0_q[3:0]} : {1'b0, s_source0_q[4:0]}; + assign s_direct_huff_bit_req = (s_state_q == Idle) && req_valid_i && req_ready_o && + (opcode_i <= `APB4_APU__MC_ENTROPY_HUFF_QUAD) && + (source1_i >= 32'd1) && (source1_i <= 32'd4096); + assign s_direct_bit_req = (s_state_q == Idle) && req_valid_i && req_ready_o && + (opcode_i > `APB4_APU__MC_ENTROPY_HUFF_QUAD) && + !((opcode_i == `APB4_APU__MC_ENTROPY_SIGN_RESTORE) && (source0_i == 32'd0)) && + !((opcode_i inside {`APB4_APU__MC_ENTROPY_RICE4, + `APB4_APU__MC_ENTROPY_RICE5}) && + (((opcode_i == `APB4_APU__MC_ENTROPY_RICE4) && (source0_i[3:0] == 4'd15)) || + ((opcode_i == `APB4_APU__MC_ENTROPY_RICE5) && (source0_i[4:0] == 5'd31))) && + ((source1_i[5:0] == 6'd0) || (source1_i[5:0] > 6'd32))); + assign s_huff_peek_req = (s_state_q == BitWait) && !s_huff_peek_pending_q && + bit_result_valid_i && !bit_error_i && (bit_result_data_i[6:0] != 7'd0); + assign s_huff_consume_req = (s_state_q == TableRequest) && memory_valid_i && + !memory_error_i && s_huff_entry_match; + assign s_unary_continue_req = (s_state_q == UnaryWait) && bit_result_valid_i && !bit_error_i && + (bit_result_data_i[0] == s_aux_q[0]) && (s_run_q < s_immediate_q); + assign s_rice_continue_req = (s_state_q == RiceWait) && bit_result_valid_i && !bit_error_i && + !bit_result_data_i[0] && (s_quotient_q < 32'd65535); + assign s_rice_value_req = (s_state_q == RiceWait) && bit_result_valid_i && !bit_error_i && + bit_result_data_i[0] && (s_rice_width != 6'd0); + assign s_sign_req = (s_state_q == ValueWait) && bit_result_valid_i && !bit_error_i && + (s_opcode_q == `APB4_APU__MC_ENTROPY_SIGN_RESTORE); + assign memory_req_o = (s_state_q == TableRequest) && (s_entry_issue_q < s_entry_count_q); + assign memory_addr_o = 17'({16'd0, s_table_offset_q} + s_source0_q + (s_entry_issue_q << 2)); + assign bit_req_valid_o = s_direct_huff_bit_req || s_direct_bit_req || s_huff_peek_req || + s_huff_consume_req || s_unary_continue_req || s_rice_continue_req || + s_rice_value_req || s_sign_req || (s_state_q inside { + BitRequest, ConsumeRequest, UnaryRequest, RiceRequest, ValueRequest, SignRequest + }); + assign bit_req_op_o = s_direct_huff_bit_req ? 3'd0 : + (((s_state_q == BitRequest) || s_huff_peek_req) ? 3'd1 : 3'd2); + assign bit_req_width_o = s_direct_huff_bit_req ? {1'b0, aux_i[4:0]} : + (s_direct_bit_req ? + ((opcode_i == `APB4_APU__MC_ENTROPY_UNARY) ? 6'd1 : + ((opcode_i inside {`APB4_APU__MC_ENTROPY_RICE4, + `APB4_APU__MC_ENTROPY_RICE5}) ? + ((((opcode_i == `APB4_APU__MC_ENTROPY_RICE4) && (source0_i[3:0] == 4'd15)) || + ((opcode_i == `APB4_APU__MC_ENTROPY_RICE5) && (source0_i[4:0] == 5'd31))) ? + source1_i[5:0] : 6'd1) : + ((source1_i[4:0] == 5'd0) ? 6'd1 : {1'b0, source1_i[4:0]}))) : + (s_huff_peek_req ? bit_result_data_i[5:0] : + (s_huff_consume_req ? {1'b0, memory_data_i[20:16]} : + (s_rice_value_req ? s_rice_width : + ((s_state_q inside {UnaryRequest, RiceRequest, SignRequest}) || + s_unary_continue_req || s_rice_continue_req || s_sign_req ? 6'd1 : + ((s_state_q == ValueRequest) ? s_value_width_q : {1'b0, s_len_q})))))); + + always_comb begin + s_candidate_code_q = s_code_q << (memory_data_i[20:16] - s_previous_len_q); + s_huff_entry_valid = (memory_data_i[31:21] == 11'd0) && + (memory_data_i[20:16] >= s_previous_len_q) && + (memory_data_i[20:16] != 5'd0) && (memory_data_i[20:16] <= s_aux_q) && + (s_candidate_code_q < (25'd1 << memory_data_i[20:16])); + s_huff_prefix = 32'd0; + if ({1'b0, memory_data_i[20:16]} <= s_huff_width_q) begin + s_huff_prefix = s_huff_bits_q >> (s_huff_width_q - memory_data_i[20:16]); + end + s_huff_entry_match = s_huff_entry_valid && + ({1'b0, memory_data_i[20:16]} <= s_huff_width_q) && + (s_huff_prefix == {7'd0, s_candidate_code_q}); + end + always_comb begin + if (s_opcode_q <= `APB4_APU__MC_ENTROPY_HUFF_QUAD) begin + cycles_o = 32'd26 + s_entry_count_q; + end else if (s_opcode_q == `APB4_APU__MC_ENTROPY_UNARY) begin + cycles_o = 32'd2 + s_run_q; + end else if (s_opcode_q inside {`APB4_APU__MC_ENTROPY_RICE4, `APB4_APU__MC_ENTROPY_RICE5}) begin + if (((s_opcode_q == `APB4_APU__MC_ENTROPY_RICE4) && + (s_source0_q[3:0] == 4'd15)) || + ((s_opcode_q == `APB4_APU__MC_ENTROPY_RICE5) && + (s_source0_q[4:0] == 5'd31))) begin + cycles_o = 32'd4 + {26'd0, s_value_width_q}; + end else begin + cycles_o = 32'd4 + s_quotient_q + {26'd0, s_value_width_q}; + end + end else begin + cycles_o = 32'd3 + {26'd0, s_value_width_q}; + end + end + assign s_unused = ^aux_i[7:5] && 1'b0; + + function automatic logic [31:0] rice_signed( + input logic [31:0] quotient_i, input logic [31:0] remainder_i, input logic [5:0] width_i); + logic [31:0] s_unsigned; + begin + s_unsigned = (quotient_i << width_i) | remainder_i; + return (s_unsigned >> 1) ^ (32'd0 - s_unsigned[0]); + end + endfunction + + `define RETROSOC_APU__ENTROPY_ERROR(CODE) \ + begin \ + s_state_q <= Idle; \ + error_o <= 1'b1; \ + error_code_o <= CODE; \ + error_stage_o <= `APB4_APU__ERROR_STAGE_ENTROPY; \ + result_valid_o <= 1'b1; \ + result_words_o <= 3'd0; \ + end + + `define RETROSOC_APU__ENTROPY_LOCAL_ERROR \ + begin \ + s_state_q <= Idle; \ + error_o <= 1'b1; \ + error_code_o <= `APB4_APU__ERROR_CODE_SEQUENCER; \ + error_stage_o <= `APB4_APU__ERROR_STAGE_LIFECYCLE; \ + result_valid_o <= 1'b1; \ + result_words_o <= 3'd0; \ + end + + `define RETROSOC_APU__ENTROPY_FINISH_ONE(VALUE) \ + begin \ + s_state_q <= Idle; \ + result_valid_o <= 1'b1; \ + result_words_o <= 3'd1; \ + result_dst_o <= s_dst_q; \ + result_data_o[0] <= VALUE; \ + end + + always_ff @(posedge clk_i or negedge rst_n_i) begin + if (!rst_n_i) begin + s_state_q <= Idle; + s_opcode_q <= 4'd0; + s_dst_q <= 4'd0; + s_aux_q <= 5'd0; + s_immediate_q <= 32'd0; + s_source0_q <= 32'd0; + s_table_offset_q <= 16'd0; + s_entry_index_q <= 32'd0; + s_entry_count_q <= 32'd0; + s_entry_issue_q <= 32'd0; + s_code_q <= 25'd0; + s_previous_len_q <= 5'd0; + s_len_q <= 5'd0; + s_symbol_q <= 32'd0; + s_run_q <= 32'd0; + s_quotient_q <= 32'd0; + s_magnitude_q <= 32'd0; + s_value_width_q <= 6'd0; + s_huff_width_q <= 6'd0; + s_huff_bits_q <= 32'd0; + s_huff_peek_pending_q <= 1'b0; + result_valid_o <= 1'b0; + result_dst_o <= 4'd0; + result_data_o <= '0; + result_words_o <= 3'd0; + error_o <= 1'b0; + error_code_o <= 6'd0; + error_stage_o <= 4'd0; + end else if (flush_i) begin + s_state_q <= Idle; + result_valid_o <= 1'b0; + error_o <= 1'b0; + end else begin + result_valid_o <= 1'b0; + error_o <= 1'b0; + unique case (s_state_q) + Idle: begin + if (req_valid_i) begin + s_opcode_q <= opcode_i; + s_dst_q <= dst_i; + s_aux_q <= aux_i[4:0]; + s_immediate_q <= immediate_i; + s_source0_q <= source0_i; + s_table_offset_q <= table_offset_i; + s_entry_index_q <= 32'd0; + s_entry_count_q <= source1_i; + s_entry_issue_q <= 32'd0; + s_code_q <= 25'd0; + s_previous_len_q <= 5'd0; + s_run_q <= 32'd0; + s_quotient_q <= 32'd0; + s_huff_peek_pending_q <= 1'b0; + if (opcode_i <= `APB4_APU__MC_ENTROPY_HUFF_QUAD) begin + if ((source1_i == 32'd0) || (source1_i > 32'd4096)) begin + `RETROSOC_APU__ENTROPY_ERROR(6'd7) + end else begin + s_state_q <= s_direct_huff_bit_req ? BitWait : BitRequest; + end + end else if (opcode_i == `APB4_APU__MC_ENTROPY_UNARY) begin + s_state_q <= s_direct_bit_req ? UnaryWait : UnaryRequest; + end else if (opcode_i inside {`APB4_APU__MC_ENTROPY_RICE4, + `APB4_APU__MC_ENTROPY_RICE5}) begin + if (((opcode_i == `APB4_APU__MC_ENTROPY_RICE4) && + (source0_i[3:0] == 4'd15)) || + ((opcode_i == `APB4_APU__MC_ENTROPY_RICE5) && + (source0_i[4:0] == 5'd31))) begin + s_value_width_q <= source1_i[5:0]; + if ((source1_i[5:0] == 6'd0) || (source1_i[5:0] > 6'd32)) begin + `RETROSOC_APU__ENTROPY_ERROR(6'd7) + end else begin + s_state_q <= s_direct_bit_req ? ValueWait : ValueRequest; + end + end else begin + s_state_q <= s_direct_bit_req ? RiceWait : RiceRequest; + end + end else begin + s_magnitude_q <= source0_i; + if (source0_i == 32'd0) begin + `RETROSOC_APU__ENTROPY_FINISH_ONE(32'd0) + end else begin + s_value_width_q <= {1'b0, source1_i[4:0]}; + if (source1_i[4:0] == 5'd0) begin + s_state_q <= s_direct_bit_req ? SignWait : SignRequest; + end else begin + s_state_q <= s_direct_bit_req ? ValueWait : ValueRequest; + end + end + end + end + end + TableRequest: begin + if (memory_req_o) s_entry_issue_q <= s_entry_issue_q + 1'b1; + if (memory_error_i) begin + `RETROSOC_APU__ENTROPY_LOCAL_ERROR + end else if (memory_valid_i) begin + if (!s_huff_entry_valid) begin + `RETROSOC_APU__ENTROPY_ERROR(6'd7); + end else if ({1'b0, memory_data_i[20:16]} > s_huff_width_q) begin + `RETROSOC_APU__ENTROPY_ERROR(6'd5); + end else if (s_huff_entry_match) begin + s_len_q <= memory_data_i[20:16]; + s_symbol_q <= {16'd0, memory_data_i[15:0]}; + s_state_q <= s_huff_consume_req ? ConsumeWait : ConsumeRequest; + end else if (s_entry_index_q + 1'b1 >= s_entry_count_q) begin + `RETROSOC_APU__ENTROPY_ERROR(6'd7); + end else begin + s_code_q <= s_candidate_code_q + 1'b1; + s_previous_len_q <= memory_data_i[20:16]; + s_entry_index_q <= s_entry_index_q + 1'b1; + end + end + end + TableWait: s_state_q <= TableRequest; + BitRequest: begin + if (bit_req_ready_i) s_state_q <= BitWait; + end + BitWait: begin + if (bit_error_i) begin + `RETROSOC_APU__ENTROPY_ERROR(6'd5); + end else if (bit_result_valid_i) begin + if (!s_huff_peek_pending_q) begin + if (bit_result_data_i[6:0] == 7'd0) begin + `RETROSOC_APU__ENTROPY_ERROR(6'd5); + end else begin + s_huff_width_q <= bit_result_data_i[5:0]; + s_huff_peek_pending_q <= 1'b1; + end + end else begin + s_huff_bits_q <= bit_result_data_i; + s_entry_index_q <= 32'd0; + s_entry_issue_q <= 32'd0; + s_code_q <= 25'd0; + s_previous_len_q <= 5'd0; + s_huff_peek_pending_q <= 1'b0; + s_state_q <= TableRequest; + end + end + end + ConsumeRequest: begin + if (bit_req_ready_i) s_state_q <= ConsumeWait; + end + ConsumeWait: begin + if (bit_error_i) begin + `RETROSOC_APU__ENTROPY_ERROR(6'd5); + end else if (bit_result_valid_i) begin + s_state_q <= Idle; + result_valid_o <= 1'b1; + result_dst_o <= s_dst_q; + if (s_opcode_q == `APB4_APU__MC_ENTROPY_HUFF_SYMBOL) begin + result_words_o <= 3'd1; + result_data_o[0] <= s_symbol_q; + end else if (s_opcode_q == `APB4_APU__MC_ENTROPY_HUFF_PAIR) begin + result_words_o <= 3'd2; + result_data_o[0] <= {28'd0, s_symbol_q[7:4]}; + result_data_o[1] <= {28'd0, s_symbol_q[3:0]}; + end else begin + result_words_o <= 3'd4; + for (int index = 0; index < 4; index++) begin + result_data_o[index] <= {31'd0, s_symbol_q[3-index]}; + end + end + end + end + UnaryRequest, RiceRequest, ValueRequest, SignRequest: begin + if (bit_req_ready_i) begin + unique case (s_state_q) + UnaryRequest: s_state_q <= UnaryWait; + RiceRequest: s_state_q <= RiceWait; + ValueRequest: s_state_q <= ValueWait; + default: s_state_q <= SignWait; + endcase + end + end + UnaryWait: begin + if (bit_error_i) begin + `RETROSOC_APU__ENTROPY_ERROR(6'd5) + end else if (bit_result_valid_i) begin + if (bit_result_data_i[0] != s_aux_q[0]) begin + `RETROSOC_APU__ENTROPY_FINISH_ONE(s_run_q) + end else if (s_run_q >= s_immediate_q) begin + `RETROSOC_APU__ENTROPY_ERROR(6'd7) + end else begin + s_run_q <= s_run_q + 1'b1; + s_state_q <= s_unary_continue_req ? UnaryWait : UnaryRequest; + end + end + end + RiceWait: begin + if (bit_error_i) begin + `RETROSOC_APU__ENTROPY_ERROR(6'd5) + end else if (bit_result_valid_i) begin + if (bit_result_data_i[0]) begin + s_value_width_q <= (s_opcode_q == `APB4_APU__MC_ENTROPY_RICE4) ? + {2'd0, s_source0_q[3:0]} : {1'd0, s_source0_q[4:0]}; + if (((s_opcode_q == `APB4_APU__MC_ENTROPY_RICE4) && + (s_source0_q[3:0] == 4'd0)) || + ((s_opcode_q == `APB4_APU__MC_ENTROPY_RICE5) && + (s_source0_q[4:0] == 5'd0))) begin + `RETROSOC_APU__ENTROPY_FINISH_ONE((s_quotient_q >> 1) ^ (32'd0 - s_quotient_q[0])); + end else begin + s_state_q <= s_rice_value_req ? ValueWait : ValueRequest; + end + end else if (s_quotient_q == 32'd65535) begin + `RETROSOC_APU__ENTROPY_ERROR(6'd7); + end else begin + s_quotient_q <= s_quotient_q + 1'b1; + s_state_q <= s_rice_continue_req ? RiceWait : RiceRequest; + end + end + end + ValueWait: begin + if (bit_error_i) begin + `RETROSOC_APU__ENTROPY_ERROR(6'd5) + end else if (bit_result_valid_i) begin + if (s_opcode_q inside {`APB4_APU__MC_ENTROPY_RICE4, `APB4_APU__MC_ENTROPY_RICE5}) begin + if (((s_opcode_q == `APB4_APU__MC_ENTROPY_RICE4) && + (s_source0_q[3:0] == 4'd15)) || + ((s_opcode_q == `APB4_APU__MC_ENTROPY_RICE5) && + (s_source0_q[4:0] == 5'd31))) begin + `RETROSOC_APU__ENTROPY_FINISH_ONE($unsigned( + $signed( + bit_result_data_i << (6'd32 - s_value_width_q) + ) >>> (6'd32 - s_value_width_q) + )); + end else begin + `RETROSOC_APU__ENTROPY_FINISH_ONE( + rice_signed(s_quotient_q, bit_result_data_i, s_value_width_q)); + end + end else begin + s_magnitude_q <= s_magnitude_q + bit_result_data_i; + s_state_q <= s_sign_req ? SignWait : SignRequest; + end + end + end + SignWait: begin + if (bit_error_i) begin + `RETROSOC_APU__ENTROPY_ERROR(6'd5) + end else if (bit_result_valid_i) begin + `RETROSOC_APU__ENTROPY_FINISH_ONE( + bit_result_data_i[0] ? (32'd0 - s_magnitude_q) : s_magnitude_q); + end + end + default: s_state_q <= Idle; + endcase + end + end + + `undef RETROSOC_APU__ENTROPY_ERROR + `undef RETROSOC_APU__ENTROPY_LOCAL_ERROR + `undef RETROSOC_APU__ENTROPY_FINISH_ONE +endmodule diff --git a/rtl/ip/multimedia/apu_kernel_engine.sv b/rtl/ip/multimedia/apu_kernel_engine.sv new file mode 100644 index 00000000..fa92c6db --- /dev/null +++ b/rtl/ip/multimedia/apu_kernel_engine.sv @@ -0,0 +1,1233 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +`include "apu_define.svh" + +module apu_kernel_engine ( + // verilog_format: off -- preserve request, local-memory, and result columns + input logic clk_i, + input logic rst_n_i, + input logic flush_i, + input logic req_valid_i, + output logic req_ready_o, + input logic [ 3:0] opcode_i, + input logic [ 7:0] aux_i, + input logic [15:0] count_i, + input logic [ 3:0] dst_i, + input logic [31:0] input_offset_i, + input logic [31:0] parameter_offset_i, + input logic [31:0] output_offset_i, + input logic [16:0] scratch_base_i, + input logic [16:0] scratch_bytes_i, + input logic [15:0] table_offset_i, + input logic [15:0] table_bytes_i, + output logic memory_req_o, + output logic memory_write_o, + output logic [16:0] memory_addr_o, + output logic [31:0] memory_data_o, + output logic [ 3:0] memory_strb_o, + input logic memory_valid_i, + input logic [31:0] memory_data_i, + input logic memory_error_i, + output logic done_o, + output logic error_o, + output logic [ 5:0] error_code_o, + output logic [ 3:0] error_stage_o, + output logic [ 7:0] error_reason_o, + output logic [ 3:0] result_dst_o, + output logic [31:0] result_count_o, + output logic [31:0] cycles_o, + output logic busy_o + // verilog_format: on +); + typedef enum logic [5:0] { + Idle, + ParameterRequest, + ParameterWait, + Validate, + Ready, + RequantInputRequest, + RequantInputWait, + RequantScaleRequest, + RequantScaleWait, + RequantWrite, + StereoFirstRequest, + StereoFirstWait, + StereoSecondRequest, + StereoSecondWait, + StereoWriteFirst, + StereoWriteSecond, + HistoryRequest, + HistoryWait, + CoefficientRequest, + CoefficientWait, + PredictInputRequest, + PredictInputWait, + PredictWrite, + HistoryWrite, + TransformInputRequest, + TransformInputWait, + TransformCoefficientRequest, + TransformCoefficientWait, + TransformWrite, + DctHistoryRequest, + DctHistoryWait, + DctWindowRequest, + DctWindowWait, + DctOutputWrite, + DctPhaseWrite, + ResampleCopyRequest, + ResampleCopyWait, + ResampleCopyWrite, + ResampleSampleRequest, + ResampleSampleWait, + ResampleCoefficientRequest, + ResampleCoefficientWait, + ResampleWrite, + ResampleStateWrite, + PcmInputRequest, + PcmInputWait, + PcmWrite, + PcmDuplicateWrite, + Finish, + Failed + } state_e; + + state_e s_state_q; + logic [3:0] s_opcode_q, s_dst_q; + logic [ 7:0] s_aux_q; + logic [15:0] s_count_q; + logic [31:0] s_input_offset_q, s_parameter_offset_q, s_output_offset_q; + logic [16:0] s_scratch_base_q, s_scratch_bytes_q; + logic [15:0] s_table_offset_q, s_table_bytes_q; + logic [7:0][31:0] s_parameter_q; + logic [3:0] s_parameter_words_q, s_parameter_index_q; + logic [31:0] s_element_q, s_block_q, s_row_q, s_tap_q, s_channel_q; + logic [31:0] s_read_issue_q, s_read_receive_q; + logic s_pair_issue_second_q, s_pair_return_second_q, s_transform_preload_q; + logic s_resample_profile_valid_q; + logic [3:0] s_resample_profile_q; + logic [31:0] s_produced_q, s_cycles_q; + logic [31:0] s_value0_q, s_value1_q, s_history_value_q; + logic signed [71:0] s_accumulator_q; + logic signed [31:0] s_vector_q [0:31]; + logic signed [31:0] s_coefficient_q [0:31]; + logic signed [31:0] s_history_q [0:31]; + logic [63:0] s_next_output_q, s_input_base_q; + logic [15:0] s_ratio_l, s_ratio_m; + logic [ 5:0] s_resample_phase; + logic [63:0] s_resample_source_index; + logic [31:0] s_resample_channels; + logic [31:0] s_transform_order; + logic [31:0] s_input_addr, s_parameter_addr, s_output_addr; + logic [32:0] s_input_addr_full, s_parameter_addr_full, s_output_addr_full; + logic [31:0] s_reference_addr[0:2]; + logic s_alignment_ok, s_validation_ok; + logic s_semantic_err; + logic [32:0] s_output_bytes, s_input_bytes; + logic [32:0] s_resample_output_frames; + logic [63:0] s_resample_limit_num, s_resample_remaining_num; + logic [32:0] s_resample_history_bytes, s_resample_input_bytes; + logic [32:0] s_resample_input_start; + logic [31:0] s_predict_order; + logic [ 3:0] s_dct_history_phase; + logic signed [31:0] s_reconstruction_result0, s_reconstruction_result1; + logic s_reconstruction_overflow; + logic signed [31:0] s_stereo_live_result0, s_stereo_live_result1; + logic s_stereo_live_overflow; + logic signed [31:0] s_transform_result; + logic s_transform_overflow; + logic signed [31:0] s_transform_coefficient [0:31]; + logic s_transform_final_return; + logic [31:0] s_pcm_word; + logic [ 3:0] s_pcm_strb; + logic [ 1:0] s_pcm_lane; + logic s_unused; + + function automatic logic range_valid(input logic [31:0] address_i, input logic [32:0] bytes_i, + input logic [31:0] base_i, input logic [31:0] size_i); + logic [32:0] s_end; + logic [32:0] s_limit; + begin + s_end = {1'b0, address_i} + bytes_i; + s_limit = {1'b0, base_i} + {1'b0, size_i}; + return !s_end[32] && (address_i >= base_i) && (s_end <= s_limit); + end + endfunction + + function automatic logic reference_valid( + input logic [31:0] reference_i, input logic [32:0] bytes_i, input logic state_only_i, + input logic [16:0] scratch_base_arg, input logic [16:0] scratch_bytes_arg, + input logic [15:0] table_offset_arg, input logic [15:0] table_bytes_arg); + logic s_table; + logic [31:0] s_offset; + logic [31:0] s_address; + logic s_reserved_ok; + begin + s_table = reference_i[31]; + s_offset = s_table ? {16'd0, reference_i[15:0]} : {15'd0, reference_i[16:0]}; + s_address = s_table ? ({16'd0, table_offset_arg} + s_offset) : + ({15'd0, scratch_base_arg} + s_offset); + s_reserved_ok = s_table ? (reference_i[30:16] == 15'd0) : (reference_i[30:17] == 14'd0); + return s_reserved_ok && (reference_i[1:0] == 2'd0) && (!state_only_i || !s_table) && + (s_table ? range_valid( + s_address, bytes_i, {16'd0, table_offset_arg}, {16'd0, table_bytes_arg} + ) : range_valid( + s_address, bytes_i, {15'd0, scratch_base_arg}, {15'd0, scratch_bytes_arg} + )); + end + endfunction + + function automatic logic [31:0] reference_address(input logic [31:0] reference_i, + input logic [16:0] scratch_base_arg, + input logic [15:0] table_offset_arg); + logic s_reserved_unused; + begin + s_reserved_unused = ^reference_i[30:17]; + return (reference_i[31] ? + ({16'd0, table_offset_arg} + {16'd0, reference_i[15:0]}) : + ({15'd0, scratch_base_arg} + {15'd0, reference_i[16:0]})) | + {32{s_reserved_unused && 1'b0}}; + end + endfunction + + function automatic logic signed [63:0] rne64(input logic signed [63:0] value_i, + input logic [5:0] shift_i); + logic signed [63:0] s_magnitude; + logic signed [63:0] s_quotient; + logic [63:0] s_remainder, s_half; + begin + if (shift_i == 6'd0) return value_i; + s_magnitude = (value_i < 0) ? -value_i : value_i; + s_quotient = s_magnitude >>> shift_i; + s_remainder = s_magnitude & ((64'd1 << shift_i) - 1'b1); + s_half = 64'd1 << (shift_i - 1'b1); + if ((s_remainder > s_half) || ((s_remainder == s_half) && s_quotient[0])) begin + s_quotient = s_quotient + 1'b1; + end + return (value_i < 0) ? -s_quotient : s_quotient; + end + endfunction + + function automatic logic signed [31:0] sat32(input logic signed [71:0] value_i); + if (value_i > 72'sh0000_0000_7fff_ffff) return 32'sh7fff_ffff; + if (value_i < -72'sh0000_0000_8000_0000) return -32'sh8000_0000; + return value_i[31:0]; + endfunction + + function automatic logic signed [71:0] rne72(input logic signed [71:0] value_i, + input logic [5:0] shift_i); + logic signed [71:0] s_magnitude; + logic signed [71:0] s_quotient; + logic [71:0] s_remainder, s_half; + begin + if (shift_i == 6'd0) return value_i; + s_magnitude = (value_i < 0) ? -value_i : value_i; + s_quotient = s_magnitude >>> shift_i; + s_remainder = s_magnitude & ((72'd1 << shift_i) - 1'b1); + s_half = 72'd1 << (shift_i - 1'b1); + if ((s_remainder > s_half) || ((s_remainder == s_half) && s_quotient[0])) begin + s_quotient = s_quotient + 1'b1; + end + return (value_i < 0) ? -s_quotient : s_quotient; + end + endfunction + + assign busy_o = s_state_q != Idle; + assign s_unused = ^s_stereo_live_result1 && 1'b0; + assign req_ready_o = s_state_q == Ready; + assign s_input_addr_full = {16'd0, s_scratch_base_q} + {1'b0, s_input_offset_q}; + assign s_parameter_addr_full = {16'd0, s_scratch_base_q} + {1'b0, s_parameter_offset_q}; + assign s_output_addr_full = {16'd0, s_scratch_base_q} + {1'b0, s_output_offset_q}; + assign s_input_addr = s_input_addr_full[31:0]; + assign s_parameter_addr = s_parameter_addr_full[31:0]; + assign s_output_addr = s_output_addr_full[31:0]; + assign s_transform_order = (s_opcode_q == `APB4_APU__MC_KERNEL_IMDCT6) ? 32'd6 : + ((s_opcode_q == `APB4_APU__MC_KERNEL_IMDCT18) ? 32'd18 : 32'd32); + assign s_resample_channels = s_parameter_q[7]; + assign s_reference_addr[0] = reference_address( + s_parameter_q[0], s_scratch_base_q, s_table_offset_q + ); + assign s_reference_addr[1] = reference_address( + s_parameter_q[1], s_scratch_base_q, s_table_offset_q + ); + assign s_reference_addr[2] = reference_address( + s_parameter_q[2], s_scratch_base_q, s_table_offset_q + ); + assign s_predict_order = (s_opcode_q == `APB4_APU__MC_KERNEL_LPC) ? + {26'd0, s_aux_q[5:0]} : {29'd0, s_aux_q[2:0]}; + assign s_dct_history_phase = s_parameter_q[3][3:0] - s_tap_q[3:0]; + + apu_reconstruction_engine u_reconstruction_engine ( + .opcode_i (s_opcode_q), + .aux_i (s_aux_q), + .value0_i (s_value0_q), + .value1_i (s_value1_q), + .scale_i (s_coefficient_q[0]), + .residual_i (s_value0_q), + .predictor_shift_i(s_parameter_q[2]), + .history_i (s_history_q), + .coefficient_i (s_coefficient_q), + .result0_o (s_reconstruction_result0), + .result1_o (s_reconstruction_result1), + .overflow_o (s_reconstruction_overflow) + ); + + apu_reconstruction_engine u_stereo_live_engine ( + .opcode_i (s_opcode_q), + .aux_i (s_aux_q), + .value0_i (s_value0_q), + .value1_i (memory_data_i), + .scale_i (32'sd0), + .residual_i (32'sd0), + .predictor_shift_i(32'sd0), + .history_i (s_history_q), + .coefficient_i (s_coefficient_q), + .result0_o (s_stereo_live_result0), + .result1_o (s_stereo_live_result1), + .overflow_o (s_stereo_live_overflow) + ); + + apu_transform_engine u_transform_engine ( + .order_i (s_transform_order[5:0]), + .sample_i (s_vector_q), + .coefficient_i(s_transform_coefficient), + .result_o (s_transform_result), + .overflow_o (s_transform_overflow) + ); + + always_comb begin + for (int index = 0; index < 32; index++) begin + s_transform_coefficient[index] = s_coefficient_q[index]; + end + if ((s_state_q == TransformCoefficientRequest) && memory_valid_i && + (s_read_receive_q < 32'd32)) begin + s_transform_coefficient[s_read_receive_q[4:0]] = memory_data_i; + end + s_transform_final_return = (s_state_q == TransformCoefficientRequest) && + memory_valid_i && (s_read_receive_q + 1'b1 >= s_transform_order); + end + + always_comb begin + s_semantic_err = 1'b0; + unique case (s_opcode_q) + `APB4_APU__MC_KERNEL_DCT32_POLY: s_semantic_err = s_parameter_q[3][31:4] != 28'd0; + `APB4_APU__MC_KERNEL_LPC: + s_semantic_err = ($signed(s_parameter_q[2]) < -32'sd31) || + ($signed(s_parameter_q[2]) > 32'sd31); + `APB4_APU__MC_KERNEL_RESAMPLE: + s_semantic_err = !(s_resample_channels inside {32'd1, 32'd2}) || + (s_resample_profile_valid_q && (s_aux_q[3:0] != s_resample_profile_q)); + `APB4_APU__MC_KERNEL_PCM_PACK: + s_semantic_err = !(s_parameter_q[1] inside {32'd1, 32'd2}) || + (s_aux_q[2] && (s_parameter_q[1] != 32'd1)); + default: s_semantic_err = 1'b0; + endcase + end + + apu_resampler u_resampler ( + .profile_i (s_aux_q[3:0]), + .next_output_num_i(s_next_output_q), + .ratio_l_o (s_ratio_l), + .ratio_m_o (s_ratio_m), + .phase_o (s_resample_phase), + .source_index_o (s_resample_source_index) + ); + + always_comb begin + s_resample_limit_num = (s_input_base_q + {48'd0, s_count_q}) * s_ratio_l; + s_resample_remaining_num = (s_next_output_q < s_resample_limit_num) ? + (s_resample_limit_num - s_next_output_q) : 64'd0; + s_resample_output_frames = 33'((s_resample_remaining_num + {48'd0, s_ratio_m} - + 64'd1) / {48'd0, s_ratio_m}); + s_resample_history_bytes = ({1'b0, s_resample_channels} * 33'd7) << 2; + s_resample_input_bytes = (({17'd0, s_count_q} + 33'd16) * {1'b0, s_resample_channels}) << 2; + s_resample_input_start = {1'b0, s_input_addr} - s_resample_history_bytes; + s_input_bytes = 33'd0; + s_output_bytes = 33'd0; + unique case (s_opcode_q) + `APB4_APU__MC_KERNEL_REQUANT, `APB4_APU__MC_KERNEL_FIXED, `APB4_APU__MC_KERNEL_LPC: begin + s_input_bytes = {17'd0, s_count_q} << 2; + s_output_bytes = {17'd0, s_count_q} << 2; + end + `APB4_APU__MC_KERNEL_STEREO, `APB4_APU__MC_KERNEL_DECORRELATE: begin + s_input_bytes = {17'd0, s_count_q} << 2; + s_output_bytes = {17'd0, s_count_q} << 3; + end + `APB4_APU__MC_KERNEL_IMDCT6: begin + s_input_bytes = ({17'd0, s_count_q} * 33'd6) << 2; + s_output_bytes = ({17'd0, s_count_q} * 33'd12) << 2; + end + `APB4_APU__MC_KERNEL_IMDCT18: begin + s_input_bytes = ({17'd0, s_count_q} * 33'd18) << 2; + s_output_bytes = ({17'd0, s_count_q} * 33'd36) << 2; + end + `APB4_APU__MC_KERNEL_DCT32_POLY: begin + s_input_bytes = ({17'd0, s_count_q} * 33'd32) << 2; + s_output_bytes = ({17'd0, s_count_q} * 33'd32) << 2; + end + `APB4_APU__MC_KERNEL_RESAMPLE: begin + s_input_bytes = (s_aux_q[3:0] == 4'd0) ? + (({17'd0, s_count_q} * {1'b0, s_resample_channels}) << 2) : + s_resample_input_bytes; + s_output_bytes = (s_aux_q[3:0] == 4'd0) ? + (({17'd0, s_count_q} * {1'b0, s_resample_channels}) << 2) : + ((s_resample_output_frames * {1'b0, s_resample_channels}) << 2); + end + default: begin + s_input_bytes = ({17'd0, s_count_q} * {1'b0, s_parameter_q[1]}) << 2; + s_output_bytes = ({17'd0, s_count_q} * {1'b0, s_parameter_q[1]}) << + ((s_aux_q[1:0] == 2'd0) ? 1 : 2); + if (s_aux_q[2]) s_output_bytes = s_output_bytes << 1; + end + endcase + end + + always_comb begin + logic [32:0] s_scale_bytes; + logic [32:0] s_transform_bytes; + logic [32:0] s_history_bytes; + s_scale_bytes = {17'd0, s_count_q} << 2; + s_transform_bytes = ({1'b0, s_transform_order} * {1'b0, s_transform_order} * 33'd2) << 2; + s_history_bytes = {27'd0, s_aux_q[5:0]} << 2; + s_alignment_ok = (s_scratch_base_q[1:0] == 2'd0) && + (s_input_offset_q[1:0] == 2'd0) && (s_parameter_offset_q[1:0] == 2'd0) && + ((s_opcode_q != `APB4_APU__MC_KERNEL_PCM_PACK) ? + (s_output_offset_q[1:0] == 2'd0) : + ((s_aux_q[1:0] == 2'd0) ? !s_output_offset_q[0] : + (s_output_offset_q[1:0] == 2'd0))); + s_validation_ok = s_alignment_ok && !s_input_addr_full[32] && !s_parameter_addr_full[32] && + !s_output_addr_full[32] && + range_valid( + s_parameter_addr, + {29'd0, s_parameter_words_q} << 2, + {15'd0, s_scratch_base_q}, + {15'd0, s_scratch_bytes_q} + ) && range_valid( + ((s_opcode_q == `APB4_APU__MC_KERNEL_RESAMPLE) && (s_aux_q[3:0] != 4'd0)) ? + s_resample_input_start[31:0] : s_input_addr, + s_input_bytes, + {15'd0, s_scratch_base_q}, + {15'd0, s_scratch_bytes_q} + ) && range_valid( + s_output_addr, s_output_bytes, {15'd0, s_scratch_base_q}, {15'd0, s_scratch_bytes_q} + ); + unique case (s_opcode_q) + `APB4_APU__MC_KERNEL_REQUANT: + s_validation_ok = s_validation_ok && reference_valid( + s_parameter_q[0], + s_scale_bytes, + 1'b0, + s_scratch_base_q, + s_scratch_bytes_q, + s_table_offset_q, + s_table_bytes_q + ); + `APB4_APU__MC_KERNEL_STEREO, `APB4_APU__MC_KERNEL_DECORRELATE: + s_validation_ok = s_validation_ok && reference_valid( + s_parameter_q[0], + {17'd0, s_count_q} << 2, + 1'b1, + s_scratch_base_q, + s_scratch_bytes_q, + s_table_offset_q, + s_table_bytes_q + ); + `APB4_APU__MC_KERNEL_IMDCT6, `APB4_APU__MC_KERNEL_IMDCT18: + s_validation_ok = s_validation_ok && reference_valid( + s_parameter_q[0], + s_transform_bytes, + 1'b0, + s_scratch_base_q, + s_scratch_bytes_q, + s_table_offset_q, + s_table_bytes_q + ); + `APB4_APU__MC_KERNEL_DCT32_POLY: + s_validation_ok = s_validation_ok && (s_parameter_q[3][31:4] == 28'd0) && reference_valid( + s_parameter_q[0], + 33'd4096, + 1'b0, + s_scratch_base_q, + s_scratch_bytes_q, + s_table_offset_q, + s_table_bytes_q + ) && reference_valid( + s_parameter_q[1], + 33'd2048, + 1'b0, + s_scratch_base_q, + s_scratch_bytes_q, + s_table_offset_q, + s_table_bytes_q + ) && reference_valid( + s_parameter_q[2], + 33'd2048, + 1'b1, + s_scratch_base_q, + s_scratch_bytes_q, + s_table_offset_q, + s_table_bytes_q + ); + `APB4_APU__MC_KERNEL_FIXED: + s_validation_ok = s_validation_ok && reference_valid( + s_parameter_q[0], + (s_aux_q[2:0] == 3'd0) ? 33'd0 : ({30'd0, s_aux_q[2:0]} << 2), + 1'b1, + s_scratch_base_q, + s_scratch_bytes_q, + s_table_offset_q, + s_table_bytes_q + ); + `APB4_APU__MC_KERNEL_LPC: + s_validation_ok = s_validation_ok && ($signed( + s_parameter_q[2] + ) >= -32'sd31) && ($signed( + s_parameter_q[2] + ) <= 32'sd31) && reference_valid( + s_parameter_q[0], + s_history_bytes, + 1'b0, + s_scratch_base_q, + s_scratch_bytes_q, + s_table_offset_q, + s_table_bytes_q + ) && reference_valid( + s_parameter_q[1], + s_history_bytes, + 1'b1, + s_scratch_base_q, + s_scratch_bytes_q, + s_table_offset_q, + s_table_bytes_q + ); + `APB4_APU__MC_KERNEL_RESAMPLE: + s_validation_ok = s_validation_ok && (s_resample_channels inside {32'd1, 32'd2}) && + ((s_aux_q[3:0] == 4'd0) || + (!s_resample_input_start[32] && + ({1'b0, s_input_addr} >= s_resample_history_bytes))) && + ((s_aux_q[3:0] == 4'd0) || reference_valid( + s_parameter_q[(s_aux_q[3:0] inside {4'd1, 4'd2})?2 : ((s_aux_q[3:0]==4'd14)?1 : 0)], + 33'd2048, + 1'b0, + s_scratch_base_q, + s_scratch_bytes_q, + s_table_offset_q, + s_table_bytes_q + )); + default: + s_validation_ok = s_validation_ok && (s_parameter_q[1] inside {32'd1, 32'd2}) && + (!s_aux_q[2] || (s_parameter_q[1] == 32'd1)); + endcase + end + + always_comb begin + memory_req_o = 1'b0; + memory_write_o = 1'b0; + memory_addr_o = 17'd0; + memory_data_o = 32'd0; + memory_strb_o = 4'hf; + unique case (s_state_q) + ParameterRequest: begin + memory_req_o = 1'b1; + memory_addr_o = 17'(s_parameter_addr + ({28'd0, s_parameter_index_q} << 2)); + end + RequantInputRequest, StereoFirstRequest, PredictInputRequest, PcmInputRequest: begin + memory_req_o = 1'b1; + memory_addr_o = 17'(s_input_addr + (s_element_q << 2)); + end + RequantInputWait, RequantScaleRequest: begin + memory_req_o = 1'b1; + memory_addr_o = 17'(s_reference_addr[0] + (s_element_q << 2)); + end + RequantWrite, PredictWrite: begin + memory_req_o = 1'b1; + memory_write_o = 1'b1; + memory_addr_o = 17'(s_output_addr + (s_element_q << 2)); + memory_data_o = s_reconstruction_result0; + end + StereoFirstWait, StereoSecondRequest: begin + memory_req_o = 1'b1; + memory_addr_o = 17'(s_reference_addr[0] + (s_element_q << 2)); + end + StereoSecondWait: begin + memory_req_o = memory_valid_i && + !((s_opcode_q == `APB4_APU__MC_KERNEL_DECORRELATE) && s_stereo_live_overflow); + memory_write_o = memory_req_o; + memory_addr_o = 17'(s_output_addr + (s_element_q << 2)); + memory_data_o = s_stereo_live_result0; + end + StereoWriteFirst, StereoWriteSecond: begin + memory_req_o = 1'b1; + memory_write_o = 1'b1; + memory_addr_o = 17'(s_output_addr + (((s_state_q == StereoWriteSecond) ? + ({16'd0, s_count_q} + s_element_q) : s_element_q) << 2)); + memory_data_o = (s_state_q == StereoWriteSecond) ? + s_reconstruction_result1 : s_reconstruction_result0; + end + HistoryRequest: begin + memory_req_o = s_read_issue_q < s_predict_order; + memory_addr_o = 17'(s_reference_addr[(s_opcode_q == + `APB4_APU__MC_KERNEL_LPC) ? 1 : 0] + (s_read_issue_q << 2)); + end + CoefficientRequest: begin + memory_req_o = s_read_issue_q < s_predict_order; + memory_addr_o = 17'(s_reference_addr[0] + (s_read_issue_q << 2)); + end + HistoryWrite: begin + memory_req_o = 1'b1; + memory_write_o = 1'b1; + memory_addr_o = 17'(s_reference_addr[(s_opcode_q == + `APB4_APU__MC_KERNEL_LPC) ? 1 : 0] + (s_tap_q << 2)); + memory_data_o = s_history_q[s_tap_q[4:0]]; + end + TransformInputRequest: begin + memory_req_o = s_read_issue_q < s_transform_order; + memory_addr_o = 17'(s_input_addr + + (((s_block_q * s_transform_order) + s_read_issue_q) << 2)); + end + TransformCoefficientRequest: begin + memory_req_o = s_transform_final_return || (s_read_issue_q < s_transform_order); + memory_write_o = s_transform_final_return; + memory_addr_o = s_transform_final_return ? + ((s_opcode_q == `APB4_APU__MC_KERNEL_DCT32_POLY) ? + 17'(s_reference_addr[2] + + (((s_parameter_q[3][3:0] * 32) + s_row_q) << 2)) : + 17'(s_output_addr + + (((s_block_q * s_transform_order * 2) + s_row_q) << 2))) : + 17'(s_reference_addr[0] + + (((s_row_q * s_transform_order) + s_read_issue_q) << 2)); + memory_data_o = s_transform_result; + end + TransformWrite: begin + memory_req_o = 1'b1; + memory_write_o = 1'b1; + memory_addr_o = (s_opcode_q == `APB4_APU__MC_KERNEL_DCT32_POLY) ? + 17'(s_reference_addr[2] + (((s_parameter_q[3][3:0] * 32) + s_row_q) << 2)) : + 17'(s_output_addr + (((s_block_q * s_transform_order * 2) + s_row_q) << 2)); + memory_data_o = s_transform_result; + end + DctHistoryRequest: begin + memory_req_o = s_tap_q < 32'd16; + memory_addr_o = s_pair_issue_second_q ? + 17'(s_reference_addr[1] + (((s_tap_q * 32) + s_row_q) << 2)) : + 17'(s_reference_addr[2] + + ((({28'd0, s_dct_history_phase} * 32'd32) + s_row_q) << 2)); + end + DctOutputWrite: begin + memory_req_o = 1'b1; + memory_write_o = 1'b1; + memory_addr_o = 17'(s_output_addr + (((s_block_q * 32) + s_row_q) << 2)); + memory_data_o = sat32(rne72(s_accumulator_q, 6'd30)); + end + DctPhaseWrite: begin + memory_req_o = 1'b1; + memory_write_o = 1'b1; + memory_addr_o = 17'(s_parameter_addr + 32'd12); + memory_data_o = {28'd0, s_parameter_q[3][3:0] + 1'b1}; + end + ResampleCopyRequest: begin + memory_req_o = 1'b1; + memory_addr_o = 17'(s_input_addr + (s_element_q << 2)); + end + ResampleCopyWait, ResampleCopyWrite: begin + memory_req_o = (s_state_q == ResampleCopyWrite) || memory_valid_i; + memory_write_o = memory_req_o; + memory_addr_o = 17'(s_output_addr + (s_element_q << 2)); + memory_data_o = (s_state_q == ResampleCopyWrite) ? s_value0_q : memory_data_i; + end + ResampleSampleRequest: begin + memory_req_o = (s_resample_source_index < s_input_base_q + {48'd0, s_count_q}) && + (s_tap_q < 32'd16); + memory_addr_o = s_pair_issue_second_q ? + 17'(s_reference_addr[(s_aux_q[3:0] inside {4'd1, 4'd2}) ? 2 : + ((s_aux_q[3:0] == 4'd14) ? 1 : 0)] + + (((s_resample_phase * 16) + s_tap_q) << 2)) : + 17'(s_input_addr + + (((s_resample_source_index - s_input_base_q + {32'd0, s_tap_q} - 64'd7) * + {32'd0, s_resample_channels} + {32'd0, s_channel_q}) << 2)); + end + ResampleWrite: begin + memory_req_o = 1'b1; + memory_write_o = 1'b1; + memory_addr_o = 17'(s_output_addr + + (((s_produced_q * s_resample_channels) + s_channel_q) << 2)); + memory_data_o = sat32(rne72(s_accumulator_q, 6'd30)); + end + ResampleStateWrite: begin + memory_req_o = 1'b1; + memory_write_o = 1'b1; + memory_addr_o = 17'(s_parameter_addr + 32'd12 + (s_tap_q << 2)); + unique case (s_tap_q[1:0]) + 2'd0: memory_data_o = s_next_output_q[31:0]; + 2'd1: memory_data_o = s_next_output_q[63:32]; + 2'd2: memory_data_o = s_input_base_q[31:0]; + default: memory_data_o = s_input_base_q[63:32]; + endcase + end + PcmWrite, PcmDuplicateWrite: begin + memory_req_o = 1'b1; + memory_write_o = 1'b1; + memory_addr_o = 17'((s_output_addr + s_produced_q) & 32'hffff_fffc); + memory_data_o = s_pcm_word; + memory_strb_o = s_pcm_strb; + end + default: begin + end + endcase + end + + always_comb begin + logic signed [63:0] s_scaled; + logic signed [31:0] s_packed; + s_scaled = rne64($signed(s_value0_q) * $signed(s_parameter_q[0]), 6'd30); + if (s_aux_q[1:0] == 2'd0) begin + if (s_scaled > 64'sd32767) s_packed = 32'sd32767; + else if (s_scaled < -64'sd32768) s_packed = -32'sd32768; + else s_packed = s_scaled[31:0]; + s_pcm_lane = 2'(s_output_addr + s_produced_q) & 2'd2; + s_pcm_word = $unsigned(s_packed) << (s_pcm_lane * 8); + s_pcm_strb = 4'b0011 << s_pcm_lane; + end else begin + if (s_scaled > 64'sd8388607) s_packed = 32'sd8388607; + else if (s_scaled < -64'sd8388608) s_packed = -32'sd8388608; + else s_packed = s_scaled[31:0]; + s_pcm_lane = 2'd0; + s_pcm_word = s_packed; + s_pcm_strb = 4'hf; + end + end + + always_ff @(posedge clk_i or negedge rst_n_i) begin + if (!rst_n_i) begin + s_state_q <= Idle; + s_opcode_q <= 4'd0; + s_aux_q <= 8'd0; + s_count_q <= 16'd0; + s_dst_q <= 4'd0; + s_input_offset_q <= 32'd0; + s_parameter_offset_q <= 32'd0; + s_output_offset_q <= 32'd0; + s_scratch_base_q <= 17'd0; + s_scratch_bytes_q <= 17'd0; + s_table_offset_q <= 16'd0; + s_table_bytes_q <= 16'd0; + s_parameter_q <= '0; + s_parameter_words_q <= 4'd0; + s_parameter_index_q <= 4'd0; + s_element_q <= 32'd0; + s_block_q <= 32'd0; + s_row_q <= 32'd0; + s_tap_q <= 32'd0; + s_channel_q <= 32'd0; + s_read_issue_q <= 32'd0; + s_read_receive_q <= 32'd0; + s_pair_issue_second_q <= 1'b0; + s_pair_return_second_q <= 1'b0; + s_transform_preload_q <= 1'b0; + s_resample_profile_valid_q <= 1'b0; + s_resample_profile_q <= 4'd0; + s_produced_q <= 32'd0; + s_cycles_q <= 32'd0; + s_value0_q <= 32'd0; + s_value1_q <= 32'd0; + s_history_value_q <= 32'd0; + s_accumulator_q <= 72'sd0; + s_next_output_q <= 64'd0; + s_input_base_q <= 64'd0; + done_o <= 1'b0; + error_o <= 1'b0; + error_code_o <= 6'd0; + error_stage_o <= 4'd0; + error_reason_o <= 8'd0; + result_dst_o <= 4'd0; + result_count_o <= 32'd0; + cycles_o <= 32'd0; + for (int index = 0; index < 32; index++) begin + s_vector_q[index] <= 32'sd0; + s_coefficient_q[index] <= 32'sd0; + s_history_q[index] <= 32'sd0; + end + end else if (flush_i) begin + s_state_q <= Idle; + s_resample_profile_valid_q <= 1'b0; + done_o <= 1'b0; + error_o <= 1'b0; + s_cycles_q <= 32'd0; + end else begin + done_o <= 1'b0; + error_o <= 1'b0; + if (s_state_q != Idle) s_cycles_q <= s_cycles_q + 1'b1; + if (memory_error_i && memory_req_o) begin + s_state_q <= Failed; + error_code_o <= `APB4_APU__ERROR_CODE_SEQUENCER; + error_stage_o <= `APB4_APU__ERROR_STAGE_LIFECYCLE; + error_reason_o <= 8'd5; + end else begin + unique case (s_state_q) + Idle: begin + if (req_valid_i) begin + s_opcode_q <= opcode_i; + s_aux_q <= aux_i; + s_count_q <= count_i; + s_dst_q <= dst_i; + s_input_offset_q <= input_offset_i; + s_parameter_offset_q <= parameter_offset_i; + s_output_offset_q <= output_offset_i; + s_scratch_base_q <= scratch_base_i; + s_scratch_bytes_q <= scratch_bytes_i; + s_table_offset_q <= table_offset_i; + s_table_bytes_q <= table_bytes_i; + s_parameter_index_q <= 4'd0; + s_cycles_q <= 32'd0; + result_dst_o <= dst_i; + unique case (opcode_i) + `APB4_APU__MC_KERNEL_LPC: s_parameter_words_q <= 4'd3; + `APB4_APU__MC_KERNEL_DCT32_POLY: s_parameter_words_q <= 4'd4; + `APB4_APU__MC_KERNEL_RESAMPLE: s_parameter_words_q <= 4'd8; + `APB4_APU__MC_KERNEL_PCM_PACK: s_parameter_words_q <= 4'd2; + default: s_parameter_words_q <= 4'd1; + endcase + s_state_q <= ParameterRequest; + end + end + ParameterRequest: s_state_q <= ParameterWait; + ParameterWait: begin + if (memory_valid_i) begin + s_parameter_q[s_parameter_index_q] <= memory_data_i; + if (s_parameter_index_q + 1'b1 >= s_parameter_words_q) begin + s_state_q <= Validate; + end else begin + s_parameter_index_q <= s_parameter_index_q + 1'b1; + s_state_q <= ParameterRequest; + end + end + end + Validate: begin + if (s_semantic_err) begin + error_code_o <= `APB4_APU__ERROR_CODE_RECONSTRUCTION; + error_stage_o <= (s_opcode_q inside {`APB4_APU__MC_KERNEL_RESAMPLE, + `APB4_APU__MC_KERNEL_PCM_PACK}) ? + `APB4_APU__ERROR_STAGE_RESAMPLER : + `APB4_APU__ERROR_STAGE_RECONSTRUCTION; + error_reason_o <= 8'd9; + s_state_q <= Failed; + end else if (!s_validation_ok) begin + error_code_o <= `APB4_APU__ERROR_CODE_SEQUENCER; + error_stage_o <= `APB4_APU__ERROR_STAGE_LIFECYCLE; + error_reason_o <= 8'd5; + s_state_q <= Failed; + end else if (s_opcode_q inside {`APB4_APU__MC_KERNEL_IMDCT6, + `APB4_APU__MC_KERNEL_IMDCT18, + `APB4_APU__MC_KERNEL_DCT32_POLY}) begin + s_transform_preload_q <= 1'b1; + s_read_issue_q <= 32'd0; + s_read_receive_q <= 32'd0; + s_state_q <= TransformInputRequest; + end else if (s_opcode_q == `APB4_APU__MC_KERNEL_LPC) begin + s_read_issue_q <= 32'd0; + s_read_receive_q <= 32'd0; + s_state_q <= CoefficientRequest; + end else if ((s_opcode_q == `APB4_APU__MC_KERNEL_FIXED) && + (s_predict_order != 32'd0)) begin + s_read_issue_q <= 32'd0; + s_read_receive_q <= 32'd0; + s_state_q <= HistoryRequest; + end else begin + s_state_q <= Ready; + end + end + Ready: begin + if (req_valid_i) begin + s_element_q <= 32'd0; + s_block_q <= 32'd0; + s_row_q <= 32'd0; + s_tap_q <= 32'd0; + s_channel_q <= 32'd0; + s_produced_q <= 32'd0; + s_read_issue_q <= 32'd0; + s_read_receive_q <= 32'd0; + s_pair_issue_second_q <= 1'b0; + s_pair_return_second_q <= 1'b0; + s_transform_preload_q <= 1'b0; + s_cycles_q <= 32'd1; + unique case (s_opcode_q) + `APB4_APU__MC_KERNEL_REQUANT: s_state_q <= RequantInputRequest; + `APB4_APU__MC_KERNEL_STEREO, `APB4_APU__MC_KERNEL_DECORRELATE: + s_state_q <= StereoFirstRequest; + `APB4_APU__MC_KERNEL_IMDCT6, + `APB4_APU__MC_KERNEL_IMDCT18, + `APB4_APU__MC_KERNEL_DCT32_POLY: + s_state_q <= TransformCoefficientRequest; + `APB4_APU__MC_KERNEL_FIXED, `APB4_APU__MC_KERNEL_LPC: + s_state_q <= PredictInputRequest; + `APB4_APU__MC_KERNEL_RESAMPLE: begin + s_resample_profile_valid_q <= 1'b1; + s_resample_profile_q <= s_aux_q[3:0]; + s_next_output_q <= {s_parameter_q[4], s_parameter_q[3]}; + s_input_base_q <= {s_parameter_q[6], s_parameter_q[5]}; + s_state_q <= (s_aux_q[3:0] == 4'd0) ? ResampleCopyRequest : ResampleSampleRequest; + end + default: s_state_q <= PcmInputRequest; + endcase + end + end + RequantInputRequest: s_state_q <= RequantInputWait; + RequantInputWait: + if (memory_valid_i) begin + s_value0_q <= memory_data_i; + s_state_q <= RequantScaleWait; + end + RequantScaleRequest: s_state_q <= RequantScaleWait; + RequantScaleWait: + if (memory_valid_i) begin + s_coefficient_q[0] <= memory_data_i; + s_state_q <= RequantWrite; + end + RequantWrite: begin + if (s_element_q + 1'b1 >= s_count_q) begin + s_produced_q <= {16'd0, s_count_q}; + s_state_q <= Finish; + end else begin + s_element_q <= s_element_q + 1'b1; + s_state_q <= RequantInputRequest; + end + end + StereoFirstRequest: s_state_q <= StereoFirstWait; + StereoFirstWait: + if (memory_valid_i) begin + s_value0_q <= memory_data_i; + s_state_q <= StereoSecondWait; + end + StereoSecondRequest: s_state_q <= StereoSecondWait; + StereoSecondWait: + if (memory_valid_i) begin + s_value1_q <= memory_data_i; + if ((s_opcode_q == `APB4_APU__MC_KERNEL_DECORRELATE) && s_stereo_live_overflow) begin + error_code_o <= `APB4_APU__ERROR_CODE_RECONSTRUCTION; + error_stage_o <= `APB4_APU__ERROR_STAGE_RECONSTRUCTION; + error_reason_o <= 8'd9; + s_state_q <= Failed; + end else begin + s_state_q <= StereoWriteSecond; + end + end + StereoWriteSecond: begin + if (s_element_q + 1'b1 >= s_count_q) begin + s_produced_q <= {15'd0, s_count_q, 1'b0}; + s_state_q <= Finish; + end else begin + s_element_q <= s_element_q + 1'b1; + s_state_q <= StereoFirstRequest; + end + end + HistoryRequest: begin + if (memory_req_o) s_read_issue_q <= s_read_issue_q + 1'b1; + if (memory_valid_i) begin + s_history_q[s_read_receive_q[4:0]] <= memory_data_i; + if (s_read_receive_q + 1'b1 >= s_predict_order) begin + s_read_issue_q <= 32'd0; + s_read_receive_q <= 32'd0; + s_state_q <= Ready; + end else begin + s_read_receive_q <= s_read_receive_q + 1'b1; + end + end + end + CoefficientRequest: begin + if (memory_req_o) s_read_issue_q <= s_read_issue_q + 1'b1; + if (memory_valid_i) begin + s_coefficient_q[s_read_receive_q[4:0]] <= memory_data_i; + if (s_read_receive_q + 1'b1 >= s_predict_order) begin + s_read_issue_q <= 32'd0; + s_read_receive_q <= 32'd0; + s_state_q <= HistoryRequest; + end else begin + s_read_receive_q <= s_read_receive_q + 1'b1; + end + end + end + PredictInputRequest: s_state_q <= PredictInputWait; + PredictInputWait: + if (memory_valid_i) begin + s_value0_q <= memory_data_i; + s_state_q <= PredictWrite; + end + PredictWrite: begin + if (s_reconstruction_overflow) begin + error_code_o <= `APB4_APU__ERROR_CODE_RECONSTRUCTION; + error_stage_o <= `APB4_APU__ERROR_STAGE_RECONSTRUCTION; + error_reason_o <= 8'd9; + s_state_q <= Failed; + end else begin + for (int index = 31; index > 0; index--) begin + if (index < s_predict_order) begin + s_history_q[index] <= s_history_q[index-1]; + end + end + if (s_predict_order != 32'd0) begin + s_history_q[0] <= s_reconstruction_result0; + end + if (s_element_q + 1'b1 >= s_count_q) begin + s_tap_q <= 32'd0; + s_state_q <= (s_predict_order == 32'd0) ? Finish : HistoryWrite; + s_produced_q <= {16'd0, s_count_q}; + end else begin + s_element_q <= s_element_q + 1'b1; + s_state_q <= PredictInputRequest; + end + end + end + HistoryWrite: begin + if (s_tap_q + 1'b1 >= s_predict_order) begin + s_state_q <= Finish; + end else begin + s_tap_q <= s_tap_q + 1'b1; + end + end + TransformInputRequest: begin + if (memory_req_o) s_read_issue_q <= s_read_issue_q + 1'b1; + if (memory_valid_i) begin + s_vector_q[s_read_receive_q[4:0]] <= memory_data_i; + if (s_read_receive_q + 1'b1 >= s_transform_order) begin + s_read_issue_q <= 32'd0; + s_read_receive_q <= 32'd0; + s_row_q <= 32'd0; + if (s_transform_preload_q) begin + s_transform_preload_q <= 1'b0; + s_state_q <= Ready; + end else begin + s_state_q <= TransformCoefficientRequest; + end + end else begin + s_read_receive_q <= s_read_receive_q + 1'b1; + end + end + end + TransformCoefficientRequest: begin + if (memory_req_o) s_read_issue_q <= s_read_issue_q + 1'b1; + if (memory_valid_i) begin + s_coefficient_q[s_read_receive_q[4:0]] <= memory_data_i; + if (s_read_receive_q + 1'b1 >= s_transform_order) begin + s_read_issue_q <= 32'd0; + s_read_receive_q <= 32'd0; + if (s_transform_overflow) begin + error_code_o <= `APB4_APU__ERROR_CODE_OVERFLOW; + error_stage_o <= `APB4_APU__ERROR_STAGE_RECONSTRUCTION; + error_reason_o <= 8'd9; + s_state_q <= Failed; + end else if (s_row_q + 1'b1 >= ((s_opcode_q == + `APB4_APU__MC_KERNEL_DCT32_POLY) ? 32'd32 : + (s_transform_order << 1))) begin + s_row_q <= 32'd0; + if (s_opcode_q == `APB4_APU__MC_KERNEL_DCT32_POLY) begin + s_tap_q <= 32'd0; + s_accumulator_q <= 72'sd0; + s_pair_issue_second_q <= 1'b0; + s_pair_return_second_q <= 1'b0; + s_state_q <= DctHistoryRequest; + end else if (s_block_q + 1'b1 >= s_count_q) begin + s_produced_q <= s_count_q * (s_transform_order << 1); + s_state_q <= Finish; + end else begin + s_block_q <= s_block_q + 1'b1; + s_state_q <= TransformInputRequest; + end + end else begin + s_row_q <= s_row_q + 1'b1; + s_state_q <= TransformCoefficientRequest; + end + end else begin + s_read_receive_q <= s_read_receive_q + 1'b1; + end + end + end + TransformWrite: begin + if (s_transform_overflow) begin + error_code_o <= `APB4_APU__ERROR_CODE_OVERFLOW; + error_stage_o <= `APB4_APU__ERROR_STAGE_RECONSTRUCTION; + s_state_q <= Failed; + end else if (s_row_q + 1'b1 >= ((s_opcode_q == + `APB4_APU__MC_KERNEL_DCT32_POLY) ? 32'd32 : + (s_transform_order << 1))) begin + s_row_q <= 32'd0; + if (s_opcode_q == `APB4_APU__MC_KERNEL_DCT32_POLY) begin + s_tap_q <= 32'd0; + s_accumulator_q <= 72'sd0; + s_pair_issue_second_q <= 1'b0; + s_pair_return_second_q <= 1'b0; + s_state_q <= DctHistoryRequest; + end else if (s_block_q + 1'b1 >= s_count_q) begin + s_produced_q <= s_count_q * (s_transform_order << 1); + s_state_q <= Finish; + end else begin + s_block_q <= s_block_q + 1'b1; + s_tap_q <= 32'd0; + s_state_q <= TransformInputRequest; + end + end else begin + s_row_q <= s_row_q + 1'b1; + s_tap_q <= 32'd0; + s_state_q <= TransformCoefficientRequest; + end + end + DctHistoryRequest: begin + if (memory_req_o) begin + s_pair_return_second_q <= s_pair_issue_second_q; + s_pair_issue_second_q <= !s_pair_issue_second_q; + if (s_pair_issue_second_q) s_tap_q <= s_tap_q + 1'b1; + end + if (memory_valid_i) begin + if (s_pair_return_second_q) begin + s_accumulator_q <= s_accumulator_q + $signed( + s_history_value_q + ) * $signed( + memory_data_i + ); + if (s_tap_q == 32'd16) s_state_q <= DctOutputWrite; + end else begin + s_history_value_q <= memory_data_i; + end + end + end + DctOutputWrite: begin + s_tap_q <= 32'd0; + s_accumulator_q <= 72'sd0; + s_pair_issue_second_q <= 1'b0; + s_pair_return_second_q <= 1'b0; + if (s_row_q == 32'd31) begin + s_state_q <= DctPhaseWrite; + end else begin + s_row_q <= s_row_q + 1'b1; + s_state_q <= DctHistoryRequest; + end + end + DctPhaseWrite: begin + s_parameter_q[3][3:0] <= s_parameter_q[3][3:0] + 1'b1; + if (s_block_q + 1'b1 >= s_count_q) begin + s_produced_q <= s_count_q * 32; + s_state_q <= Finish; + end else begin + s_block_q <= s_block_q + 1'b1; + s_tap_q <= 32'd0; + s_state_q <= TransformInputRequest; + end + end + ResampleCopyRequest: s_state_q <= ResampleCopyWait; + ResampleCopyWait: + if (memory_valid_i) begin + if (s_element_q + 1'b1 >= s_count_q * s_resample_channels) begin + s_next_output_q <= s_next_output_q + {48'd0, s_count_q}; + s_input_base_q <= s_input_base_q + {48'd0, s_count_q}; + s_produced_q <= {16'd0, s_count_q}; + s_tap_q <= 32'd0; + s_state_q <= ResampleStateWrite; + end else begin + s_element_q <= s_element_q + 1'b1; + s_state_q <= ResampleCopyRequest; + end + end + ResampleSampleRequest: begin + if (s_resample_source_index >= s_input_base_q + {48'd0, s_count_q}) begin + s_input_base_q <= s_input_base_q + {48'd0, s_count_q}; + s_tap_q <= 32'd0; + s_state_q <= ResampleStateWrite; + end else begin + if (memory_req_o) begin + s_pair_return_second_q <= s_pair_issue_second_q; + s_pair_issue_second_q <= !s_pair_issue_second_q; + if (s_pair_issue_second_q) s_tap_q <= s_tap_q + 1'b1; + end + if (memory_valid_i) begin + if (s_pair_return_second_q) begin + s_accumulator_q <= s_accumulator_q + $signed(s_value0_q) * $signed(memory_data_i); + if (s_tap_q == 32'd16) s_state_q <= ResampleWrite; + end else begin + s_value0_q <= memory_data_i; + end + end + end + end + ResampleWrite: begin + s_tap_q <= 32'd0; + s_accumulator_q <= 72'sd0; + s_pair_issue_second_q <= 1'b0; + s_pair_return_second_q <= 1'b0; + if (s_channel_q + 1'b1 >= s_resample_channels) begin + s_channel_q <= 32'd0; + s_produced_q <= s_produced_q + 1'b1; + s_next_output_q <= s_next_output_q + {48'd0, s_ratio_m}; + end else begin + s_channel_q <= s_channel_q + 1'b1; + end + s_state_q <= ResampleSampleRequest; + end + ResampleStateWrite: begin + if (s_tap_q == 32'd3) begin + s_state_q <= Finish; + end else begin + s_tap_q <= s_tap_q + 1'b1; + end + end + PcmInputRequest: s_state_q <= PcmInputWait; + PcmInputWait: + if (memory_valid_i) begin + s_value0_q <= memory_data_i; + s_state_q <= PcmWrite; + end + PcmWrite: begin + s_produced_q <= s_produced_q + ((s_aux_q[1:0] == 2'd0) ? 32'd2 : 32'd4); + if (s_aux_q[2]) begin + s_state_q <= PcmDuplicateWrite; + end else if (s_element_q + 1'b1 >= s_count_q * s_parameter_q[1]) begin + s_state_q <= Finish; + end else begin + s_element_q <= s_element_q + 1'b1; + s_state_q <= PcmInputRequest; + end + end + PcmDuplicateWrite: begin + s_produced_q <= s_produced_q + ((s_aux_q[1:0] == 2'd0) ? 32'd2 : 32'd4); + if (s_element_q + 1'b1 >= s_count_q * s_parameter_q[1]) begin + s_state_q <= Finish; + end else begin + s_element_q <= s_element_q + 1'b1; + s_state_q <= PcmInputRequest; + end + end + Finish: begin + done_o <= 1'b1; + result_dst_o <= s_dst_q; + result_count_o <= s_produced_q; + unique case (s_opcode_q) + `APB4_APU__MC_KERNEL_REQUANT: cycles_o <= 32'd8 + ({16'd0, s_count_q} << 2); + `APB4_APU__MC_KERNEL_STEREO, `APB4_APU__MC_KERNEL_DECORRELATE: + cycles_o <= 32'd8 + ({16'd0, s_count_q} << 2); + `APB4_APU__MC_KERNEL_IMDCT6: cycles_o <= 32'd8 + (s_count_q * 32'd94); + `APB4_APU__MC_KERNEL_IMDCT18: cycles_o <= 32'd8 + (s_count_q * 32'd706); + `APB4_APU__MC_KERNEL_DCT32_POLY: cycles_o <= 32'd8 + (s_count_q * 32'd2304); + `APB4_APU__MC_KERNEL_FIXED: + cycles_o <= 32'd8 + (s_count_q * ({29'd0, s_aux_q[2:0]} + 32'd4)); + `APB4_APU__MC_KERNEL_LPC: + cycles_o <= 32'd8 + (s_count_q * (({26'd0, s_aux_q[5:0]} << 1) + 32'd5)); + `APB4_APU__MC_KERNEL_RESAMPLE: + cycles_o <= 32'd8 + ((s_count_q * s_resample_channels) << 1) + + ((s_aux_q[3:0] == 4'd0) ? 32'd0 : + (s_produced_q * s_resample_channels * 32'd36)); + default: + cycles_o <= 32'd8 + (s_produced_q * 32'd5 / ((s_aux_q[1:0] == 2'd0) ? 32'd2 : 32'd4)); + endcase + s_state_q <= Idle; + end + Failed: begin + done_o <= 1'b1; + error_o <= 1'b1; + cycles_o <= s_cycles_q; + s_state_q <= Idle; + end + default: s_state_q <= Idle; + endcase + end + end + end +endmodule diff --git a/rtl/ip/multimedia/apu_local_sram.sv b/rtl/ip/multimedia/apu_local_sram.sv new file mode 100644 index 00000000..a4e20018 --- /dev/null +++ b/rtl/ip/multimedia/apu_local_sram.sv @@ -0,0 +1,149 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +`include "apu_define.svh" + +module apu_local_sram #( + parameter int unsigned MemoryWordCount = `APB4_APU__LOCAL_DATA_BYTES / 4 +) ( + // verilog_format: off -- preserve loader and codec port columns + input logic clk_i, + input logic rst_n_i, + input logic image_valid_i, + input logic [15:0] table_bytes_i, + input logic epoch_clear_i, + input logic loader_active_i, + input logic loader_req_i, + input logic [16:0] loader_addr_i, + input logic [31:0] loader_data_i, + input logic [ 3:0] loader_strb_i, + output logic loader_ready_o, + input logic codec_req_i, + input logic codec_write_i, + input logic [16:0] codec_addr_i, + input logic [31:0] codec_data_i, + input logic [ 3:0] codec_strb_i, + output logic codec_ready_o, + output logic [31:0] codec_data_o, + output logic codec_valid_o, + output logic codec_access_err_o + // verilog_format: on +); + localparam logic [14:0] MemoryWordCountValue = 15'(MemoryWordCount); + + logic s_req; + logic s_write; + logic [ 16:0] s_addr; + logic [ 31:0] s_write_data; + logic [ 3:0] s_write_strb; + logic [ 14:0] s_word_addr; + logic s_codec_range_ok; + logic s_codec_table; + logic s_codec_mutable_valid; + logic s_codec_access_err; + logic [MemoryWordCount-1:0] s_mutable_valid_q; + logic s_read_q, s_read_codec_q, s_read_err_q; + logic [31:0] s_read_data; + logic s_unused; + + assign loader_ready_o = loader_active_i; + assign codec_ready_o = !loader_active_i; + assign s_req = loader_active_i ? loader_req_i : codec_req_i; + assign s_write = loader_active_i || codec_write_i; + assign s_addr = loader_active_i ? loader_addr_i : codec_addr_i; + assign s_write_data = loader_active_i ? loader_data_i : codec_data_i; + assign s_write_strb = loader_active_i ? loader_strb_i : codec_strb_i; + assign s_word_addr = s_addr[16:2]; + assign s_codec_range_ok = (codec_addr_i[1:0] == 2'd0) && + ((codec_addr_i < `APB4_APU__LOCAL_CODEC_BYTES) || + ((codec_addr_i >= `APB4_APU__LOCAL_INTERNAL_BASE) && + (codec_addr_i < `APB4_APU__LOCAL_DATA_BYTES))); + assign s_codec_table = codec_addr_i < {1'b0, table_bytes_i}; + assign s_codec_mutable_valid = (s_word_addr < MemoryWordCountValue) && + s_mutable_valid_q[s_word_addr]; + assign s_codec_access_err = codec_req_i && + ((s_word_addr >= MemoryWordCountValue) || !s_codec_range_ok || + (codec_write_i && s_codec_table) || + (!codec_write_i && (s_codec_table ? !image_valid_i : !s_codec_mutable_valid))); + assign codec_data_o = s_read_data; + assign codec_valid_o = s_read_q && s_read_codec_q; + assign codec_access_err_o = (codec_req_i && codec_ready_o && s_codec_access_err) || + (codec_valid_o && s_read_err_q); + +`ifdef HAVE_SRAM_MACRO + localparam int unsigned BankCount = 28; + + logic [ 4:0] s_bank; + logic [ 9:0] s_bank_addr; + logic [BankCount-1:0][31:0] s_bank_data; + logic [ 4:0] s_read_bank_q; + + assign s_bank = s_word_addr[14:10]; + assign s_bank_addr = s_word_addr[9:0]; + + for (genvar bank = 0; bank < BankCount; bank++) begin : gen_local_bank + tc_sram_1024x32 u_local_sram ( + .clk_i (clk_i), + .cs_i (s_req && (s_bank == 5'(bank)) && (loader_active_i || !s_codec_access_err)), + .addr_i(s_bank_addr), + .data_i(s_write_data), + .mask_i(s_write_strb), + .wren_i(s_write), + .data_o(s_bank_data[bank]) + ); + end + + assign s_read_data = s_bank_data[s_read_bank_q]; + + always_ff @(posedge clk_i or negedge rst_n_i) begin + if (!rst_n_i) begin + s_read_bank_q <= 5'd0; + end else if (s_req && !s_write && (loader_active_i || !s_codec_access_err)) begin + s_read_bank_q <= s_bank; + end + end +`else + logic [31:0] mem [0:MemoryWordCount-1]; + logic [31:0] s_read_data_q; + + assign s_read_data = s_read_data_q; + + always_ff @(posedge clk_i) begin + if (s_req && (loader_active_i || !s_codec_access_err)) begin + if (s_write) begin + if (s_write_strb[0]) mem[s_word_addr][7:0] <= s_write_data[7:0]; + if (s_write_strb[1]) mem[s_word_addr][15:8] <= s_write_data[15:8]; + if (s_write_strb[2]) mem[s_word_addr][23:16] <= s_write_data[23:16]; + if (s_write_strb[3]) mem[s_word_addr][31:24] <= s_write_data[31:24]; + end else begin + s_read_data_q <= mem[s_word_addr]; + end + end + end +`endif + + always_ff @(posedge clk_i or negedge rst_n_i) begin + if (!rst_n_i) begin + for (int word_index = 0; word_index < MemoryWordCount; word_index++) begin + s_mutable_valid_q[word_index] <= 1'b0; + end + s_read_q <= 1'b0; + s_read_codec_q <= 1'b0; + s_read_err_q <= 1'b0; + end else begin + s_read_q <= s_req && !s_write; + s_read_codec_q <= !loader_active_i; + s_read_err_q <= !loader_active_i && s_codec_access_err; + if (epoch_clear_i) begin + for (int word_index = 0; word_index < MemoryWordCount; word_index++) begin + s_mutable_valid_q[word_index] <= 1'b0; + end + end else if (!loader_active_i && codec_req_i && codec_ready_o && codec_write_i && + !s_codec_access_err && (s_word_addr < MemoryWordCountValue)) begin + s_mutable_valid_q[s_word_addr] <= 1'b1; + end + end + end + + assign s_unused = ^s_addr[1:0] && 1'b0; +endmodule diff --git a/rtl/ip/multimedia/apu_microcode_loader.sv b/rtl/ip/multimedia/apu_microcode_loader.sv index dc489be9..be690f62 100644 --- a/rtl/ip/multimedia/apu_microcode_loader.sv +++ b/rtl/ip/multimedia/apu_microcode_loader.sv @@ -4,7 +4,8 @@ `include "apu_define.svh" module apu_microcode_loader #( - parameter int unsigned PathStackDepth = 2048 + parameter int unsigned PathStackDepth = 2048, + parameter bit EnableP4 = 1'b0 ) ( // verilog_format: off -- preserve command, DMA, store, and result columns input logic clk_i, @@ -39,6 +40,11 @@ module apu_microcode_loader #( output logic [63:0] store_data_o, input logic [63:0] store_data_i, input logic store_valid_i, + output logic local_write_o, + output logic [16:0] local_addr_o, + output logic [31:0] local_data_o, + output logic [3:0] local_strb_o, + output logic [15:0] table_bytes_o, output logic [7:0] stat_o, output logic [31:0] abi_o, output logic [63:0] build_id_o, @@ -50,6 +56,11 @@ module apu_microcode_loader #( output logic [2:0][10:0] entry_last_o, output logic [2:0][15:0] entry_max_loop_o, output logic [2:0][23:0] entry_max_retired_o, + output logic [2:0][16:0] entry_scratch_base_o, + output logic [2:0][16:0] entry_scratch_bytes_o, + output logic [2:0][31:0] entry_primitive_mask_o, + output logic [2:0][15:0] entry_table_offset_o, + output logic [2:0][15:0] entry_table_bytes_o, output logic load_done_o, output logic abort_done_o, output logic fault_valid_o, @@ -63,7 +74,7 @@ module apu_microcode_loader #( ); import apu_microcode_pkg::*; - localparam logic [11:0] PathStackLimit = PathStackDepth; + localparam logic [11:0] PathStackLimit = 12'(PathStackDepth); typedef enum logic [4:0] { Idle, @@ -115,7 +126,7 @@ module apu_microcode_loader #( logic s_path_stack_cs, s_path_stack_write, s_path_stack_push; logic [10:0] s_path_stack_addr; logic [63:0] s_path_stack_write_data, s_path_stack_read_data; - logic s_path_stop_valid_q; + logic s_path_stop_valid_q; logic [10:0] s_path_stop_pc_q; logic s_scan_control_err_q, s_scan_capability_err_q; logic [31:0] s_scan_control_addr_q, s_scan_control_detail_q; @@ -123,6 +134,7 @@ module apu_microcode_loader #( logic s_cancel_resource_q, s_cancel_abort_q; logic s_header_err, s_header_range_err, s_range_err, s_table_err, s_capability_err; + logic s_table_header_err; logic [31:0] s_header_addr, s_header_detail; logic [31:0] s_header_range_addr, s_header_range_detail; logic [31:0] s_range_addr, s_range_detail; @@ -136,7 +148,7 @@ module apu_microcode_loader #( logic [ 4:0] s_entry_word_relative; logic [63:0] s_scan_instruction; logic [3:0] s_scan_class, s_scan_opcode, s_scan_predicate; - logic [ 1:0] s_scan_aux; + logic [ 7:0] s_scan_aux; logic [10:0] s_scan_immediate; logic s_scan_last; logic s_scan_instruction_err, s_scan_target_err, s_scan_call_err, s_scan_loop_err; @@ -172,6 +184,12 @@ module apu_microcode_loader #( }) ? s_scan_pc_q : s_instruction_word_relative[11:1]; assign store_data_o = {dma_data_i, s_instruction_low_q}; + assign local_write_o = EnableP4 && (s_state_q == PayloadReceive) && s_data_accept && + s_data_in_table; + assign local_addr_o = 17'(s_byte_offset_q - s_header_q[5]); + assign local_data_o = dma_data_i; + assign local_strb_o = dma_keep_i; + assign table_bytes_o = s_header_q[6][15:0]; assign idle_o = s_state_q == Idle; assign stat_o = { s_err_bits_q[5], @@ -190,11 +208,16 @@ module apu_microcode_loader #( assign load_count_o = s_load_count_q; for (genvar entry = 0; entry < 3; entry++) begin : gen_entry_outputs - assign entry_pc_o[entry] = s_entry_word_q[entry*8][14:4]; - assign entry_first_o[entry] = s_entry_word_q[(entry*8)+1][10:0]; - assign entry_last_o[entry] = s_entry_word_q[(entry*8)+1][26:16]; - assign entry_max_loop_o[entry] = s_entry_word_q[(entry*8)+4][15:0]; - assign entry_max_retired_o[entry] = s_entry_word_q[(entry*8)+5][23:0]; + assign entry_pc_o[entry] = s_entry_word_q[entry*8][14:4]; + assign entry_first_o[entry] = s_entry_word_q[(entry*8)+1][10:0]; + assign entry_last_o[entry] = s_entry_word_q[(entry*8)+1][26:16]; + assign entry_max_loop_o[entry] = s_entry_word_q[(entry*8)+4][15:0]; + assign entry_max_retired_o[entry] = s_entry_word_q[(entry*8)+5][23:0]; + assign entry_scratch_base_o[entry] = s_entry_word_q[(entry*8)+2][16:0]; + assign entry_scratch_bytes_o[entry] = s_entry_word_q[(entry*8)+3][16:0]; + assign entry_primitive_mask_o[entry] = s_entry_word_q[(entry*8)+6]; + assign entry_table_offset_o[entry] = s_entry_word_q[(entry*8)+7][15:0]; + assign entry_table_bytes_o[entry] = s_entry_word_q[(entry*8)+7][31:16]; end assign s_instruction_end = {1'b0, s_header_q[4]} + ({1'b0, s_header_q[3]} << 3); @@ -284,7 +307,8 @@ module apu_microcode_loader #( s_header_range_err = 1'b1; s_header_range_addr = s_source_addr_q + 32'd20; s_header_range_detail = s_header_q[5]; - end else if (s_header_q[10] != 32'd0) begin + end else if ((!EnableP4 && (s_header_q[10] != 32'd0)) || + (EnableP4 && (s_header_q[10] > 32'h0000_6000))) begin s_header_range_err = 1'b1; s_header_range_addr = s_source_addr_q + 32'd40; s_header_range_detail = s_header_q[10]; @@ -292,10 +316,15 @@ module apu_microcode_loader #( end always_comb begin - s_range_err = s_header_range_err; - s_range_addr = s_header_range_addr; - s_range_detail = s_header_range_detail; + logic [31:0] s_scratch_end; + logic [31:0] s_max_scratch_end; + s_range_err = s_header_range_err; + s_range_addr = s_header_range_addr; + s_range_detail = s_header_range_detail; + s_max_scratch_end = 32'd0; for (int unsigned entry = 0; entry < 3; entry++) begin + s_scratch_end = s_entry_word_q[(entry*8)+2] + s_entry_word_q[(entry*8)+3]; + if (s_scratch_end > s_max_scratch_end) s_max_scratch_end = s_scratch_end; if (!s_range_err && ((s_entry_word_q[entry*8][31:15] != 17'd0) || (s_entry_word_q[entry*8][3:0] != 4'(entry)))) begin s_range_err = 1'b1; @@ -312,11 +341,19 @@ module apu_microcode_loader #( s_range_err = 1'b1; s_range_addr = s_source_addr_q + s_header_q[7] + 32'(entry * 32) + 32'd4; s_range_detail = s_entry_word_q[(entry*8)+1]; - end else if (!s_range_err && (s_entry_word_q[(entry*8)+2] != 32'd0)) begin + end else if (!s_range_err && + ((!EnableP4 && (s_entry_word_q[(entry*8)+2] != 32'd0)) || + (EnableP4 && ((s_entry_word_q[(entry*8)+2][31:17] != 15'd0) || + (s_entry_word_q[(entry*8)+2][1:0] != 2'd0) || + (s_entry_word_q[(entry*8)+3][31:17] != 15'd0) || + (s_entry_word_q[(entry*8)+3][1:0] != 2'd0) || + (s_scratch_end > 32'h0000_6000) || + ((s_entry_word_q[(entry*8)+3] != 32'd0) && + (s_entry_word_q[(entry*8)+2] < s_header_q[6])))))) begin s_range_err = 1'b1; s_range_addr = s_source_addr_q + s_header_q[7] + 32'(entry * 32) + 32'd8; s_range_detail = s_entry_word_q[(entry*8)+2]; - end else if (!s_range_err && (s_entry_word_q[(entry*8)+3] != 32'd0)) begin + end else if (!s_range_err && !EnableP4 && (s_entry_word_q[(entry*8)+3] != 32'd0)) begin s_range_err = 1'b1; s_range_addr = s_source_addr_q + s_header_q[7] + 32'(entry * 32) + 32'd12; s_range_detail = s_entry_word_q[(entry*8)+3]; @@ -334,21 +371,35 @@ module apu_microcode_loader #( s_range_detail = s_entry_word_q[(entry*8)+5]; end end + if (!s_range_err && EnableP4 && (s_header_q[10] != s_max_scratch_end)) begin + s_range_err = 1'b1; + s_range_addr = s_source_addr_q + 32'd40; + s_range_detail = s_header_q[10]; + end end always_comb begin - s_table_err = 1'b0; - s_table_addr = s_source_addr_q + 32'd20; + logic [16:0] s_entry_table_end; + s_table_err = 1'b0; + s_table_addr = s_source_addr_q + 32'd20; s_table_detail = s_header_q[5]; - if (s_header_q[5] != 32'd0) begin + s_table_header_err = EnableP4 && + ((s_header_q[6] > 32'h0000_6000) || (s_header_q[6][1:0] != 2'd0)); + if (!EnableP4 && (s_header_q[5] != 32'd0)) begin s_table_err = 1'b1; - end else if (s_header_q[6] != 32'd0) begin + end else if (!EnableP4 && (s_header_q[6] != 32'd0)) begin s_table_err = 1'b1; s_table_addr = s_source_addr_q + 32'd24; s_table_detail = s_header_q[6]; end else begin for (int unsigned entry = 0; entry < 3; entry++) begin - if (!s_table_err && (s_entry_word_q[(entry*8)+7] != 32'd0)) begin + s_entry_table_end = {1'b0, s_entry_word_q[(entry*8)+7][15:0]} + + {1'b0, s_entry_word_q[(entry*8)+7][31:16]}; + if (!s_table_err && + ((!EnableP4 && (s_entry_word_q[(entry*8)+7] != 32'd0)) || + (EnableP4 && ((s_entry_word_q[(entry*8)+7][1:0] != 2'd0) || + (s_entry_word_q[(entry*8)+7][17:16] != 2'd0) || + (s_entry_table_end > {1'b0, s_header_q[6][15:0]}))))) begin s_table_err = 1'b1; s_table_addr = s_source_addr_q + s_header_q[7] + 32'(entry * 32) + 32'd28; s_table_detail = s_entry_word_q[(entry*8)+7]; @@ -365,12 +416,17 @@ module apu_microcode_loader #( s_capability_detail = s_header_q[9] ^ s_entry_mask; if (s_header_q[9] != s_entry_mask) begin s_capability_err = 1'b1; - end else if (s_header_q[9] != 32'd0) begin + end else if ((!EnableP4 && (s_header_q[9] != 32'd0)) || + (EnableP4 && + ((s_header_q[9] & ~`APB4_APU__APUMC_P4_PRIMITIVE_MASK) != 32'd0))) begin s_capability_err = 1'b1; s_capability_detail = s_header_q[9]; end else begin for (int unsigned entry = 0; entry < 3; entry++) begin - if (!s_capability_err && (s_entry_word_q[(entry*8)+6] != 32'd0)) begin + if (!s_capability_err && + ((!EnableP4 && (s_entry_word_q[(entry*8)+6] != 32'd0)) || + (EnableP4 && ((s_entry_word_q[(entry*8)+6] & + ~`APB4_APU__APUMC_P4_PRIMITIVE_MASK) != 32'd0)))) begin s_capability_err = 1'b1; s_capability_addr = s_source_addr_q + s_header_q[7] + 32'(entry * 32) + 32'd24; s_capability_detail = s_entry_word_q[(entry*8)+6]; @@ -383,7 +439,7 @@ module apu_microcode_loader #( assign s_scan_class = s_scan_instruction[63:60]; assign s_scan_opcode = s_scan_instruction[59:56]; assign s_scan_predicate = s_scan_instruction[55:52]; - assign s_scan_aux = s_scan_instruction[33:32]; + assign s_scan_aux = s_scan_instruction[39:32]; assign s_scan_immediate = s_scan_instruction[10:0]; assign s_scan_last = s_scan_pc_q == entry_last_o[s_scan_entry_q]; assign s_scan_target = {1'b0, s_scan_pc_q} + 1'b1 + {1'b0, s_scan_immediate}; @@ -391,7 +447,7 @@ module apu_microcode_loader #( (s_scan_predicate != 4'd0) && (s_scan_class == 4'd0) && (s_scan_opcode inside {4'd3, 4'd4}) && (s_path_stack_count_q < PathStackLimit); assign s_path_stack_cs = s_path_stack_push || - ((s_state_q == PathPop) && (s_path_stack_count_q != 7'd0)); + ((s_state_q == PathPop) && (s_path_stack_count_q != 12'd0)); assign s_path_stack_write = s_path_stack_push; assign s_path_stack_addr = s_path_stack_push ? s_path_stack_count_q[10:0] : s_path_stack_count_q[10:0] - 1'b1; @@ -452,7 +508,7 @@ module apu_microcode_loader #( end end `else - logic [63:0] s_path_stack_mem [0:PathStackDepth-1]; + logic [63:0] s_path_stack_mem [0:PathStackDepth-1]; logic [63:0] s_path_stack_read_data_q; assign s_path_stack_read_data = s_path_stack_read_data_q; @@ -495,12 +551,23 @@ module apu_microcode_loader #( ((s_loop_active_q & ~(4'b0001 << s_scan_aux[1:0])) != 4'd0) : ((s_loop_active_q != 4'd0) || ((s_scan_class == 4'd0) && (s_scan_opcode == 4'd6)))); - assign s_scan_capability_instruction = ((s_scan_class >= 4'd2) && (s_scan_class <= 4'd6)) || - ((s_scan_class == 4'd0) && (s_scan_opcode == 4'd8)); + assign s_scan_capability_instruction = EnableP4 ? + ((s_scan_class == 4'd6) || + ((s_scan_class == 4'd0) && (s_scan_opcode == 4'd8) && + !(s_scan_aux inside {8'd1, 8'd2, 8'd3})) || + (!((s_scan_class == 4'd0) && (s_scan_opcode == 4'd8) && + (s_scan_aux == 8'd1)) && + ((instruction_primitive_mask( + s_scan_instruction + ) & ~entry_primitive_mask_o[s_scan_entry_q]) != 32'd0)) || + (((s_scan_class == 4'd0) && (s_scan_opcode == 4'd8) && (s_scan_aux == 8'd1)) && + ((entry_primitive_mask_o[s_scan_entry_q] & 32'h0000_ffc0) == 32'd0))) : + (((s_scan_class >= 4'd2) && (s_scan_class <= 4'd6)) || + ((s_scan_class == 4'd0) && (s_scan_opcode == 4'd8))); always_comb begin s_path_fallthrough = s_scan_predicate != 4'd0; - if (s_scan_class == 4'd1) begin + if ((s_scan_class >= 4'd1) && (s_scan_class <= (EnableP4 ? 4'd5 : 4'd1))) begin s_path_fallthrough = 1'b1; end else if ((s_scan_class == 4'd0) && (s_scan_opcode inside {4'd0, 4'd6, 4'd7})) begin s_path_fallthrough = 1'b1; @@ -727,6 +794,14 @@ module apu_microcode_loader #( fault_stage_o <= `APB4_APU__ERROR_STAGE_LOADER; fault_addr_o <= s_header_range_addr; fault_detail_o <= s_header_range_detail; + end else if (s_table_header_err) begin + s_state_q <= Idle; + s_err_bits_q[3] <= 1'b1; + fault_valid_o <= 1'b1; + fault_code_o <= `APB4_APU__ERROR_CODE_MICROCODE; + fault_stage_o <= `APB4_APU__ERROR_STAGE_LOADER; + fault_addr_o <= s_source_addr_q + 32'd24; + fault_detail_o <= s_header_q[6]; end else begin s_transfer_err_q <= 1'b0; s_byte_offset_q <= s_header_q[7]; @@ -789,6 +864,14 @@ module apu_microcode_loader #( fault_stage_o <= `APB4_APU__ERROR_STAGE_LOADER; fault_addr_o <= s_range_addr; fault_detail_o <= s_range_detail; + end else if (EnableP4 && s_table_err) begin + s_state_q <= Idle; + s_err_bits_q[3] <= 1'b1; + fault_valid_o <= 1'b1; + fault_code_o <= `APB4_APU__ERROR_CODE_MICROCODE; + fault_stage_o <= `APB4_APU__ERROR_STAGE_LOADER; + fault_addr_o <= s_table_addr; + fault_detail_o <= s_table_detail; end else begin s_byte_offset_q <= 32'd64; s_crc_q <= 32'hffff_ffff; @@ -865,7 +948,7 @@ module apu_microcode_loader #( fault_addr_o <= s_range_addr; fault_detail_o <= s_range_detail; end else begin - s_actual_crc_q <= ~s_crc_q; + s_actual_crc_q <= ~s_crc_q; s_scan_entry_q <= 2'd0; s_scan_pc_q <= entry_first_o[0]; s_call_depth_q <= 3'd0; @@ -894,15 +977,15 @@ module apu_microcode_loader #( s_scan_pc_q, s_scan_instruction ); - s_scan_pc_q <= entry_pc_o[s_scan_entry_q]; - s_call_depth_q <= 3'd0; - s_return_pc_q <= '0; - s_loop_active_q <= 4'd0; - s_loop_start_q <= '0; + s_scan_pc_q <= entry_pc_o[s_scan_entry_q]; + s_call_depth_q <= 3'd0; + s_return_pc_q <= '0; + s_loop_active_q <= 4'd0; + s_loop_start_q <= '0; s_path_stack_count_q <= '0; - s_path_stop_valid_q <= 1'b1; - s_path_stop_pc_q <= s_scan_pc_q; - s_state_q <= PathRequest; + s_path_stop_valid_q <= 1'b1; + s_path_stop_pc_q <= s_scan_pc_q; + s_state_q <= PathRequest; end else begin if (!s_scan_capability_err_q && s_scan_capability_instruction && instruction_encoding_valid( @@ -1000,7 +1083,7 @@ module apu_microcode_loader #( end end PathPop: begin - if (s_path_stack_count_q != 7'd0) begin + if (s_path_stack_count_q != 12'd0) begin s_path_stack_count_q <= s_path_stack_count_q - 1'b1; s_state_q <= PathPopLoad; end else if (s_scan_control_err_q || (s_scan_entry_q == 2'd2)) begin diff --git a/rtl/ip/multimedia/apu_microcode_pkg.sv b/rtl/ip/multimedia/apu_microcode_pkg.sv index 6db0e02f..b1b0a0a2 100644 --- a/rtl/ip/multimedia/apu_microcode_pkg.sv +++ b/rtl/ip/multimedia/apu_microcode_pkg.sv @@ -10,8 +10,53 @@ package apu_microcode_pkg; logic [10:0] last_pc; logic [15:0] max_loop_count; logic [23:0] max_retired; + logic [16:0] scratch_base; + logic [16:0] scratch_bytes; + logic [31:0] primitive_mask; + logic [15:0] table_offset; + logic [15:0] table_bytes; } apu_mc_entry_t; + function automatic logic [31:0] instruction_primitive_mask(input logic [63:0] instruction_i); + logic [3:0] s_class; + logic [3:0] s_opcode; + logic [7:0] s_aux; + logic s_unused; + begin + s_class = instruction_i[63:60]; + s_opcode = instruction_i[59:56]; + s_aux = instruction_i[39:32]; + s_unused = ^{instruction_i[55:40], instruction_i[31:0]}; + instruction_primitive_mask = 32'd0; + unique case (s_class) + `APB4_APU__MC_CLASS_CONTROL: begin + if (s_opcode == `APB4_APU__MC_CONTROL_WAIT) begin + if (s_aux == `APB4_APU__MC_WAIT_KERNEL) instruction_primitive_mask = 32'h0000_ffc0; + else if (s_aux inside {`APB4_APU__MC_WAIT_INPUT_FIFO, `APB4_APU__MC_WAIT_OUTPUT_FIFO}) + instruction_primitive_mask = 32'd1 << `APB4_APU__MC_PRIMITIVE_LOCAL_FIFO; + end + end + `APB4_APU__MC_CLASS_BITSTREAM: begin + instruction_primitive_mask = 32'd1 << `APB4_APU__MC_PRIMITIVE_BITSTREAM; + if (s_opcode inside {`APB4_APU__MC_BITSTREAM_CRC8, `APB4_APU__MC_BITSTREAM_CRC16}) + instruction_primitive_mask |= 32'd1 << `APB4_APU__MC_PRIMITIVE_CRC; + end + `APB4_APU__MC_CLASS_ENTROPY: + instruction_primitive_mask = (32'd1 << `APB4_APU__MC_PRIMITIVE_BITSTREAM) | + (32'd1 << ((s_opcode <= `APB4_APU__MC_ENTROPY_HUFF_QUAD) ? + `APB4_APU__MC_PRIMITIVE_HUFFMAN : `APB4_APU__MC_PRIMITIVE_RICE)); + `APB4_APU__MC_CLASS_LOCAL: + instruction_primitive_mask = 32'd1 << + ((s_opcode inside {`APB4_APU__MC_LOCAL_FIFO_POP, `APB4_APU__MC_LOCAL_FIFO_PUSH}) ? + `APB4_APU__MC_PRIMITIVE_LOCAL_FIFO : `APB4_APU__MC_PRIMITIVE_LOCAL_MEMORY); + `APB4_APU__MC_CLASS_KERNEL: + instruction_primitive_mask = 32'd1 << (s_opcode + `APB4_APU__MC_PRIMITIVE_REQUANTIZE); + default: instruction_primitive_mask = 32'd0; + endcase + return instruction_primitive_mask | {32{s_unused && 1'b0}}; + end + endfunction + function automatic logic [31:0] crc32_word(input logic [31:0] crc_i, input logic [31:0] data_i, input logic [3:0] keep_i); logic [31:0] s_crc; diff --git a/rtl/ip/multimedia/apu_primitive_dispatcher.sv b/rtl/ip/multimedia/apu_primitive_dispatcher.sv new file mode 100644 index 00000000..062fcaf0 --- /dev/null +++ b/rtl/ip/multimedia/apu_primitive_dispatcher.sv @@ -0,0 +1,688 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +`include "apu_define.svh" + +module apu_primitive_dispatcher ( + // verilog_format: off -- preserve request, result, memory, and FIFO columns + input logic clk_i, + input logic rst_n_i, + input logic flush_i, + input logic req_valid_i, + output logic req_ready_o, + input logic [63:0] instruction_i, + input logic [31:0] source0_i, + input logic [31:0] source1_i, + input logic [31:0] destination_i, + input logic [16:0] scratch_base_i, + input logic [16:0] scratch_bytes_i, + input logic [15:0] table_offset_i, + input logic [15:0] table_bytes_i, + output logic result_valid_o, + output logic [ 3:0] result_dst_o, + output logic [ 3:0][31:0] result_data_o, + output logic [ 2:0] result_words_o, + output logic result_kernel_o, + output logic error_o, + output logic [ 5:0] error_code_o, + output logic [ 3:0] error_stage_o, + output logic [ 7:0] error_reason_o, + output logic [31:0] cycles_o, + output logic kernel_done_o, + output logic busy_o, + output logic input_exhausted_o, + output logic input_ready_o, + output logic output_ready_o, + output logic [ 6:0] input_count_o, + output logic [ 6:0] output_count_o, + output logic kernel_busy_o, + output logic memory_req_o, + output logic memory_write_o, + output logic [16:0] memory_addr_o, + output logic [31:0] memory_data_o, + output logic [ 3:0] memory_strb_o, + input logic memory_valid_i, + input logic [31:0] memory_data_i, + input logic memory_error_i, + input logic input_valid_i, + input logic [40:0] input_data_i, + output logic input_accept_o, + output logic output_valid_o, + output logic [40:0] output_data_o, + input logic output_accept_i + // verilog_format: on +); + typedef enum logic [3:0] { + Idle, + BitRequest, + BitWait, + FrameAlignRequest, + FrameAlignWait, + FramePeekRequest, + FramePeekWait, + FrameSkipRequest, + FrameSkipWait, + EntropyRequest, + EntropyWait, + LocalRequest, + LocalWait + } state_e; + + state_e s_state_q; + logic [63:0] s_instruction_q; + logic [31:0] s_source0_q, s_source1_q; + logic [16:0] s_scratch_base_q, s_scratch_bytes_q; + logic [15:0] s_table_offset_q, s_table_bytes_q; + logic [15:0] s_frame_skipped_q; + logic [16:0] s_local_addr_q; + logic [ 1:0] s_local_lane_q; + logic [ 2:0] s_local_size_q; + logic [31:0] s_cycles_q; + + logic [3:0] s_class, s_opcode, s_dst; + logic [ 7:0] s_aux; + logic [31:0] s_immediate; + logic s_input_full, s_input_empty, s_output_full, s_output_empty; + logic [6:0] s_input_count, s_output_count; + logic [40:0] s_input_fifo_data, s_output_fifo_data; + logic s_input_fifo_push, s_input_fifo_pop, s_output_fifo_push, s_output_fifo_pop; + logic [40:0] s_output_fifo_write_data; + logic s_fifo_metadata_ok; + + logic s_bit_req_valid, s_bit_req_ready, s_direct_bit_req, s_direct_frame_align; + logic s_frame_match, s_frame_can_skip; + logic [2:0] s_bit_req_op; + logic [5:0] s_bit_req_width; + logic s_bit_result_valid, s_bit_err, s_bit_fifo_pop; + logic [31:0] s_bit_result_data; + logic [ 6:0] s_available_bits; + logic s_reservoir_eof; + + logic s_entropy_req_valid, s_entropy_req_ready; + logic s_direct_entropy_req; + logic s_entropy_memory_req, s_entropy_bit_req_valid, s_entropy_bit_req_ready; + logic [16:0] s_entropy_memory_addr; + logic [ 2:0] s_entropy_bit_req_op; + logic [ 5:0] s_entropy_bit_req_width; + logic s_entropy_result_valid, s_entropy_err; + logic [ 3:0] s_entropy_result_dst; + logic [ 3:0][31:0] s_entropy_result_data; + logic [ 2:0] s_entropy_result_words; + logic [ 5:0] s_entropy_err_code; + logic [ 3:0] s_entropy_err_stage; + logic [31:0] s_entropy_cycles; + + logic s_kernel_req_valid, s_kernel_req_ready, s_kernel_memory_req, s_kernel_memory_write; + logic [16:0] s_kernel_memory_addr; + logic [31:0] s_kernel_memory_data; + logic [ 3:0] s_kernel_memory_strb; + logic s_kernel_done, s_kernel_err, s_kernel_busy; + logic [5:0] s_kernel_err_code; + logic [3:0] s_kernel_err_stage, s_kernel_result_dst; + logic [7:0] s_kernel_err_reason; + logic [31:0] s_kernel_result_count, s_kernel_cycles; + + logic [32:0] s_scratch_end, s_local_end, s_table_end; + logic [32:0] s_entropy_table_end; + logic s_entropy_range_ok; + logic [34:0] s_local_addr_full; + logic s_local_addr_overflow_q; + logic s_local_range_ok, s_local_access_ok, s_local_write; + logic [31:0] s_crc_result; + + assign s_class = instruction_i[63:60]; + assign s_opcode = instruction_i[59:56]; + assign s_dst = instruction_i[51:48]; + assign s_aux = instruction_i[39:32]; + assign s_immediate = instruction_i[31:0]; + + assign req_ready_o = (s_state_q == Idle) && + (((s_class == `APB4_APU__MC_CLASS_KERNEL) && s_kernel_req_ready) || + (!s_kernel_busy && + ((s_class inside {`APB4_APU__MC_CLASS_BITSTREAM, `APB4_APU__MC_CLASS_ENTROPY}) || + ((s_class == `APB4_APU__MC_CLASS_LOCAL) && + (((s_opcode == `APB4_APU__MC_LOCAL_FIFO_POP) && !s_input_empty) || + ((s_opcode == `APB4_APU__MC_LOCAL_FIFO_PUSH) && !s_output_full) || + (s_opcode < `APB4_APU__MC_LOCAL_FIFO_POP)))))); + assign busy_o = (s_state_q != Idle) || s_kernel_busy; + assign kernel_done_o = result_valid_o && result_kernel_o && !error_o; + assign input_exhausted_o = s_reservoir_eof; + assign input_ready_o = !s_input_empty; + assign output_ready_o = !s_output_full; + assign input_count_o = s_input_count; + assign output_count_o = s_output_count; + assign kernel_busy_o = s_kernel_busy; + + assign input_accept_o = input_valid_i && !s_input_full; + assign s_input_fifo_push = input_accept_o; + assign s_input_fifo_pop = s_bit_fifo_pop || + (req_valid_i && req_ready_o && (s_class == `APB4_APU__MC_CLASS_LOCAL) && + (s_opcode == `APB4_APU__MC_LOCAL_FIFO_POP)); + assign s_fifo_metadata_ok = ((source1_i & ~32'h0000_0107) == 32'd0) && + (source1_i[2:0] != 3'd0) && (source1_i[2:0] <= 3'd4); + assign s_output_fifo_push = req_valid_i && req_ready_o && s_fifo_metadata_ok && + (s_class == `APB4_APU__MC_CLASS_LOCAL) && + (s_opcode == `APB4_APU__MC_LOCAL_FIFO_PUSH); + assign s_output_fifo_write_data = {source1_i[8], 5'd0, source1_i[2:0], source0_i}; + assign output_valid_o = !s_output_empty; + assign output_data_o = s_output_fifo_data; + assign s_output_fifo_pop = output_accept_i && output_valid_o; + + stream_fifo #( + .DATA_WIDTH (41), + .BUFFER_DEPTH (64), + .LOG_BUFFER_DEPTH(6) + ) u_input_fifo ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .flush_i(flush_i), + .full_o (s_input_full), + .empty_o(s_input_empty), + .cnt_o (s_input_count), + .dat_i (input_data_i), + .push_i (s_input_fifo_push), + .dat_o (s_input_fifo_data), + .pop_i (s_input_fifo_pop) + ); + + stream_fifo #( + .DATA_WIDTH (41), + .BUFFER_DEPTH (64), + .LOG_BUFFER_DEPTH(6) + ) u_output_fifo ( + .clk_i (clk_i), + .rst_n_i(rst_n_i), + .flush_i(flush_i), + .full_o (s_output_full), + .empty_o(s_output_empty), + .cnt_o (s_output_count), + .dat_i (s_output_fifo_write_data), + .push_i (s_output_fifo_push), + .dat_o (s_output_fifo_data), + .pop_i (s_output_fifo_pop) + ); + + assign s_direct_bit_req = (s_state_q == Idle) && req_valid_i && req_ready_o && + (s_class == `APB4_APU__MC_CLASS_BITSTREAM) && + (s_opcode <= `APB4_APU__MC_BITSTREAM_ALIGN); + assign s_direct_frame_align = (s_state_q == Idle) && req_valid_i && req_ready_o && + (s_class == `APB4_APU__MC_CLASS_BITSTREAM) && + (s_opcode == `APB4_APU__MC_BITSTREAM_FRAME_SYNC); + assign s_frame_match = (s_bit_result_data[15:0] & s_source1_q[15:0]) == + (s_source0_q[15:0] & s_source1_q[15:0]); + assign s_frame_can_skip = !s_bit_err && !s_frame_match && + (s_frame_skipped_q < s_instruction_q[15:0] - 1'b1); + assign s_bit_req_valid = s_direct_bit_req || s_direct_frame_align || + ((s_state_q == FrameAlignWait) && s_bit_result_valid && !s_bit_err) || + ((s_state_q == FramePeekWait) && s_bit_result_valid && s_frame_can_skip) || + ((s_state_q == FrameSkipWait) && s_bit_result_valid && !s_bit_err) || + (s_state_q inside { + BitRequest, FrameAlignRequest, FramePeekRequest, FrameSkipRequest + }) || s_entropy_bit_req_valid; + assign s_bit_req_op = s_entropy_bit_req_valid ? s_entropy_bit_req_op : + (s_direct_bit_req ? s_opcode[2:0] : + ((s_direct_frame_align || (s_state_q == FrameAlignRequest)) ? 3'd4 : + ((s_state_q inside {FrameAlignWait, FramePeekRequest, FrameSkipWait}) ? 3'd1 : + ((s_state_q inside {FramePeekWait, FrameSkipRequest}) ? + 3'd2 : s_instruction_q[58:56])))); + assign s_bit_req_width = s_entropy_bit_req_valid ? s_entropy_bit_req_width : + (s_direct_bit_req ? s_immediate[5:0] : + ((s_direct_frame_align || (s_state_q == FrameAlignRequest)) ? 6'd0 : + ((s_state_q inside {FrameAlignWait, FramePeekRequest, FrameSkipWait}) ? + {1'b0, s_instruction_q[36:32]} : + ((s_state_q inside {FramePeekWait, FrameSkipRequest}) ? + 6'd8 : s_instruction_q[5:0])))); + assign s_entropy_bit_req_ready = s_bit_req_ready && s_entropy_bit_req_valid; + + apu_bitstream_engine u_bitstream_engine ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .flush_i (flush_i), + .req_valid_i (s_bit_req_valid), + .req_ready_o (s_bit_req_ready), + .req_op_i (s_bit_req_op), + .req_width_i (s_bit_req_width), + .result_valid_o (s_bit_result_valid), + .result_data_o (s_bit_result_data), + .available_bits_o(s_available_bits), + .eof_o (s_reservoir_eof), + .error_o (s_bit_err), + .fifo_empty_i (s_input_empty), + .fifo_data_i (s_input_fifo_data), + .fifo_pop_o (s_bit_fifo_pop) + ); + + assign s_direct_entropy_req = (s_state_q == Idle) && req_valid_i && req_ready_o && + (s_class == `APB4_APU__MC_CLASS_ENTROPY); + assign s_entropy_req_valid = (s_state_q == EntropyRequest) || s_direct_entropy_req; + assign s_kernel_req_valid = (s_state_q == Idle) && req_valid_i && + (s_class == `APB4_APU__MC_CLASS_KERNEL) && !s_kernel_done && + !(result_valid_o && result_kernel_o); + + apu_entropy_engine u_entropy_engine ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .flush_i (flush_i), + .req_valid_i (s_entropy_req_valid), + .req_ready_o (s_entropy_req_ready), + .opcode_i (s_direct_entropy_req ? s_opcode : s_instruction_q[59:56]), + .dst_i (s_direct_entropy_req ? s_dst : s_instruction_q[51:48]), + .aux_i (s_direct_entropy_req ? s_aux : s_instruction_q[39:32]), + .immediate_i (s_direct_entropy_req ? s_immediate : s_instruction_q[31:0]), + .source0_i (s_direct_entropy_req ? source0_i : s_source0_q), + .source1_i (s_direct_entropy_req ? source1_i : s_source1_q), + .table_offset_i (s_direct_entropy_req ? table_offset_i : s_table_offset_q), + .memory_req_o (s_entropy_memory_req), + .memory_addr_o (s_entropy_memory_addr), + .memory_valid_i (memory_valid_i), + .memory_data_i (memory_data_i), + .memory_error_i (memory_error_i), + .bit_req_valid_o (s_entropy_bit_req_valid), + .bit_req_ready_i (s_entropy_bit_req_ready), + .bit_req_op_o (s_entropy_bit_req_op), + .bit_req_width_o (s_entropy_bit_req_width), + .bit_result_valid_i(s_bit_result_valid), + .bit_result_data_i (s_bit_result_data), + .bit_error_i (s_bit_err), + .result_valid_o (s_entropy_result_valid), + .result_dst_o (s_entropy_result_dst), + .result_data_o (s_entropy_result_data), + .result_words_o (s_entropy_result_words), + .error_o (s_entropy_err), + .error_code_o (s_entropy_err_code), + .error_stage_o (s_entropy_err_stage), + .cycles_o (s_entropy_cycles) + ); + + apu_kernel_engine u_kernel_engine ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .flush_i (flush_i), + .req_valid_i (s_kernel_req_valid), + .req_ready_o (s_kernel_req_ready), + .opcode_i (s_opcode), + .aux_i (s_aux), + .count_i (s_immediate[15:0]), + .dst_i (s_dst), + .input_offset_i (source0_i), + .parameter_offset_i(source1_i), + .output_offset_i (destination_i), + .scratch_base_i (scratch_base_i), + .scratch_bytes_i (scratch_bytes_i), + .table_offset_i (table_offset_i), + .table_bytes_i (table_bytes_i), + .memory_req_o (s_kernel_memory_req), + .memory_write_o (s_kernel_memory_write), + .memory_addr_o (s_kernel_memory_addr), + .memory_data_o (s_kernel_memory_data), + .memory_strb_o (s_kernel_memory_strb), + .memory_valid_i (memory_valid_i), + .memory_data_i (memory_data_i), + .memory_error_i (memory_error_i), + .done_o (s_kernel_done), + .error_o (s_kernel_err), + .error_code_o (s_kernel_err_code), + .error_stage_o (s_kernel_err_stage), + .error_reason_o (s_kernel_err_reason), + .result_dst_o (s_kernel_result_dst), + .result_count_o (s_kernel_result_count), + .cycles_o (s_kernel_cycles), + .busy_o (s_kernel_busy) + ); + + assign memory_req_o = s_kernel_busy ? s_kernel_memory_req : + (s_entropy_memory_req || ((s_state_q == LocalRequest) && s_local_access_ok)); + assign memory_write_o = s_kernel_busy ? s_kernel_memory_write : + ((s_state_q == LocalRequest) && s_local_access_ok && s_local_write); + assign memory_addr_o = s_kernel_busy ? s_kernel_memory_addr : + (s_entropy_memory_req ? s_entropy_memory_addr : {s_local_addr_q[16:2], 2'd0}); + assign memory_data_o = s_kernel_busy ? s_kernel_memory_data : s_source1_q; + assign memory_strb_o = s_kernel_busy ? s_kernel_memory_strb : 4'hf; + + always_comb begin + logic [15:0] s_crc8; + s_crc8 = (s_opcode == `APB4_APU__MC_BITSTREAM_CRC8) ? {8'd0, source0_i[7:0]} : source0_i[15:0]; + for (int bit_index = 0; bit_index < 8; bit_index++) begin + if (s_opcode == `APB4_APU__MC_BITSTREAM_CRC8) begin + s_crc8[7:0] = {s_crc8[6:0], 1'b0} ^ ({8{(s_crc8[7] ^ source1_i[7-bit_index])}} & 8'h07); + end else begin + s_crc8 = {s_crc8[14:0], 1'b0} ^ ({16{(s_crc8[15] ^ source1_i[7-bit_index])}} & 16'h8005); + end + end + s_crc_result = (s_opcode == `APB4_APU__MC_BITSTREAM_CRC8) ? + {24'd0, s_crc8[7:0]} : {16'd0, s_crc8}; + end + + assign s_scratch_end = {16'd0, s_scratch_base_q} + {16'd0, s_scratch_bytes_q}; + assign s_local_end = {16'd0, s_local_addr_q} + {30'd0, s_local_size_q}; + assign s_table_end = {17'd0, s_table_offset_q} + {17'd0, s_table_bytes_q}; + assign s_entropy_table_end = {1'b0, source0_i} + ({1'b0, source1_i} << 2); + assign s_entropy_range_ok = (source0_i[1:0] == 2'd0) && !s_entropy_table_end[32] && + (s_entropy_table_end <= {17'd0, table_bytes_i}); + assign s_local_range_ok = !s_local_addr_overflow_q && + ((s_instruction_q[59:56] inside { + `APB4_APU__MC_LOCAL_TABLE8, `APB4_APU__MC_LOCAL_TABLE16, + `APB4_APU__MC_LOCAL_TABLE32 + }) ? (({16'd0, s_local_addr_q} >= {17'd0, s_table_offset_q}) && (s_local_end <= s_table_end)) + : (({16'd0, s_local_addr_q} >= {16'd0, s_scratch_base_q}) && + (s_local_end <= s_scratch_end))); + assign s_local_access_ok = s_local_range_ok && + ((s_local_size_q == 3'd1) || + ((s_local_addr_q & ({14'd0, s_local_size_q} - 17'd1)) == 17'd0)); + + always_comb begin + unique case (s_opcode) + `APB4_APU__MC_LOCAL_LD32, `APB4_APU__MC_LOCAL_ST32: + s_local_addr_full = {18'd0, scratch_base_i} + {3'd0, source0_i} + + {{19{instruction_i[15]}}, instruction_i[15:0]}; + `APB4_APU__MC_LOCAL_TABLE8: + s_local_addr_full = {19'd0, table_offset_i} + {3'd0, source0_i} + {3'd0, source1_i}; + `APB4_APU__MC_LOCAL_TABLE16: + s_local_addr_full = {19'd0, table_offset_i} + {3'd0, source0_i} + ({3'd0, source1_i} << 1); + default: + s_local_addr_full = {19'd0, table_offset_i} + {3'd0, source0_i} + ({3'd0, source1_i} << 2); + endcase + end + + always_ff @(posedge clk_i or negedge rst_n_i) begin + if (!rst_n_i) begin + s_state_q <= Idle; + s_instruction_q <= 64'd0; + s_source0_q <= 32'd0; + s_source1_q <= 32'd0; + s_scratch_base_q <= 17'd0; + s_scratch_bytes_q <= 17'd0; + s_table_offset_q <= 16'd0; + s_table_bytes_q <= 16'd0; + s_frame_skipped_q <= 16'd0; + s_local_addr_q <= 17'd0; + s_local_lane_q <= 2'd0; + s_local_size_q <= 3'd0; + s_local_write <= 1'b0; + s_local_addr_overflow_q <= 1'b0; + s_cycles_q <= 32'd0; + result_valid_o <= 1'b0; + result_dst_o <= 4'd0; + result_data_o <= '0; + result_words_o <= 3'd0; + result_kernel_o <= 1'b0; + error_o <= 1'b0; + error_code_o <= 6'd0; + error_stage_o <= 4'd0; + error_reason_o <= 8'd0; + cycles_o <= 32'd0; + end else if (flush_i) begin + s_state_q <= Idle; + result_valid_o <= 1'b0; + result_kernel_o <= 1'b0; + error_o <= 1'b0; + s_cycles_q <= 32'd0; + end else begin + result_valid_o <= 1'b0; + result_kernel_o <= 1'b0; + error_o <= 1'b0; + if (s_kernel_done) begin + result_valid_o <= 1'b1; + result_kernel_o <= 1'b1; + result_dst_o <= s_kernel_result_dst; + result_data_o <= s_kernel_err ? '0 : {96'd0, s_kernel_result_count}; + result_words_o <= s_kernel_err ? 3'd0 : 3'd1; + cycles_o <= s_kernel_cycles; + error_o <= s_kernel_err; + error_code_o <= s_kernel_err_code; + error_stage_o <= s_kernel_err_stage; + error_reason_o <= s_kernel_err_reason; + end + if (s_state_q != Idle) s_cycles_q <= s_cycles_q + 1'b1; + unique case (s_state_q) + Idle: begin + if (req_valid_i && req_ready_o && (s_class != `APB4_APU__MC_CLASS_KERNEL)) begin + s_instruction_q <= instruction_i; + s_source0_q <= source0_i; + s_source1_q <= source1_i; + s_scratch_base_q <= scratch_base_i; + s_scratch_bytes_q <= scratch_bytes_i; + s_table_offset_q <= table_offset_i; + s_table_bytes_q <= table_bytes_i; + s_cycles_q <= 32'd1; + s_local_addr_overflow_q <= s_local_addr_full[34:17] != 18'd0; + result_dst_o <= instruction_i[51:48]; + result_data_o <= '0; + result_words_o <= 3'd0; + if (s_class == `APB4_APU__MC_CLASS_BITSTREAM) begin + if (s_opcode == `APB4_APU__MC_BITSTREAM_FRAME_SYNC) begin + s_frame_skipped_q <= 16'd0; + s_state_q <= s_direct_frame_align ? FrameAlignWait : FrameAlignRequest; + end else if (s_opcode inside {`APB4_APU__MC_BITSTREAM_CRC8, + `APB4_APU__MC_BITSTREAM_CRC16}) begin + result_valid_o <= 1'b1; + result_words_o <= 3'd1; + result_data_o[0] <= s_crc_result; + cycles_o <= 32'd1; + end else begin + s_state_q <= s_direct_bit_req ? BitWait : BitRequest; + end + end else if (s_class == `APB4_APU__MC_CLASS_ENTROPY) begin + if ((s_opcode <= `APB4_APU__MC_ENTROPY_HUFF_QUAD) && + (source1_i >= 32'd1) && (source1_i <= 32'd4096) && + !s_entropy_range_ok) begin + result_valid_o <= 1'b1; + result_words_o <= 3'd0; + error_o <= 1'b1; + error_code_o <= `APB4_APU__ERROR_CODE_SEQUENCER; + error_stage_o <= `APB4_APU__ERROR_STAGE_LIFECYCLE; + error_reason_o <= 8'd5; + cycles_o <= 32'd1; + end else begin + s_state_q <= s_direct_entropy_req ? EntropyWait : EntropyRequest; + end + end else if (s_opcode == `APB4_APU__MC_LOCAL_FIFO_POP) begin + result_valid_o <= 1'b1; + result_words_o <= 3'd2; + result_data_o[0] <= s_input_fifo_data[31:0]; + result_data_o[1] <= {23'd0, s_input_fifo_data[40], 5'd0, s_input_fifo_data[34:32]}; + cycles_o <= 32'd1; + end else if (s_opcode == `APB4_APU__MC_LOCAL_FIFO_PUSH) begin + result_valid_o <= 1'b1; + result_words_o <= 3'd0; + cycles_o <= 32'd1; + if ((source1_i & ~32'h0000_0107) != 32'd0 || + (source1_i[2:0] == 3'd0) || (source1_i[2:0] > 3'd4)) begin + error_o <= 1'b1; + error_code_o <= `APB4_APU__ERROR_CODE_OVERFLOW; + error_stage_o <= `APB4_APU__ERROR_STAGE_RESAMPLER; + error_reason_o <= 8'd9; + end + end else begin + s_local_write <= s_opcode == `APB4_APU__MC_LOCAL_ST32; + s_local_addr_q <= s_local_addr_full[16:0]; + s_local_lane_q <= s_local_addr_full[1:0]; + unique case (s_opcode) + `APB4_APU__MC_LOCAL_LD32, `APB4_APU__MC_LOCAL_ST32: begin + s_local_size_q <= 3'd4; + end + `APB4_APU__MC_LOCAL_TABLE8: begin + s_local_size_q <= 3'd1; + end + `APB4_APU__MC_LOCAL_TABLE16: begin + s_local_size_q <= 3'd2; + end + default: begin + s_local_size_q <= 3'd4; + end + endcase + s_state_q <= LocalRequest; + end + end + end + BitRequest: begin + if (s_bit_req_ready) s_state_q <= BitWait; + end + BitWait: begin + if (s_bit_result_valid) begin + s_state_q <= Idle; + result_valid_o <= 1'b1; + result_words_o <= (s_instruction_q[59:56] inside {`APB4_APU__MC_BITSTREAM_SKIP, + `APB4_APU__MC_BITSTREAM_ALIGN + }) ? 3'd0 : 3'd1; + result_data_o[0] <= s_bit_result_data; + cycles_o <= (s_instruction_q[59:56] == `APB4_APU__MC_BITSTREAM_REFILL) ? 32'd3 : 32'd1; + if (s_bit_err && (s_instruction_q[59:56] != `APB4_APU__MC_BITSTREAM_REFILL)) begin + result_data_o <= '0; + result_words_o <= 3'd0; + error_o <= 1'b1; + error_code_o <= `APB4_APU__ERROR_CODE_TRUNCATED; + error_stage_o <= `APB4_APU__ERROR_STAGE_BITSTREAM; + error_reason_o <= 8'd9; + end + end + end + FrameAlignRequest: begin + if (s_bit_req_ready) s_state_q <= FrameAlignWait; + end + FrameAlignWait: begin + if (s_bit_result_valid) begin + if (s_bit_err) begin + s_state_q <= Idle; + result_valid_o <= 1'b1; + result_data_o <= '0; + result_words_o <= 3'd0; + error_o <= 1'b1; + error_code_o <= `APB4_APU__ERROR_CODE_TRUNCATED; + error_stage_o <= `APB4_APU__ERROR_STAGE_BITSTREAM; + error_reason_o <= 8'd9; + end else begin + s_state_q <= FramePeekWait; + end + end + end + FramePeekRequest: begin + if (s_bit_req_ready) s_state_q <= FramePeekWait; + end + FramePeekWait: begin + if (s_bit_result_valid) begin + if (s_bit_err) begin + s_state_q <= Idle; + result_valid_o <= 1'b1; + result_data_o <= '0; + result_words_o <= 3'd0; + error_o <= 1'b1; + error_code_o <= `APB4_APU__ERROR_CODE_MALFORMED; + error_stage_o <= `APB4_APU__ERROR_STAGE_BITSTREAM; + error_reason_o <= 8'd9; + cycles_o <= 32'd2 + ({16'd0, s_instruction_q[15:0]} << 1); + end else if (s_frame_match) begin + s_state_q <= Idle; + result_valid_o <= 1'b1; + result_words_o <= 3'd1; + result_data_o[0] <= {16'd0, s_frame_skipped_q}; + cycles_o <= 32'd2 + ({16'd0, s_frame_skipped_q} << 1); + end else if (s_frame_skipped_q >= s_instruction_q[15:0] - 1'b1) begin + s_state_q <= Idle; + result_valid_o <= 1'b1; + result_data_o <= '0; + result_words_o <= 3'd0; + error_o <= 1'b1; + error_code_o <= `APB4_APU__ERROR_CODE_MALFORMED; + error_stage_o <= `APB4_APU__ERROR_STAGE_BITSTREAM; + error_reason_o <= 8'd9; + cycles_o <= 32'd2 + ({16'd0, s_instruction_q[15:0]} << 1); + end else begin + s_state_q <= FrameSkipWait; + end + end + end + FrameSkipRequest: begin + if (s_bit_req_ready) s_state_q <= FrameSkipWait; + end + FrameSkipWait: begin + if (s_bit_result_valid) begin + if (s_bit_err) begin + s_state_q <= Idle; + result_valid_o <= 1'b1; + result_data_o <= '0; + result_words_o <= 3'd0; + error_o <= 1'b1; + error_code_o <= `APB4_APU__ERROR_CODE_MALFORMED; + error_stage_o <= `APB4_APU__ERROR_STAGE_BITSTREAM; + error_reason_o <= 8'd9; + end else begin + s_frame_skipped_q <= s_frame_skipped_q + 1'b1; + s_state_q <= FramePeekWait; + end + end + end + EntropyRequest: begin + if (s_entropy_req_ready) s_state_q <= EntropyWait; + end + EntropyWait: begin + if (s_entropy_result_valid) begin + s_state_q <= Idle; + result_valid_o <= 1'b1; + result_dst_o <= s_entropy_result_dst; + result_data_o <= s_entropy_result_data; + result_words_o <= s_entropy_result_words; + cycles_o <= s_entropy_cycles; + if (s_entropy_err) begin + result_data_o <= '0; + result_words_o <= 3'd0; + error_o <= 1'b1; + error_code_o <= s_entropy_err_code; + error_stage_o <= s_entropy_err_stage; + error_reason_o <= (s_entropy_err_code == `APB4_APU__ERROR_CODE_SEQUENCER) ? + 8'd5 : 8'd9; + end + end + end + LocalRequest: begin + if (!s_local_access_ok) begin + s_state_q <= Idle; + result_valid_o <= 1'b1; + result_data_o <= '0; + result_words_o <= 3'd0; + error_o <= 1'b1; + error_code_o <= `APB4_APU__ERROR_CODE_SEQUENCER; + error_stage_o <= `APB4_APU__ERROR_STAGE_LIFECYCLE; + error_reason_o <= 8'd5; + cycles_o <= 32'd1; + end else if (s_local_write) begin + s_state_q <= Idle; + result_valid_o <= 1'b1; + result_words_o <= 3'd0; + cycles_o <= 32'd1; + end else begin + s_state_q <= LocalWait; + end + end + LocalWait: begin + if (memory_error_i) begin + s_state_q <= Idle; + result_valid_o <= 1'b1; + result_data_o <= '0; + result_words_o <= 3'd0; + error_o <= 1'b1; + error_code_o <= `APB4_APU__ERROR_CODE_SEQUENCER; + error_stage_o <= `APB4_APU__ERROR_STAGE_LIFECYCLE; + error_reason_o <= 8'd5; + cycles_o <= 32'd2; + end else if (memory_valid_i) begin + s_state_q <= Idle; + result_valid_o <= 1'b1; + result_words_o <= 3'd1; + cycles_o <= 32'd2; + unique case (s_local_size_q) + 3'd1: result_data_o[0] <= {24'd0, memory_data_i[s_local_lane_q*8+:8]}; + 3'd2: result_data_o[0] <= {16'd0, memory_data_i[s_local_lane_q[1]*16+:16]}; + default: result_data_o[0] <= memory_data_i; + endcase + end + end + default: s_state_q <= Idle; + endcase + end + end + + logic s_unused; + assign s_unused = s_reservoir_eof ^ ^s_available_bits ^ ^s_input_count ^ ^s_output_count ^ + ^s_instruction_q[63:60] ^ ^s_instruction_q[55:40] ^ ^s_immediate[31:16]; +endmodule diff --git a/rtl/ip/multimedia/apu_reconstruction_engine.sv b/rtl/ip/multimedia/apu_reconstruction_engine.sv new file mode 100644 index 00000000..6fd39228 --- /dev/null +++ b/rtl/ip/multimedia/apu_reconstruction_engine.sv @@ -0,0 +1,174 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +`include "apu_define.svh" + +module apu_reconstruction_engine ( + // verilog_format: off -- preserve arithmetic operand columns + input logic [ 3:0] opcode_i, + input logic [ 7:0] aux_i, + input logic signed [31:0] value0_i, + input logic signed [31:0] value1_i, + input logic signed [31:0] scale_i, + input logic signed [31:0] residual_i, + input logic signed [31:0] predictor_shift_i, + input logic signed [31:0] history_i [0:31], + input logic signed [31:0] coefficient_i [0:31], + output logic signed [31:0] result0_o, + output logic signed [31:0] result1_o, + output logic overflow_o + // verilog_format: on +); + logic signed [71:0] s_accumulator; + logic signed [63:0] s_value0, s_value1; + logic signed [63:0] s_product; + logic signed [63:0] s_predictor; + logic signed [63:0] s_shifted; + logic signed [63:0] s_value0_ext, s_value1_ext, s_residual_ext; + logic signed [71:0] s_lpc_shifted; + logic signed [72:0] s_lpc_value; + logic s_lpc_shift_overflow; + logic [ 5:0] s_order; + + function automatic logic signed [63:0] rne(input logic signed [63:0] value_i, + input logic [5:0] shift_i); + logic signed [63:0] s_magnitude; + logic signed [63:0] s_quotient; + logic [63:0] s_remainder; + logic [63:0] s_half; + begin + if (shift_i == 6'd0) return value_i; + s_magnitude = (value_i < 0) ? -value_i : value_i; + s_quotient = s_magnitude >>> shift_i; + s_remainder = s_magnitude & ((64'd1 << shift_i) - 1'b1); + s_half = 64'd1 << (shift_i - 1'b1); + if ((s_remainder > s_half) || ((s_remainder == s_half) && s_quotient[0])) begin + s_quotient = s_quotient + 1'b1; + end + return (value_i < 0) ? -s_quotient : s_quotient; + end + endfunction + + function automatic logic signed [31:0] sat32(input logic signed [63:0] value_i); + if (value_i > 64'sh0000_0000_7fff_ffff) return 32'sh7fff_ffff; + if (value_i < -64'sh0000_0000_8000_0000) return -32'sh8000_0000; + return value_i[31:0]; + endfunction + + function automatic logic signed [31:0] sat_width(input logic signed [63:0] value_i, + input logic [1:0] width_i); + logic signed [63:0] s_minimum, s_maximum; + begin + unique case (width_i) + 2'd0: begin + s_minimum = -64'sd32768; + s_maximum = 64'sd32767; + end + 2'd1: begin + s_minimum = -64'sd8388608; + s_maximum = 64'sd8388607; + end + default: begin + s_minimum = -64'sh0000_0000_8000_0000; + s_maximum = 64'sh0000_0000_7fff_ffff; + end + endcase + if (value_i < s_minimum) return s_minimum[31:0]; + if (value_i > s_maximum) return s_maximum[31:0]; + return value_i[31:0]; + end + endfunction + + always_comb begin + s_order = aux_i[5:0]; + s_accumulator = 72'sd0; + for (int index = 0; index < 32; index++) begin + if (index < s_order) begin + s_accumulator += $signed(coefficient_i[index]) * $signed(history_i[index]); + end + end + s_product = $signed(value0_i) * $signed(scale_i); + s_value0_ext = {{32{value0_i[31]}}, value0_i}; + s_value1_ext = {{32{value1_i[31]}}, value1_i}; + s_residual_ext = {{32{residual_i[31]}}, residual_i}; + s_predictor = 64'sd0; + unique case (aux_i[2:0]) + 3'd1: s_predictor = {{32{history_i[0][31]}}, history_i[0]}; + 3'd2: + s_predictor = 64'sd2 * {{32{history_i[0][31]}}, history_i[0]} - + {{32{history_i[1][31]}}, history_i[1]}; + 3'd3: + s_predictor = 64'sd3 * {{32{history_i[0][31]}}, history_i[0]} - + 64'sd3 * {{32{history_i[1][31]}}, history_i[1]} + + {{32{history_i[2][31]}}, history_i[2]}; + 3'd4: + s_predictor = 64'sd4 * {{32{history_i[0][31]}}, history_i[0]} - + 64'sd6 * {{32{history_i[1][31]}}, history_i[1]} + + 64'sd4 * {{32{history_i[2][31]}}, history_i[2]} - + {{32{history_i[3][31]}}, history_i[3]}; + default: s_predictor = 64'sd0; + endcase + s_value0 = s_value0_ext; + s_value1 = s_value1_ext; + if (predictor_shift_i >= 0) begin + s_lpc_shifted = s_accumulator >>> predictor_shift_i[5:0]; + s_lpc_shift_overflow = 1'b0; + end else begin + s_lpc_shifted = s_accumulator <<< (-predictor_shift_i); + s_lpc_shift_overflow = (s_lpc_shifted >>> (-predictor_shift_i)) != s_accumulator; + end + s_shifted = s_lpc_shifted[63:0]; + s_lpc_value = {s_lpc_shifted[71], s_lpc_shifted} + {{41{residual_i[31]}}, residual_i}; + result0_o = value0_i; + result1_o = value1_i; + overflow_o = (s_accumulator[71:63] != {9{s_accumulator[63]}}); + unique case (opcode_i) + `APB4_APU__MC_KERNEL_REQUANT: begin + s_shifted = aux_i[5] ? + rne(s_product, 6'(30 + aux_i[4:0])) : (s_product >>> (30 + aux_i[4:0])); + result0_o = sat_width(s_shifted, aux_i[7:6]); + end + `APB4_APU__MC_KERNEL_STEREO, `APB4_APU__MC_KERNEL_DECORRELATE: begin + unique case (aux_i[1:0]) + 2'd0: begin + s_value0 = s_value0_ext; + s_value1 = s_value1_ext; + end + 2'd1: begin + s_value0 = s_value0_ext; + s_value1 = s_value0_ext - s_value1_ext; + end + 2'd2: begin + s_value0 = s_value0_ext + s_value1_ext; + s_value1 = s_value1_ext; + end + default: begin + s_predictor = s_value0_ext * 64'sd2 + (s_value1_ext & 64'sd1); + s_value0 = (s_predictor + s_value1_ext) >>> 1; + s_value1 = (s_predictor - s_value1_ext) >>> 1; + end + endcase + overflow_o = (s_value0 > 64'sh0000_0000_7fff_ffff) || + (s_value0 < -64'sh0000_0000_8000_0000) || + (s_value1 > 64'sh0000_0000_7fff_ffff) || + (s_value1 < -64'sh0000_0000_8000_0000); + result0_o = sat32(s_value0); + result1_o = sat32(s_value1); + end + `APB4_APU__MC_KERNEL_FIXED: begin + s_value0 = s_predictor + s_residual_ext; + overflow_o = (s_value0 > 64'sh0000_0000_7fff_ffff) || + (s_value0 < -64'sh0000_0000_8000_0000); + result0_o = s_value0[31:0]; + end + `APB4_APU__MC_KERNEL_LPC: begin + overflow_o = overflow_o || s_lpc_shift_overflow || + (s_lpc_value > 73'sh0000_0000_7fff_ffff) || + (s_lpc_value < -73'sh0000_0000_8000_0000); + result0_o = s_lpc_value[31:0]; + end + default: begin + end + endcase + end +endmodule diff --git a/rtl/ip/multimedia/apu_reg.sv b/rtl/ip/multimedia/apu_reg.sv index 2796b4ec..a9e7dd15 100644 --- a/rtl/ip/multimedia/apu_reg.sv +++ b/rtl/ip/multimedia/apu_reg.sv @@ -80,8 +80,8 @@ module apu_reg ( ); localparam logic [31:0] IpId = 32'h4150_5530; localparam logic [31:0] IpVersion = 32'h0001_0000; - localparam logic [31:0] Capability0 = 32'h0000_0098; - localparam logic [31:0] Capability1 = 32'h0000_0010; + localparam logic [31:0] Capability0 = 32'h0000_0198; + localparam logic [31:0] Capability1 = 32'h0182_7010; localparam logic [31:0] AbiDigest = 32'd0; localparam logic [10:0] IrqMask = 11'h7ff; localparam logic [31:0] TimeoutReset = 32'h0000_ffff; diff --git a/rtl/ip/multimedia/apu_resampler.sv b/rtl/ip/multimedia/apu_resampler.sv new file mode 100644 index 00000000..27b6467c --- /dev/null +++ b/rtl/ip/multimedia/apu_resampler.sv @@ -0,0 +1,101 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +module apu_resampler ( + input logic [ 3:0] profile_i, + input logic [63:0] next_output_num_i, + output logic [15:0] ratio_l_o, + output logic [15:0] ratio_m_o, + output logic [ 5:0] phase_o, + output logic [63:0] source_index_o +); + logic [69:0] s_phase_numerator; + logic [63:0] s_source_remainder; + logic [ 5:0] s_phase_quotient; + logic [15:0] s_phase_remainder; + logic [16:0] s_phase_remainder_twice; + + always_comb begin + unique case (profile_i) + 4'd0: begin + ratio_l_o = 16'd1; + ratio_m_o = 16'd1; + end + 4'd1: begin + ratio_l_o = 16'd1; + ratio_m_o = 16'd2; + end + 4'd2: begin + ratio_l_o = 16'd80; + ratio_m_o = 16'd147; + end + 4'd3: begin + ratio_l_o = 16'd160; + ratio_m_o = 16'd147; + end + 4'd4: begin + ratio_l_o = 16'd3; + ratio_m_o = 16'd2; + end + 4'd5: begin + ratio_l_o = 16'd2; + ratio_m_o = 16'd1; + end + 4'd6: begin + ratio_l_o = 16'd3; + ratio_m_o = 16'd1; + end + 4'd7: begin + ratio_l_o = 16'd4; + ratio_m_o = 16'd1; + end + 4'd8: begin + ratio_l_o = 16'd6; + ratio_m_o = 16'd1; + end + 4'd9: begin + ratio_l_o = 16'd8; + ratio_m_o = 16'd1; + end + 4'd10: begin + ratio_l_o = 16'd12; + ratio_m_o = 16'd1; + end + 4'd11: begin + ratio_l_o = 16'd320; + ratio_m_o = 16'd147; + end + 4'd12: begin + ratio_l_o = 16'd640; + ratio_m_o = 16'd147; + end + 4'd13: begin + ratio_l_o = 16'd1280; + ratio_m_o = 16'd147; + end + 4'd14: begin + ratio_l_o = 16'd2; + ratio_m_o = 16'd3; + end + default: begin + ratio_l_o = 16'd4; + ratio_m_o = 16'd3; + end + endcase + source_index_o = next_output_num_i / {48'd0, ratio_l_o}; + s_source_remainder = next_output_num_i % {48'd0, ratio_l_o}; + s_phase_numerator = {6'd0, s_source_remainder} * 70'd32; + s_phase_quotient = 6'(s_phase_numerator / {54'd0, ratio_l_o}); + s_phase_remainder = 16'(s_phase_numerator % {54'd0, ratio_l_o}); + s_phase_remainder_twice = {1'b0, s_phase_remainder} << 1; + phase_o = s_phase_quotient; + if ((s_phase_remainder_twice > {1'b0, ratio_l_o}) || + ((s_phase_remainder_twice == {1'b0, ratio_l_o}) && s_phase_quotient[0])) begin + phase_o = phase_o + 1'b1; + end + if (phase_o == 6'd32) begin + phase_o = 6'd0; + source_index_o = source_index_o + 1'b1; + end + end +endmodule diff --git a/rtl/ip/multimedia/apu_transform_engine.sv b/rtl/ip/multimedia/apu_transform_engine.sv new file mode 100644 index 00000000..9b039412 --- /dev/null +++ b/rtl/ip/multimedia/apu_transform_engine.sv @@ -0,0 +1,32 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +module apu_transform_engine ( + input logic [ 5:0] order_i, + input logic signed [31:0] sample_i [0:31], + input logic signed [31:0] coefficient_i[0:31], + output logic signed [31:0] result_o, + output logic overflow_o +); + logic signed [71:0] s_accumulator; + logic signed [71:0] s_magnitude; + logic signed [71:0] s_rounded; + + always_comb begin + s_accumulator = 72'sd0; + for (int index = 0; index < 32; index++) begin + if (index < order_i) begin + s_accumulator += $signed(sample_i[index]) * $signed(coefficient_i[index]); + end + end + s_magnitude = (s_accumulator < 0) ? -s_accumulator : s_accumulator; + s_rounded = (s_magnitude >>> 30) + + (((s_magnitude[29:0] > 30'h2000_0000) || + ((s_magnitude[29:0] == 30'h2000_0000) && s_magnitude[30])) ? 72'd1 : 72'd0); + if (s_accumulator < 0) s_rounded = -s_rounded; + overflow_o = 1'b0; + if (s_rounded > 72'sh0000_0000_7fff_ffff) result_o = 32'sh7fff_ffff; + else if (s_rounded < -72'sh0000_0000_8000_0000) result_o = -32'sh8000_0000; + else result_o = s_rounded[31:0]; + end +endmodule diff --git a/rtl/mini/filelist/ip.fl b/rtl/mini/filelist/ip.fl index 74264cc8..c1bd4a0b 100644 --- a/rtl/mini/filelist/ip.fl +++ b/rtl/mini/filelist/ip.fl @@ -131,6 +131,14 @@ /ip/multimedia/apu_stream_router.sv /ip/multimedia/apu_control_store.sv /ip/multimedia/apu_microcode_loader.sv +/ip/multimedia/apu_local_sram.sv +/ip/multimedia/apu_bitstream_engine.sv +/ip/multimedia/apu_entropy_engine.sv +/ip/multimedia/apu_reconstruction_engine.sv +/ip/multimedia/apu_transform_engine.sv +/ip/multimedia/apu_resampler.sv +/ip/multimedia/apu_kernel_engine.sv +/ip/multimedia/apu_primitive_dispatcher.sv /ip/multimedia/apu_codec_sequencer.sv /ip/multimedia/apu_reg.sv /ip/multimedia/apb4_apu.sv diff --git a/rtl/mini/formal/apu_primitives_formal.sv b/rtl/mini/formal/apu_primitives_formal.sv new file mode 100644 index 00000000..fbd33df9 --- /dev/null +++ b/rtl/mini/formal/apu_primitives_formal.sv @@ -0,0 +1,332 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +`include "apu_define.svh" + +module apu_primitives_formal_design ( + // verilog_format: off -- preserve formal observation columns + input logic clk_i, + output logic rst_n_i, + output logic f_past_valid, + output logic [ 2:0] scenario_o, + output logic [ 2:0] phase_o, + output logic request_valid_o, + output logic request_ready_o, + output logic [ 3:0] request_class_o, + output logic request_accept_o, + output logic kernel_busy_o, + output logic kernel_outstanding_o, + output logic [31:0] kernel_latency_o, + output logic result_valid_o, + output logic result_kernel_o, + output logic result_error_o, + output logic [ 5:0] result_error_code_o, + output logic [ 3:0] result_error_stage_o, + output logic [ 7:0] result_error_reason_o, + output logic [31:0] result_cycles_o, + output logic done_o, + output logic last_error_o, + output logic [ 5:0] last_error_code_o, + output logic [ 3:0] last_error_stage_o, + output logic [ 7:0] last_error_reason_o, + output logic [31:0] last_data_o, + output logic [ 6:0] input_count_o, + output logic [ 6:0] output_count_o, + output logic memory_request_o + // verilog_format: on +); +`ifdef APU_PRIMITIVES_FORMAL_SCENARIO + localparam logic [2:0] f_scenario = `APU_PRIMITIVES_FORMAL_SCENARIO; +`else + (* anyconst *) logic [2:0] f_scenario; +`endif + + logic [ 7:0] s_cycle_q; + logic [ 2:0] s_phase_q; + logic [63:0] s_instruction; + logic [31:0] s_source0, s_source1, s_destination; + logic s_req_valid, s_req_ready; + logic s_result_valid, s_result_kernel, s_result_error; + logic [ 3:0] s_result_dst; + logic [ 3:0][31:0] s_result_data; + logic [ 2:0] s_result_words; + logic [ 5:0] s_result_error_code; + logic [ 3:0] s_result_error_stage; + logic [ 7:0] s_result_error_reason; + logic [31:0] s_result_cycles; + logic s_kernel_done, s_dispatcher_busy; + logic s_input_exhausted, s_input_ready, s_output_ready; + logic s_memory_req, s_memory_write, s_memory_valid, s_memory_error; + logic [16:0] s_memory_addr; + logic [31:0] s_memory_write_data, s_memory_read_data; + logic [3:0] s_memory_strb; + logic s_input_valid, s_input_accept, s_output_valid; + logic [40:0] s_input_data, s_output_data; + logic s_inject, s_codec_req, s_codec_write; + logic [16:0] s_codec_addr; + logic [31:0] s_codec_data; + logic [ 3:0] s_codec_strb; + logic s_kernel_outstanding_q; + logic [31:0] s_kernel_latency_q; + logic s_done_q, s_last_error_q; + logic [ 5:0] s_last_error_code_q; + logic [ 3:0] s_last_error_stage_q; + logic [ 7:0] s_last_error_reason_q; + logic [31:0] s_last_data_q; + + function automatic logic [63:0] encode_instruction( + input logic [3:0] class_i, input logic [3:0] opcode_i, input logic [3:0] dst_i, + input logic [3:0] src0_i, input logic [3:0] src1_i, input logic [7:0] aux_i, + input logic [31:0] immediate_i); + return {class_i, opcode_i, 4'd0, dst_i, src0_i, src1_i, aux_i, immediate_i}; + endfunction + + assign scenario_o = f_scenario; + assign phase_o = s_phase_q; + assign request_valid_o = s_req_valid; + assign request_ready_o = s_req_ready; + assign request_class_o = s_instruction[63:60]; + assign request_accept_o = s_req_valid && s_req_ready; + assign kernel_outstanding_o = s_kernel_outstanding_q; + assign kernel_latency_o = s_kernel_latency_q; + assign result_valid_o = s_result_valid; + assign result_kernel_o = s_result_kernel; + assign result_error_o = s_result_error; + assign result_error_code_o = s_result_error_code; + assign result_error_stage_o = s_result_error_stage; + assign result_error_reason_o = s_result_error_reason; + assign result_cycles_o = s_result_cycles; + assign done_o = s_done_q; + assign last_error_o = s_last_error_q; + assign last_error_code_o = s_last_error_code_q; + assign last_error_stage_o = s_last_error_stage_q; + assign last_error_reason_o = s_last_error_reason_q; + assign last_data_o = s_last_data_q; + assign memory_request_o = s_memory_req; + + always_comb begin + s_instruction = 64'd0; + s_source0 = 32'd0; + s_source1 = 32'd0; + s_destination = 32'd0; + s_req_valid = 1'b0; + if (rst_n_i) begin + unique case (f_scenario) + 3'd0: begin + s_instruction = encode_instruction(4'd4, 4'd0, 4'd0, 4'd0, 4'd0, 8'd0, 32'd0); + s_req_valid = s_phase_q == 3'd0; + end + 3'd1: begin + if (s_phase_q == 3'd0) begin + s_instruction = encode_instruction(4'd4, 4'd1, 4'd0, 4'd0, 4'd1, 8'd0, 32'd0); + s_source1 = 32'hdead_beef; + s_req_valid = 1'b1; + end else if (s_phase_q == 3'd2) begin + s_instruction = encode_instruction(4'd4, 4'd0, 4'd0, 4'd0, 4'd0, 8'd0, 32'd0); + s_req_valid = 1'b1; + end + end + 3'd2: begin + if (s_phase_q == 3'd0) begin + s_instruction = encode_instruction(4'd5, 4'd0, 4'd2, 4'd0, 4'd1, 8'h80, 32'd1); + s_source0 = 32'h20; + s_source1 = 32'h28; + s_destination = 32'h2c; + s_req_valid = s_cycle_q >= 8'd4; + end else if (s_phase_q == 3'd1) begin + s_instruction = encode_instruction(4'd4, 4'd1, 4'd0, 4'd0, 4'd1, 8'd0, 32'd0); + s_source0 = 32'h30; + s_source1 = 32'h1357_9bdf; + s_req_valid = 1'b1; + end else if (s_phase_q == 3'd3) begin + s_instruction = encode_instruction(4'd4, 4'd0, 4'd0, 4'd0, 4'd0, 8'd0, 32'd0); + s_source0 = 32'h30; + s_req_valid = 1'b1; + end + end + 3'd3: begin + s_instruction = encode_instruction(4'd4, 4'd5, 4'd0, 4'd0, 4'd0, 8'd0, 32'd0); + s_req_valid = (s_phase_q == 3'd0) && (s_cycle_q >= 8'd2); + end + 3'd4: begin + s_instruction = encode_instruction(4'd5, 4'd0, 4'd2, 4'd0, 4'd1, 8'h80, 32'd1); + s_source0 = 32'h21; + s_source1 = 32'h28; + s_destination = 32'h2c; + s_req_valid = (s_phase_q == 3'd0) && (s_cycle_q >= 8'd4); + end + 3'd5: begin + s_instruction = encode_instruction(4'd4, 4'd6, 4'd0, 4'd0, 4'd1, 8'd0, 32'd0); + s_source0 = 32'hcafe_f00d; + s_source1 = 32'h0000_0004; + s_req_valid = s_phase_q == 3'd0; + end + default: begin + s_instruction = encode_instruction(4'd4, 4'd1, 4'd0, 4'd0, 4'd1, 8'd0, 32'd1); + s_source0 = 32'hffff_ffff; + s_source1 = 32'hdead_beef; + s_req_valid = s_phase_q == 3'd0; + end + endcase + end + end + + assign s_input_valid = (f_scenario == 3'd3) && (s_cycle_q == 8'd1); + assign s_input_data = {1'b1, 5'd0, 3'd4, 32'h2468_ace0}; + assign s_inject = ((f_scenario == 3'd2) || (f_scenario == 3'd4)) && + (s_cycle_q inside {8'd1, 8'd2, 8'd3}); + assign s_codec_req = s_inject || s_memory_req; + assign s_codec_write = s_inject || s_memory_write; + assign s_codec_addr = s_inject ? + ((s_cycle_q == 8'd1) ? 17'h20 : ((s_cycle_q == 8'd2) ? 17'h24 : 17'h28)) : + s_memory_addr; + assign s_codec_data = s_inject ? + ((s_cycle_q == 8'd1) ? 32'd1000 : + ((s_cycle_q == 8'd2) ? 32'h4000_0000 : 32'h0000_0024)) : + s_memory_write_data; + assign s_codec_strb = s_inject ? 4'hf : s_memory_strb; + + apu_local_sram #( + .MemoryWordCount(64) + ) u_local_sram ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .image_valid_i (1'b1), + .table_bytes_i (16'd0), + .epoch_clear_i (1'b0), + .loader_active_i (1'b0), + .loader_req_i (1'b0), + .loader_addr_i (17'd0), + .loader_data_i (32'd0), + .loader_strb_i (4'd0), + .loader_ready_o (), + .codec_req_i (s_codec_req), + .codec_write_i (s_codec_write), + .codec_addr_i (s_codec_addr), + .codec_data_i (s_codec_data), + .codec_strb_i (s_codec_strb), + .codec_ready_o (), + .codec_data_o (s_memory_read_data), + .codec_valid_o (s_memory_valid), + .codec_access_err_o(s_memory_error) + ); + + apu_primitive_dispatcher u_dispatcher ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .flush_i (1'b0), + .req_valid_i (s_req_valid), + .req_ready_o (s_req_ready), + .instruction_i (s_instruction), + .source0_i (s_source0), + .source1_i (s_source1), + .destination_i (s_destination), + .scratch_base_i (17'd0), + .scratch_bytes_i (17'h100), + .table_offset_i (16'd0), + .table_bytes_i (16'd0), + .result_valid_o (s_result_valid), + .result_dst_o (s_result_dst), + .result_data_o (s_result_data), + .result_words_o (s_result_words), + .result_kernel_o (s_result_kernel), + .error_o (s_result_error), + .error_code_o (s_result_error_code), + .error_stage_o (s_result_error_stage), + .error_reason_o (s_result_error_reason), + .cycles_o (s_result_cycles), + .kernel_done_o (s_kernel_done), + .busy_o (s_dispatcher_busy), + .input_exhausted_o(s_input_exhausted), + .input_ready_o (s_input_ready), + .output_ready_o (s_output_ready), + .input_count_o (input_count_o), + .output_count_o (output_count_o), + .kernel_busy_o (kernel_busy_o), + .memory_req_o (s_memory_req), + .memory_write_o (s_memory_write), + .memory_addr_o (s_memory_addr), + .memory_data_o (s_memory_write_data), + .memory_strb_o (s_memory_strb), + .memory_valid_i (s_memory_valid), + .memory_data_i (s_memory_read_data), + .memory_error_i (s_memory_error), + .input_valid_i (s_input_valid), + .input_data_i (s_input_data), + .input_accept_o (s_input_accept), + .output_valid_o (s_output_valid), + .output_data_o (s_output_data), + .output_accept_i (1'b0) + ); + + initial begin + rst_n_i = 1'b0; + f_past_valid = 1'b0; + end + + always_ff @(posedge clk_i) begin + rst_n_i <= 1'b1; + f_past_valid <= 1'b1; + if (!rst_n_i) begin + s_cycle_q <= 8'd0; + s_phase_q <= 3'd0; + s_kernel_outstanding_q <= 1'b0; + s_kernel_latency_q <= 32'd0; + s_done_q <= 1'b0; + s_last_error_q <= 1'b0; + s_last_error_code_q <= 6'd0; + s_last_error_stage_q <= 4'd0; + s_last_error_reason_q <= 8'd0; + s_last_data_q <= 32'd0; + end else begin + s_cycle_q <= s_cycle_q + 1'b1; + if (s_kernel_outstanding_q) s_kernel_latency_q <= s_kernel_latency_q + 1'b1; + if (request_accept_o && (request_class_o == `APB4_APU__MC_CLASS_KERNEL)) begin + s_kernel_outstanding_q <= 1'b1; + s_kernel_latency_q <= 32'd1; + end + if (s_result_valid) begin + s_last_error_q <= s_result_error; + s_last_error_code_q <= s_result_error_code; + s_last_error_stage_q <= s_result_error_stage; + s_last_error_reason_q <= s_result_error_reason; + s_last_data_q <= s_result_data[0]; + if (s_result_kernel) s_kernel_outstanding_q <= 1'b0; + end + + unique case (f_scenario) + 3'd0, 3'd3, 3'd4, 3'd5, 3'd6: begin + if (request_accept_o) s_phase_q <= 3'd1; + if (s_result_valid) begin + s_phase_q <= 3'd7; + s_done_q <= 1'b1; + end + end + 3'd1: begin + if ((s_phase_q == 3'd0) && request_accept_o) s_phase_q <= 3'd1; + if ((s_phase_q == 3'd1) && s_result_valid) s_phase_q <= 3'd2; + if ((s_phase_q == 3'd2) && request_accept_o) s_phase_q <= 3'd3; + if ((s_phase_q == 3'd3) && s_result_valid) begin + s_phase_q <= 3'd7; + s_done_q <= 1'b1; + end + end + default: begin + if ((s_phase_q == 3'd0) && request_accept_o) s_phase_q <= 3'd1; + if ((s_phase_q == 3'd1) && request_accept_o) s_phase_q <= 3'd2; + if ((s_phase_q == 3'd2) && s_result_valid && !s_result_kernel) s_phase_q <= 3'd3; + if ((s_phase_q == 3'd3) && request_accept_o) s_phase_q <= 3'd4; + if ((s_phase_q == 3'd4) && s_result_valid) begin + s_phase_q <= 3'd7; + s_done_q <= 1'b1; + end + end + endcase + end + end + + logic s_unused; + assign s_unused = s_result_dst[0] ^ s_result_words[0] ^ s_kernel_done ^ s_dispatcher_busy ^ + s_input_exhausted ^ s_input_ready ^ s_output_ready ^ s_input_accept ^ s_output_valid ^ + s_output_data[0]; +endmodule diff --git a/rtl/mini/formal/apu_primitives_formal_props.sv b/rtl/mini/formal/apu_primitives_formal_props.sv new file mode 100644 index 00000000..281d3d79 --- /dev/null +++ b/rtl/mini/formal/apu_primitives_formal_props.sv @@ -0,0 +1,87 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +module apu_primitives_formal; + (* anyseq *) (* gclk *) reg clk_i; + wire rst_n_i, f_past_valid; + wire [2:0] scenario_o, phase_o; + wire request_valid_o, request_ready_o, request_accept_o; + wire [3:0] request_class_o; + wire kernel_busy_o, kernel_outstanding_o; + wire [31:0] kernel_latency_o; + wire result_valid_o, result_kernel_o, result_error_o; + wire [ 5:0] result_error_code_o; + wire [ 3:0] result_error_stage_o; + wire [ 7:0] result_error_reason_o; + wire [31:0] result_cycles_o; + wire done_o, last_error_o; + wire [ 5:0] last_error_code_o; + wire [ 3:0] last_error_stage_o; + wire [ 7:0] last_error_reason_o; + wire [31:0] last_data_o; + wire [6:0] input_count_o, output_count_o; + wire memory_request_o; + + apu_primitives_formal_design u_design (.*); + + always @(posedge clk_i) begin + assume (scenario_o <= 3'd6); + if (rst_n_i) begin + assert (input_count_o <= 7'd64); + assert (output_count_o <= 7'd64); + if (kernel_busy_o && request_valid_o && (request_class_o != 4'd5)) begin + assert (!request_ready_o); + end + if (request_accept_o && (request_class_o != 4'd5)) begin + assert (!kernel_busy_o); + end + if (result_valid_o && result_kernel_o && !result_error_o) begin + assert (kernel_outstanding_o); + assert (kernel_latency_o <= result_cycles_o); + end + if (done_o && (scenario_o == 3'd0)) begin + assert (last_error_o); + assert ({last_error_code_o, last_error_stage_o, last_error_reason_o} == + {6'd11, 4'd11, 8'd5}); + end + if (done_o && (scenario_o == 3'd1)) begin + assert (!last_error_o); + assert (last_data_o == 32'hdead_beef); + end + if (done_o && (scenario_o == 3'd2)) begin + assert (!last_error_o); + assert (last_data_o == 32'h1357_9bdf); + end + if (done_o && (scenario_o == 3'd3)) begin + assert (!last_error_o); + assert (last_data_o == 32'h2468_ace0); + end + if (done_o && (scenario_o == 3'd4)) begin + assert (last_error_o); + assert ({last_error_code_o, last_error_stage_o, last_error_reason_o} == + {6'd11, 4'd11, 8'd5}); + end + if (done_o && (scenario_o == 3'd5)) begin + assert (!last_error_o); + assert (output_count_o == 7'd1); + end + if ((scenario_o == 3'd6) && (phase_o == 3'd1)) begin + assert (!memory_request_o); + end + if (done_o && (scenario_o == 3'd6)) begin + assert (last_error_o); + assert ({last_error_code_o, last_error_stage_o, last_error_reason_o} == + {6'd11, 4'd11, 8'd5}); + assert (last_data_o == 32'd0); + end + end + + cover (rst_n_i && done_o); + cover (rst_n_i && ((scenario_o != 3'd2) || + (kernel_outstanding_o && request_valid_o && !request_ready_o && + (request_class_o == 4'd4)))); + end + + logic s_unused; + assign s_unused = f_past_valid ^ phase_o[0]; +endmodule diff --git a/rtl/mini/formal/apu_sequencer_formal.sv b/rtl/mini/formal/apu_sequencer_formal.sv index 887d4d74..0bb0b639 100644 --- a/rtl/mini/formal/apu_sequencer_formal.sv +++ b/rtl/mini/formal/apu_sequencer_formal.sv @@ -67,51 +67,77 @@ module apu_sequencer_formal_design ( apu_codec_sequencer u_dut ( .clk_i, .rst_n_i, - .soft_reset_i (1'b0), - .resource_reset_i (1'b0), - .counter_clear_i (1'b0), - .abort_i ((f_scenario == 2'd2) && (s_cycle_q == 6'd8)), - .launch_i (s_cycle_q == 6'd1), - .launch_entry_i (2'd0), - .image_valid_i (1'b1), - .timeout_i ((f_scenario == 2'd1) ? 32'd1 : 32'd16), - .entry_pc_i ({11'd0, 11'd0, 11'd0}), - .entry_first_i ({11'd0, 11'd0, 11'd0}), - .entry_last_i (s_entry_last), - .entry_max_loop_i ({16'd4, 16'd4, 16'd4}), - .entry_max_retired_i (s_entry_max_retired), - .input_exhausted_i (1'b1), - .input_ready_i (1'b0), - .output_ready_i (1'b1), - .kernel_done_i (1'b0), - .transport_idle_success_i(1'b1), - .stall_i (1'b0), - .cause_valid_i (1'b0), - .cause_code_i (6'd0), - .cause_stage_i (4'd0), - .cause_resp_i (2'd0), - .cause_index_i (8'd0), - .cause_addr_i (32'd0), - .cause_detail_i (32'd0), - .fetch_o (s_fetch), - .fetch_addr_o (s_fetch_addr), - .fetch_data_i (s_fetch_data), - .fetch_valid_i (s_fetch_pending_q), - .stat_o (status), - .retired_o (retired), - .gpr_o (s_gpr), - .trapped_o (trapped), - .trap_event_o (trap_event), - .abort_done_o (abort_done), - .fault_valid_o (fault_valid), - .fault_code_o (fault_code), - .fault_stage_o (fault_stage), - .fault_resp_o (fault_resp), - .fault_index_o (fault_index), - .fault_addr_o (fault_addr), - .fault_detail_o (fault_detail), - .perf_retired_o (), - .idle_o (idle) + .soft_reset_i (1'b0), + .resource_reset_i (1'b0), + .counter_clear_i (1'b0), + .abort_i ((f_scenario == 2'd2) && (s_cycle_q == 6'd8)), + .launch_i (s_cycle_q == 6'd1), + .launch_entry_i (2'd0), + .image_valid_i (1'b1), + .timeout_i ((f_scenario == 2'd1) ? 32'd1 : 32'd16), + .entry_pc_i ({11'd0, 11'd0, 11'd0}), + .entry_first_i ({11'd0, 11'd0, 11'd0}), + .entry_last_i (s_entry_last), + .entry_max_loop_i ({16'd4, 16'd4, 16'd4}), + .entry_max_retired_i (s_entry_max_retired), + .entry_scratch_base_i ('0), + .entry_scratch_bytes_i ('0), + .entry_primitive_mask_i ('0), + .entry_table_offset_i ('0), + .entry_table_bytes_i ('0), + .input_exhausted_i (1'b1), + .input_ready_i (1'b0), + .output_ready_i (1'b1), + .kernel_done_i (1'b0), + .transport_idle_success_i (1'b1), + .stall_i (1'b0), + .cause_valid_i (1'b0), + .cause_code_i (6'd0), + .cause_stage_i (4'd0), + .cause_resp_i (2'd0), + .cause_index_i (8'd0), + .cause_addr_i (32'd0), + .cause_detail_i (32'd0), + .primitive_req_valid_o (), + .primitive_req_ready_i (1'b0), + .primitive_instruction_o (), + .primitive_source0_o (), + .primitive_source1_o (), + .primitive_destination_o (), + .primitive_result_valid_i (1'b0), + .primitive_result_dst_i (4'd0), + .primitive_result_data_i ('0), + .primitive_result_words_i (3'd0), + .primitive_result_kernel_i(1'b0), + .primitive_error_i (1'b0), + .primitive_error_code_i (6'd0), + .primitive_error_stage_i (4'd0), + .primitive_error_reason_i (8'd0), + .fetch_o (s_fetch), + .fetch_addr_o (s_fetch_addr), + .fetch_data_i (s_fetch_data), + .fetch_valid_i (s_fetch_pending_q), + .stat_o (status), + .retired_o (retired), + .gpr_o (s_gpr), + .trapped_o (trapped), + .trap_event_o (trap_event), + .abort_done_o (abort_done), + .fault_valid_o (fault_valid), + .fault_code_o (fault_code), + .fault_stage_o (fault_stage), + .fault_resp_o (fault_resp), + .fault_index_o (fault_index), + .fault_addr_o (fault_addr), + .fault_detail_o (fault_detail), + .perf_retired_o (), + .active_scratch_base_o (), + .active_scratch_bytes_o (), + .active_primitive_mask_o (), + .active_table_offset_o (), + .active_table_bytes_o (), + .launch_epoch_o (), + .idle_o (idle) ); initial begin diff --git a/rtl/mini/formal/generate_formal_filelist.py b/rtl/mini/formal/generate_formal_filelist.py index 963f6bf3..e38e08ee 100644 --- a/rtl/mini/formal/generate_formal_filelist.py +++ b/rtl/mini/formal/generate_formal_filelist.py @@ -33,6 +33,15 @@ APU_LOADER_SCENARIOS = { "apu_loader_abort": 5, "apu_loader_resource_reset": 6, } +APU_PRIMITIVE_SCENARIOS = { + "apu_primitives_invalid_read": 0, + "apu_primitives_local": 1, + "apu_primitives_kernel": 2, + "apu_primitives_input_fifo": 3, + "apu_primitives_alignment": 4, + "apu_primitives_output_fifo": 5, + "apu_primitives_local_overflow": 6, +} def target_defines(target: str) -> list[str]: @@ -45,6 +54,11 @@ def target_defines(target: str) -> list[str]: "+define+SYNTHESIS", f"+define+APU_LOADER_FORMAL_SCENARIO={scenario}", ] + if target in APU_PRIMITIVE_SCENARIOS: + return [ + "+define+SV_ASSRT_DISABLE", + f"+define+APU_PRIMITIVES_FORMAL_SCENARIO={APU_PRIMITIVE_SCENARIOS[target]}", + ] if target == "opipsram": return ["+define+PDK_BEHAV"] if target == "sysctrl": @@ -273,6 +287,20 @@ def source_files(target: str) -> list[Path]: MULTIMEDIA / "apu_dma.sv", SCRIPT_DIR / "apu_formal.sv", ] + if target == "apu_primitives" or target in APU_PRIMITIVE_SCENARIOS: + return [ + COMMON_RTL / "utils/fifo.sv", + MULTIMEDIA / "apu_microcode_pkg.sv", + MULTIMEDIA / "apu_local_sram.sv", + MULTIMEDIA / "apu_bitstream_engine.sv", + MULTIMEDIA / "apu_entropy_engine.sv", + MULTIMEDIA / "apu_reconstruction_engine.sv", + MULTIMEDIA / "apu_transform_engine.sv", + MULTIMEDIA / "apu_resampler.sv", + MULTIMEDIA / "apu_kernel_engine.sv", + MULTIMEDIA / "apu_primitive_dispatcher.sv", + SCRIPT_DIR / "apu_primitives_formal.sv", + ] if target == "apu_loader" or target in APU_LOADER_SCENARIOS: return [ ROOT / "rtl/tech/tc_sram.sv", @@ -362,6 +390,8 @@ def parse_args() -> argparse.Namespace: "opipsram", "dma", "apu", + "apu_primitives", + *APU_PRIMITIVE_SCENARIOS, *APU_LOADER_SCENARIOS, "apu_sequencer", "gateway_a", diff --git a/rtl/mini/mk/formal.mk b/rtl/mini/mk/formal.mk index db71fb2d..b1c6a00f 100644 --- a/rtl/mini/mk/formal.mk +++ b/rtl/mini/mk/formal.mk @@ -31,12 +31,13 @@ FORMAL_APU_TIMEOUT ?= 300 FORMAL_GATEWAY_A_TIMEOUT ?= 120 FORMAL_SDIO_TIMEOUT ?= 120 FORMAL_APU_LOADER_TARGETS := apu_loader_success apu_loader_header_range apu_loader_descriptor_range apu_loader_crc apu_loader_control_flow apu_loader_abort apu_loader_resource_reset -FORMAL_TARGET_TIMEOUT = $(if $(filter dma,$*),$(FORMAL_DMA_TIMEOUT),$(if $(filter apu apu_sequencer $(FORMAL_APU_LOADER_TARGETS),$*),$(FORMAL_APU_TIMEOUT),$(if $(filter gateway_a,$*),$(FORMAL_GATEWAY_A_TIMEOUT),$(if $(filter sdio,$*),$(FORMAL_SDIO_TIMEOUT),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_TIMEOUT),$(if $(filter ws2812,$*),$(FORMAL_WS2812_TIMEOUT),$(if $(filter i2c,$*),$(FORMAL_I2C_TIMEOUT),$(FORMAL_TIMEOUT)))))))) +FORMAL_APU_PRIMITIVE_TARGETS := apu_primitives_invalid_read apu_primitives_local apu_primitives_local_overflow apu_primitives_kernel apu_primitives_input_fifo apu_primitives_alignment apu_primitives_output_fifo +FORMAL_TARGET_TIMEOUT = $(if $(filter dma,$*),$(FORMAL_DMA_TIMEOUT),$(if $(filter apu apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS),$*),$(FORMAL_APU_TIMEOUT),$(if $(filter gateway_a,$*),$(FORMAL_GATEWAY_A_TIMEOUT),$(if $(filter sdio,$*),$(FORMAL_SDIO_TIMEOUT),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_TIMEOUT),$(if $(filter ws2812,$*),$(FORMAL_WS2812_TIMEOUT),$(if $(filter i2c,$*),$(FORMAL_I2C_TIMEOUT),$(FORMAL_TIMEOUT)))))))) FORMAL_APU_TARGET_DEPTH = $(if $(filter $(FORMAL_APU_LOADER_TARGETS),$*),$(FORMAL_APU_LOADER_DEPTH),$(if $(filter apu_sequencer,$*),$(FORMAL_APU_SEQUENCER_DEPTH),$(FORMAL_APU_DEPTH))) FORMAL_APU_TARGET_COVER_DEPTH = $(if $(filter $(FORMAL_APU_LOADER_TARGETS),$*),$(FORMAL_APU_LOADER_COVER_DEPTH),$(FORMAL_APU_TARGET_DEPTH)) -FORMAL_TARGET_TOP = $(if $(filter $(FORMAL_APU_LOADER_TARGETS),$*),apu_loader,$*) -FORMAL_COMPACT_COVER_TARGETS := i2c opipsram dma $(FORMAL_APU_LOADER_TARGETS) -FORMAL_TARGETS := bus rib_adapter rib2apb sysctrl pll_rcu gpio ws2812 uart i2c timer clint dvp i2s psram onchip_ram opipsram dma apu $(FORMAL_APU_LOADER_TARGETS) apu_sequencer gateway_a sdio +FORMAL_TARGET_TOP = $(if $(filter $(FORMAL_APU_LOADER_TARGETS),$*),apu_loader,$(if $(filter $(FORMAL_APU_PRIMITIVE_TARGETS),$*),apu_primitives,$*)) +FORMAL_COMPACT_COVER_TARGETS := i2c opipsram dma $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS) +FORMAL_TARGETS := bus rib_adapter rib2apb sysctrl pll_rcu gpio ws2812 uart i2c timer clint dvp i2s psram onchip_ram opipsram dma apu $(FORMAL_APU_PRIMITIVE_TARGETS) $(FORMAL_APU_LOADER_TARGETS) apu_sequencer gateway_a sdio FORMAL_FILELIST_GENERATOR := $(RTL_PATH)/formal/generate_formal_filelist.py FORMAL_SBY_GENERATOR := $(RTL_PATH)/formal/generate_sby_config.py FORMAL_RESULT_GENERATOR := $(RTL_PATH)/formal/formal_results.py @@ -76,6 +77,8 @@ FORMAL_SOURCE_FILES := $(RTL_PATH)/formal/bus_formal.sv \ $(RTL_PATH)/formal/dma_formal_props.sv \ $(RTL_PATH)/formal/apu_formal.sv \ $(RTL_PATH)/formal/apu_formal_props.sv \ + $(RTL_PATH)/formal/apu_primitives_formal.sv \ + $(RTL_PATH)/formal/apu_primitives_formal_props.sv \ $(RTL_PATH)/formal/apu_loader_formal.sv \ $(RTL_PATH)/formal/apu_loader_formal_props.sv \ $(RTL_PATH)/formal/apu_sequencer_formal.sv \ @@ -212,14 +215,14 @@ $(FORMAL_DIR)/%/prove.sby: $(FORMAL_DIR)/%/design.v \ $(FORMAL_SBY_GENERATOR) python3 $(FORMAL_SBY_GENERATOR) --top $(FORMAL_TARGET_TOP)_formal --input $< \ --properties $(RTL_PATH)/formal/$(FORMAL_TARGET_TOP)_formal_props.sv --solver $(FORMAL_SOLVER) \ - --mode $(if $(filter dma apu apu_sequencer $(FORMAL_APU_LOADER_TARGETS) gateway_a sdio onchip_ram,$*),bmc,prove) --depth $(if $(filter pll_rcu,$*),$(FORMAL_PLL_RCU_DEPTH),$(if $(filter dma,$*),$(FORMAL_DMA_DEPTH),$(if $(filter apu apu_sequencer $(FORMAL_APU_LOADER_TARGETS),$*),$(FORMAL_APU_TARGET_DEPTH),$(if $(filter gateway_a,$*),$(FORMAL_GATEWAY_A_DEPTH),$(if $(filter sdio,$*),$(FORMAL_SDIO_DEPTH),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_DEPTH),$(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(if $(filter psram,$*),$(FORMAL_PSRAM_DEPTH),$(FORMAL_DEPTH))))))))))) --output $@ + --mode $(if $(filter dma apu apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS) gateway_a sdio onchip_ram,$*),bmc,prove) --depth $(if $(filter pll_rcu,$*),$(FORMAL_PLL_RCU_DEPTH),$(if $(filter dma,$*),$(FORMAL_DMA_DEPTH),$(if $(filter apu apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS),$*),$(FORMAL_APU_TARGET_DEPTH),$(if $(filter gateway_a,$*),$(FORMAL_GATEWAY_A_DEPTH),$(if $(filter sdio,$*),$(FORMAL_SDIO_DEPTH),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_DEPTH),$(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(if $(filter psram,$*),$(FORMAL_PSRAM_DEPTH),$(FORMAL_DEPTH))))))))))) --output $@ @if [ "$*" = opipsram ]; then sed -i '/^async2sync/i clk2fflogic' $@; fi $(FORMAL_DIR)/%/cover.sby: $(FORMAL_DIR)/%/design.v \ $(FORMAL_SBY_GENERATOR) python3 $(FORMAL_SBY_GENERATOR) --top $(FORMAL_TARGET_TOP)_formal --input $< \ --properties $(RTL_PATH)/formal/$(FORMAL_TARGET_TOP)_formal_props.sv --solver $(FORMAL_SOLVER) \ - --mode cover --depth $(if $(filter pll_rcu,$*),$(FORMAL_PLL_RCU_DEPTH),$(if $(filter dma,$*),$(FORMAL_DMA_COVER_DEPTH),$(if $(filter apu apu_sequencer $(FORMAL_APU_LOADER_TARGETS),$*),$(FORMAL_APU_TARGET_COVER_DEPTH),$(if $(filter gateway_a,$*),$(FORMAL_GATEWAY_A_DEPTH),$(if $(filter sdio,$*),$(FORMAL_SDIO_COVER_DEPTH),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_DEPTH),$(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(if $(filter psram,$*),$(FORMAL_PSRAM_DEPTH),$(FORMAL_DEPTH))))))))))) \ + --mode cover --depth $(if $(filter pll_rcu,$*),$(FORMAL_PLL_RCU_DEPTH),$(if $(filter dma,$*),$(FORMAL_DMA_COVER_DEPTH),$(if $(filter apu apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS),$*),$(FORMAL_APU_TARGET_COVER_DEPTH),$(if $(filter gateway_a,$*),$(FORMAL_GATEWAY_A_DEPTH),$(if $(filter sdio,$*),$(FORMAL_SDIO_COVER_DEPTH),$(if $(filter opipsram,$*),$(FORMAL_OPIPSRAM_DEPTH),$(if $(filter ws2812,$*),$(FORMAL_WS2812_DEPTH),$(if $(filter i2c,$*),$(FORMAL_I2C_DEPTH),$(if $(filter clint,$*),$(FORMAL_CLINT_DEPTH),$(if $(filter psram,$*),$(FORMAL_PSRAM_DEPTH),$(FORMAL_DEPTH))))))))))) \ $(if $(filter $(FORMAL_COMPACT_COVER_TARGETS),$*),--no-vcd) --output $@ @if [ "$*" = opipsram ]; then sed -i '/^async2sync/i clk2fflogic' $@; fi @@ -316,7 +319,7 @@ formal-opipsram: $(FORMAL_DIR)/opipsram/.stamp | manifest formal-dma: $(FORMAL_DIR)/dma/.stamp | manifest -formal-apu: $(FORMAL_DIR)/apu/.stamp | manifest +formal-apu: $(FORMAL_DIR)/apu/.stamp $(addsuffix /.stamp,$(addprefix $(FORMAL_DIR)/,$(FORMAL_APU_PRIMITIVE_TARGETS))) | manifest formal-apu-loader: $(addsuffix /.stamp,$(addprefix $(FORMAL_DIR)/,$(FORMAL_APU_LOADER_TARGETS))) | manifest diff --git a/rtl/rtl_style_audit.json b/rtl/rtl_style_audit.json index d04c25c3..35b3796f 100644 --- a/rtl/rtl_style_audit.json +++ b/rtl/rtl_style_audit.json @@ -135,15 +135,23 @@ "rtl/ip/multimedia": [ "apb4_apu.sv", "apb4_jpeg.sv", + "apu_bitstream_engine.sv", "apu_codec_sequencer.sv", "apu_control_store.sv", "apu_dma.sv", "apu_define.svh", + "apu_entropy_engine.sv", + "apu_kernel_engine.sv", + "apu_local_sram.sv", "apu_microcode_loader.sv", "apu_microcode_pkg.sv", + "apu_primitive_dispatcher.sv", + "apu_reconstruction_engine.sv", + "apu_resampler.sv", "apu_ring_scheduler.sv", "apu_reg.sv", "apu_stream_router.sv", + "apu_transform_engine.sv", "axi4s_dvp.sv", "dvp_core.sv", "dvp_define.svh", diff --git a/scripts/README.md b/scripts/README.md index 93ee4d30..cecafd5d 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -4,10 +4,11 @@ This directory contains the Python implementation of setup, dependency locking, build manifests, flow execution, simulation checks, regression orchestration, quality checks, metrics, packaging, and cleanup. -`apu_mcasm.py`, `apu_isa.py`, and `apu_interpreter.py` are the frozen APU -microcode V1 assembler/ABI model and P3 reference interpreter. They accept only -the APU instruction language and deliberately have no C, ELF, RV32, dynamic -linking, or runtime code-generation path. +`apu_mcasm.py`, `apu_isa.py`, `apu_interpreter.py`, and `apu_primitives.py` are +the frozen APU microcode V1 assembler/ABI model, P3/P4 target interpreters, and +bit-accurate P4 primitive model. They accept only the APU instruction language +and deliberately have no C, ELF, RV32, dynamic linking, or runtime +code-generation path. Scripts are part of the build contract. Prefer existing helpers over ad-hoc shell behavior, preserve structured JSON results, and keep setup/download diff --git a/scripts/apu_interpreter.py b/scripts/apu_interpreter.py index fafec85a..05b27908 100644 --- a/scripts/apu_interpreter.py +++ b/scripts/apu_interpreter.py @@ -1,4 +1,4 @@ -"""Reference interpreter for the enabled APU-P3 microcode subset.""" +"""Reference interpreter for the enabled APU-P3/P4 microcode targets.""" from __future__ import annotations @@ -8,15 +8,20 @@ from dataclasses import dataclass, field from pathlib import Path from apu_isa import ( + APUMC_TARGETS, + BitstreamOpcode, ControlOpcode, + EntropyOpcode, Entry, Instruction, InstructionClass, + LocalOpcode, ScalarOpcode, parse_apumc, trap_detail, - validate_p3_instruction, + validate_instruction, ) +from apu_primitives import PrimitiveBam, PrimitiveFault, PrimitiveResult class SequencerTrap(RuntimeError): @@ -55,15 +60,36 @@ class Machine: loops: dict[int, tuple[int, int]] = field(default_factory=dict) watchdog: int = 0 trace: list[dict[str, object]] = field(default_factory=list) + target: str = "p3" + primitives: PrimitiveBam | None = None + primitive_cycles: list[int] = field(default_factory=list) + kernel_result: PrimitiveResult | None = field(default=None, init=False) + kernel_fault: PrimitiveFault | None = field(default=None, init=False) + kernel_pending_fault: PrimitiveFault | None = field(default=None, init=False) + kernel_pending_dst: int | None = field(default=None, init=False) + kernel_cycles_remaining: int = field(default=0, init=False) def __post_init__(self) -> None: - self.pc = self.entry.entry_pc + self.begin_entry(self.entry) @staticmethod def _signed(value: int) -> int: return value if value < 0x80000000 else value - 0x100000000 def _predicate(self, predicate: int) -> bool: + input_ready = self.predicate_inputs.input_ready + output_ready = self.predicate_inputs.output_ready + kernel_done = self.predicate_inputs.kernel_done + input_exhausted = self.predicate_inputs.input_exhausted + if self.target == "p4" and self.primitives is not None: + input_ready = not self.primitives.input_fifo.empty + output_ready = not self.primitives.output_fifo.full + kernel_done = self.primitives.kernel_done + input_exhausted = ( + self.primitives.eof + and self.primitives.input_fifo.empty + and not self.primitives.bits + ) values = ( True, self.eq, @@ -72,10 +98,10 @@ class Machine: not self.signed_lt, self.unsigned_lt, not self.unsigned_lt, - self.predicate_inputs.input_exhausted, - self.predicate_inputs.input_ready, - self.predicate_inputs.output_ready, - self.predicate_inputs.kernel_done, + input_exhausted, + input_ready, + output_ready, + kernel_done, self.predicate_inputs.transport_done, ) if predicate >= len(values): @@ -85,6 +111,86 @@ class Machine: def _write(self, register: int, value: int) -> None: self.registers[register] = value & 0xFFFFFFFF + def _complete_kernel(self) -> None: + if self.kernel_pending_fault is not None: + self.kernel_fault = self.kernel_pending_fault + elif self.kernel_result is not None: + for register, value in self.kernel_result.writes.items(): + self._write(register, value) + if self.primitives is not None: + self.primitives.kernel_done = True + self.kernel_result = None + self.kernel_pending_fault = None + self.kernel_pending_dst = None + self.kernel_cycles_remaining = 0 + + def _tick_kernel(self) -> None: + if self.kernel_pending_dst is None: + return + if self.kernel_cycles_remaining > 0: + self.kernel_cycles_remaining -= 1 + if self.kernel_cycles_remaining == 0: + self._complete_kernel() + + def _pending_dependency(self, instruction: Instruction) -> bool: + pending = self.kernel_pending_dst + if pending is None: + return False + instruction_class = instruction.instruction_class + opcode = instruction.opcode + if instruction_class == InstructionClass.CONTROL: + if opcode == ControlOpcode.END: + return True + return opcode == ControlOpcode.LOOP_SETUP and instruction.src0 == pending + if instruction_class == InstructionClass.SCALAR: + if opcode == ScalarOpcode.MOV: + return instruction.src0 == pending or instruction.dst == pending + if opcode == ScalarOpcode.MOVI: + return instruction.dst == pending + if opcode == ScalarOpcode.CMP: + return instruction.src0 == pending or instruction.src1 == pending + if opcode == ScalarOpcode.SAT: + return instruction.src0 == pending or instruction.dst == pending + return ( + instruction.src0 == pending + or instruction.src1 == pending + or instruction.dst == pending + ) + return True + + def begin_entry(self, entry: Entry | None = None) -> None: + if entry is not None: + self.entry = entry + self.pc = self.entry.entry_pc + self.retired = 0 + self.registers = [0] * 16 + self.eq = False + self.signed_lt = False + self.unsigned_lt = False + self.return_stack.clear() + self.loops.clear() + self.watchdog = 0 + self.kernel_result = None + self.kernel_fault = None + self.kernel_pending_fault = None + self.kernel_pending_dst = None + self.kernel_cycles_remaining = 0 + if self.primitives is not None: + self.primitives.reset_mutable() + + def abort(self) -> None: + self.kernel_result = None + self.kernel_fault = None + self.kernel_pending_fault = None + self.kernel_pending_dst = None + self.kernel_cycles_remaining = 0 + self.watchdog = 0 + if self.primitives is not None: + self.primitives.reset_mutable() + + def reset(self) -> None: + self.begin_entry(self.entry) + def _retire(self, instruction: Instruction, predicate_true: bool) -> None: self.retired += 1 self.watchdog = 0 @@ -97,7 +203,9 @@ class Machine: } ) - def _no_retirement_cycle(self, instruction: Instruction) -> None: + def _no_retirement_cycle(self, instruction: Instruction, *, tick_kernel: bool = True) -> None: + if tick_kernel: + self._tick_kernel() self.watchdog += 1 if self.timeout <= 0 or self.watchdog >= self.timeout: raise SequencerTrap(7, self.pc, instruction) @@ -115,8 +223,13 @@ class Machine: if self.pc < self.entry.first_pc or self.pc > self.entry.last_pc: raise SequencerTrap(2, self.pc, Instruction(0, 0)) instruction = self.instructions[self.pc] + self._tick_kernel() + if self.kernel_fault is not None: + fault = self.kernel_fault + self.kernel_fault = None + raise SequencerTrap(fault.reason, self.pc, instruction) try: - validate_p3_instruction(instruction) + validate_instruction(instruction, self.target) except ValueError as error: raise SequencerTrap(1, self.pc, instruction) from error if self.retired >= self.entry.max_retired: @@ -129,6 +242,10 @@ class Machine: self.pc += 1 return False + if self._pending_dependency(instruction): + self._no_retirement_cycle(instruction, tick_kernel=False) + return False + next_pc = self.pc + 1 terminal = False if instruction.instruction_class == InstructionClass.CONTROL: @@ -167,7 +284,18 @@ class Machine: next_pc = target else: del self.loops[slot] - else: + elif opcode == ControlOpcode.WAIT: + ready = False + if self.primitives is not None: + ready = ( + (instruction.aux == 1 and self.primitives.kernel_done) + or (instruction.aux == 2 and not self.primitives.input_fifo.empty) + or (instruction.aux == 3 and not self.primitives.output_fifo.full) + ) + if not ready: + self._no_retirement_cycle(instruction, tick_kernel=False) + return False + elif instruction.instruction_class == InstructionClass.SCALAR: opcode = ScalarOpcode(instruction.opcode) source0 = self.registers[instruction.src0] source1 = self.registers[instruction.src1] @@ -205,6 +333,71 @@ class Machine: instruction.dst, self._saturate(source0, instruction.aux & 0x1F, bool(instruction.aux & 0x20)), ) + else: + if self.primitives is None: + raise SequencerTrap(6, self.pc, instruction) + if instruction.instruction_class == InstructionClass.LOCAL: + opcode = LocalOpcode(instruction.opcode) + if (opcode == LocalOpcode.FIFO_POP and self.primitives.input_fifo.empty) or ( + opcode == LocalOpcode.FIFO_PUSH and self.primitives.output_fifo.full + ): + self._no_retirement_cycle(instruction, tick_kernel=False) + return False + if instruction.instruction_class == InstructionClass.BITSTREAM: + opcode = BitstreamOpcode(instruction.opcode) + required = ( + instruction.immediate & 0x3F + if opcode + in ( + BitstreamOpcode.REFILL, + BitstreamOpcode.PEEK, + BitstreamOpcode.GET, + BitstreamOpcode.SKIP, + ) + else 1 + ) + self.primitives._refill(required) + if len(self.primitives.bits) < required and not self.primitives.eof: + self._no_retirement_cycle(instruction, tick_kernel=False) + return False + if instruction.instruction_class == InstructionClass.ENTROPY: + opcode = EntropyOpcode(instruction.opcode) + if opcode <= EntropyOpcode.HUFF_QUAD: + required = instruction.aux + elif opcode == EntropyOpcode.UNARY: + required = instruction.immediate + 1 + elif opcode == EntropyOpcode.SIGN_RESTORE: + required = 0 if self.registers[instruction.src0] == 0 else ( + self.registers[instruction.src1] & 0x1F + ) + 1 + else: + required = 1 + self.primitives._refill(required) + if len(self.primitives.bits) < required and not self.primitives.eof: + self._no_retirement_cycle(instruction, tick_kernel=False) + return False + try: + if instruction.instruction_class == InstructionClass.KERNEL: + self.primitives.kernel_done = False + result = self.primitives.execute(instruction, self.registers, self.entry) + except PrimitiveFault as error: + if instruction.instruction_class != InstructionClass.KERNEL or error.reason == 5: + raise SequencerTrap(error.reason, self.pc, instruction) from error + result = PrimitiveResult(cycles=1, kernel_done=True) + self.kernel_pending_fault = error + self.primitive_cycles.append(result.cycles) + if instruction.instruction_class == InstructionClass.KERNEL: + self.primitives.kernel_done = False + self.kernel_result = result + self.kernel_pending_dst = instruction.dst + self.kernel_cycles_remaining = max(result.cycles - 1, 0) + if self.kernel_cycles_remaining == 0: + self._complete_kernel() + else: + for register, value in result.writes.items(): + self._write(register, value) + for _ in range(max(result.cycles - 1, 0)): + self._no_retirement_cycle(instruction) self._retire(instruction, predicate_true) if not terminal: @@ -214,30 +407,36 @@ class Machine: return terminal def run(self) -> dict[str, object]: + fetch_required = True while True: if self.pc < self.entry.first_pc or self.pc > self.entry.last_pc: raise SequencerTrap(2, self.pc, Instruction(0, 0)) instruction = self.instructions[self.pc] - for _ in range(self.fetch_no_retirement_cycles): - self._no_retirement_cycle(instruction) - remaining_stalls = self.stall_cycles.get(self.pc, 0) - while remaining_stalls > 0: - self._no_retirement_cycle(instruction) - remaining_stalls -= 1 - self.stall_cycles[self.pc] = 0 + if fetch_required: + for _ in range(self.fetch_no_retirement_cycles): + self._no_retirement_cycle(instruction) + remaining_stalls = self.stall_cycles.get(self.pc, 0) + while remaining_stalls > 0: + self._no_retirement_cycle(instruction) + remaining_stalls -= 1 + self.stall_cycles[self.pc] = 0 + retired_before = self.retired if self.step(): break + fetch_required = self.retired != retired_before return { "pc": self.pc, "retired": self.retired, "registers": self.registers, "trace": self.trace, + "primitive_cycles": self.primitive_cycles, } def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("bundle", type=Path) + parser.add_argument("--target", choices=APUMC_TARGETS, default="p3") parser.add_argument("--entry", type=int, choices=range(3), default=0) parser.add_argument("--timeout", type=int, default=0xFFFF) parser.add_argument("--input-exhausted", action="store_true") @@ -246,7 +445,7 @@ def main() -> int: parser.add_argument("--kernel-done", action="store_true") parser.add_argument("--transport-done", action="store_true") args = parser.parse_args() - instructions, entries, _ = parse_apumc(args.bundle.read_bytes()) + instructions, entries, header = parse_apumc(args.bundle.read_bytes(), args.target) inputs = PredicateInputs( input_exhausted=args.input_exhausted, input_ready=args.input_ready, @@ -256,7 +455,15 @@ def main() -> int: ) print( json.dumps( - Machine(instructions, entries[args.entry], inputs, args.timeout).run(), sort_keys=True + Machine( + instructions, + entries[args.entry], + inputs, + args.timeout, + target=args.target, + primitives=PrimitiveBam() if args.target == "p4" else None, + ).run(), + sort_keys=True, ) ) return 0 diff --git a/scripts/apu_isa.py b/scripts/apu_isa.py index 6729c4c5..4f85eea8 100644 --- a/scripts/apu_isa.py +++ b/scripts/apu_isa.py @@ -17,6 +17,9 @@ APUMC_ENTRY_COUNT = 3 APUMC_INSTRUCTION_OFFSET = 192 APUMC_MAX_INSTRUCTIONS = 2048 APUMC_P3_CAPABILITY_MASK = 0 +APUMC_P4_CAPABILITY_MASK = 0x0000FFFF +APUMC_P4_TABLE_SCRATCH_BYTES = 0x6000 +APUMC_TARGETS = ("p3", "p4") class InstructionClass(IntEnum): @@ -58,6 +61,50 @@ class ScalarOpcode(IntEnum): SAT = 13 +class BitstreamOpcode(IntEnum): + REFILL = 0 + PEEK = 1 + GET = 2 + SKIP = 3 + ALIGN = 4 + FRAME_SYNC = 5 + CRC8 = 6 + CRC16 = 7 + + +class EntropyOpcode(IntEnum): + HUFF_SYMBOL = 0 + HUFF_PAIR = 1 + HUFF_QUAD = 2 + UNARY = 3 + RICE4 = 4 + RICE5 = 5 + SIGN_RESTORE = 6 + + +class LocalOpcode(IntEnum): + LD32 = 0 + ST32 = 1 + TABLE8 = 2 + TABLE16 = 3 + TABLE32 = 4 + FIFO_POP = 5 + FIFO_PUSH = 6 + + +class KernelOpcode(IntEnum): + REQUANT = 0 + STEREO = 1 + IMDCT6 = 2 + IMDCT18 = 3 + DCT32_POLY = 4 + FIXED = 5 + LPC = 6 + DECORRELATE = 7 + RESAMPLE = 8 + PCM_PACK = 9 + + PREDICATES = { "always": 0, "eq": 1, @@ -184,6 +231,9 @@ def abi_manifest() -> dict[str, object]: "table": 7, }, "p3_implemented_primitive_mask": APUMC_P3_CAPABILITY_MASK, + "p4_implemented_primitive_mask": APUMC_P4_CAPABILITY_MASK, + "p4_local_data_bytes": 0x1C000, + "p4_local_table_scratch_bytes": APUMC_P4_TABLE_SCRATCH_BYTES, } @@ -281,9 +331,36 @@ def _require_zero(instruction: Instruction, names: Iterable[str]) -> None: raise ValueError(f"{name} is reserved for this opcode") -def validate_p3_instruction(instruction: Instruction) -> None: - """Validate one instruction against the P3 class-0/class-1 subset.""" +def _target_mask(target: str) -> int: + if target not in APUMC_TARGETS: + raise ValueError(f"unknown APU target {target}") + return APUMC_P3_CAPABILITY_MASK if target == "p3" else APUMC_P4_CAPABILITY_MASK + +def instruction_required_mask(instruction: Instruction) -> int: + instruction_class = instruction.instruction_class + opcode = instruction.opcode + if instruction_class == InstructionClass.CONTROL and opcode == ControlOpcode.WAIT: + if instruction.aux == 1: + return 0x0000FFC0 + if instruction.aux in (2, 3): + return 1 << 5 + return 0 + if instruction_class == InstructionClass.BITSTREAM: + return (1 << 0) | ((1 << 1) if opcode in (6, 7) else 0) + if instruction_class == InstructionClass.ENTROPY: + return (1 << 0) | (1 << (2 if opcode <= 2 else 3)) + if instruction_class == InstructionClass.LOCAL: + return 1 << (5 if opcode in (5, 6) else 4) + if instruction_class == InstructionClass.KERNEL: + return 1 << (opcode + 6) + return 0 + + +def validate_instruction(instruction: Instruction, target: str = "p3") -> None: + """Validate one frozen V1 instruction for the selected implementation target.""" + + target_mask = _target_mask(target) if instruction.predicate > 11: raise ValueError("predicate is reserved") if instruction.instruction_class == InstructionClass.CONTROL: @@ -318,45 +395,136 @@ def validate_p3_instruction(instruction: Instruction) -> None: _require_zero(instruction, ("dst", "src0", "src1", "immediate")) if instruction.predicate != 0 or instruction.aux > 5: raise ValueError("WAIT encoding is invalid") - raise ValueError("WAIT requires a primitive unavailable in P3") + if target == "p3" or instruction.aux not in (1, 2, 3): + raise ValueError(f"WAIT source is unavailable in {target.upper()}") return - if instruction.instruction_class != InstructionClass.SCALAR: - if instruction.instruction_class <= 6: - raise ValueError("instruction class is unavailable in P3") + if instruction.instruction_class == InstructionClass.SCALAR: + if instruction.opcode > ScalarOpcode.SAT: + raise ValueError("scalar opcode is unallocated") + opcode = ScalarOpcode(instruction.opcode) + if opcode == ScalarOpcode.MOV: + _require_zero(instruction, ("src1", "aux", "immediate")) + elif opcode == ScalarOpcode.MOVI: + _require_zero(instruction, ("src0", "src1", "aux")) + elif opcode in ( + ScalarOpcode.ADD, + ScalarOpcode.SUB, + ScalarOpcode.AND, + ScalarOpcode.OR, + ScalarOpcode.XOR, + ScalarOpcode.SHL, + ScalarOpcode.SHR, + ScalarOpcode.SAR, + ): + _require_zero(instruction, ("aux", "immediate")) + elif opcode == ScalarOpcode.CMP: + _require_zero(instruction, ("dst", "aux", "immediate")) + elif opcode in (ScalarOpcode.MIN, ScalarOpcode.MAX): + _require_zero(instruction, ("immediate",)) + if instruction.aux > 1: + raise ValueError("MIN/MAX aux must select unsigned or signed") + elif opcode == ScalarOpcode.SAT: + _require_zero(instruction, ("src1", "immediate")) + if instruction.aux & 0xC0 or (instruction.aux & 0x1F) not in (0, 8, 16, 24): + raise ValueError("SAT width/sign encoding is invalid") + return + + if instruction.instruction_class > InstructionClass.TRANSPORT: raise ValueError("instruction class is invalid in V1") - if instruction.opcode > ScalarOpcode.SAT: - raise ValueError("scalar opcode is unallocated") + if target == "p3" or instruction.instruction_class == InstructionClass.TRANSPORT: + raise ValueError(f"instruction class is unavailable in {target.upper()}") - opcode = ScalarOpcode(instruction.opcode) - if opcode == ScalarOpcode.MOV: - _require_zero(instruction, ("src1", "aux", "immediate")) - elif opcode == ScalarOpcode.MOVI: - _require_zero(instruction, ("src0", "src1", "aux")) - elif opcode in ( - ScalarOpcode.ADD, - ScalarOpcode.SUB, - ScalarOpcode.AND, - ScalarOpcode.OR, - ScalarOpcode.XOR, - ScalarOpcode.SHL, - ScalarOpcode.SHR, - ScalarOpcode.SAR, - ): - _require_zero(instruction, ("aux", "immediate")) - elif opcode == ScalarOpcode.CMP: - _require_zero(instruction, ("dst", "aux", "immediate")) - elif opcode in (ScalarOpcode.MIN, ScalarOpcode.MAX): - _require_zero(instruction, ("immediate",)) - if instruction.aux > 1: - raise ValueError("MIN/MAX aux must select unsigned or signed") - elif opcode == ScalarOpcode.SAT: - _require_zero(instruction, ("src1", "immediate")) - if instruction.aux & 0xC0 or (instruction.aux & 0x1F) not in (0, 8, 16, 24): - raise ValueError("SAT width/sign encoding is invalid") + if instruction.instruction_class == InstructionClass.BITSTREAM: + if instruction.opcode > BitstreamOpcode.CRC16: + raise ValueError("bitstream opcode is unallocated") + opcode = BitstreamOpcode(instruction.opcode) + if opcode in (BitstreamOpcode.REFILL, BitstreamOpcode.PEEK, BitstreamOpcode.GET): + _require_zero(instruction, ("src0", "src1", "aux")) + if not 1 <= instruction.immediate <= 32: + raise ValueError("bit width must be 1..32") + elif opcode == BitstreamOpcode.SKIP: + _require_zero(instruction, ("dst", "src0", "src1", "aux")) + if not 1 <= instruction.immediate <= 32: + raise ValueError("bit width must be 1..32") + elif opcode == BitstreamOpcode.ALIGN: + _require_zero(instruction, ("dst", "src0", "src1", "aux", "immediate")) + elif opcode == BitstreamOpcode.FRAME_SYNC: + if not 8 <= instruction.aux <= 16 or not 1 <= instruction.immediate <= 0xFFFF: + raise ValueError("FRAME_SYNC width/limit is invalid") + else: + _require_zero(instruction, ("aux", "immediate")) + elif instruction.instruction_class == InstructionClass.ENTROPY: + if instruction.opcode > EntropyOpcode.SIGN_RESTORE: + raise ValueError("entropy opcode is unallocated") + opcode = EntropyOpcode(instruction.opcode) + if opcode <= EntropyOpcode.HUFF_QUAD: + if not 1 <= instruction.aux <= 24: + raise ValueError("Huffman maximum length must be 1..24") + if opcode == EntropyOpcode.HUFF_PAIR and instruction.dst > 14: + raise ValueError("HUFF_PAIR destination must be 0..14") + if opcode == EntropyOpcode.HUFF_QUAD and instruction.dst > 12: + raise ValueError("HUFF_QUAD destination must be 0..12") + _require_zero(instruction, ("immediate",)) + elif opcode == EntropyOpcode.UNARY: + _require_zero(instruction, ("src0", "src1")) + if instruction.aux > 1 or not 1 <= instruction.immediate <= 0xFFFF: + raise ValueError("UNARY polarity/maximum is invalid") + else: + _require_zero(instruction, ("aux", "immediate")) + elif instruction.instruction_class == InstructionClass.LOCAL: + if instruction.opcode > LocalOpcode.FIFO_PUSH: + raise ValueError("local opcode is unallocated") + opcode = LocalOpcode(instruction.opcode) + if opcode == LocalOpcode.LD32: + _require_zero(instruction, ("src1", "aux")) + if instruction.immediate >> 16: + raise ValueError("LD32 immediate must fit 16 bits") + elif opcode == LocalOpcode.ST32: + _require_zero(instruction, ("dst", "aux")) + if instruction.immediate >> 16: + raise ValueError("ST32 immediate must fit 16 bits") + elif opcode in (LocalOpcode.TABLE8, LocalOpcode.TABLE16, LocalOpcode.TABLE32): + _require_zero(instruction, ("aux", "immediate")) + elif opcode == LocalOpcode.FIFO_POP: + _require_zero(instruction, ("src0", "src1", "aux", "immediate")) + if instruction.dst > 14: + raise ValueError("FIFO_POP destination must be 0..14") + else: + _require_zero(instruction, ("dst", "aux", "immediate")) + elif instruction.instruction_class == InstructionClass.KERNEL: + if instruction.opcode > KernelOpcode.PCM_PACK or not 1 <= instruction.immediate <= 0xFFFF: + raise ValueError("kernel opcode/count is invalid") + opcode = KernelOpcode(instruction.opcode) + if opcode == KernelOpcode.REQUANT and (instruction.aux >> 6) == 3: + raise ValueError("REQUANT output width is invalid") + if opcode in (KernelOpcode.STEREO, KernelOpcode.DECORRELATE) and instruction.aux >> 2: + raise ValueError("stereo mode is invalid") + if opcode in (KernelOpcode.IMDCT6, KernelOpcode.IMDCT18, KernelOpcode.DCT32_POLY) and instruction.aux: + raise ValueError("transform aux is reserved") + if opcode == KernelOpcode.FIXED and instruction.aux > 4: + raise ValueError("fixed predictor order is invalid") + if opcode == KernelOpcode.LPC and not 1 <= instruction.aux <= 32: + raise ValueError("LPC order is invalid") + if opcode == KernelOpcode.RESAMPLE and instruction.aux > 15: + raise ValueError("resampler profile is invalid") + if opcode == KernelOpcode.PCM_PACK and (instruction.aux >> 3 or (instruction.aux & 3) > 1): + raise ValueError("PCM pack format is invalid") + + if instruction_required_mask(instruction) & ~target_mask: + raise ValueError(f"required primitive is unavailable in {target.upper()}") -def validate_entry(entry: Entry, instruction_count: int) -> None: +def validate_p3_instruction(instruction: Instruction) -> None: + validate_instruction(instruction, "p3") + + +def validate_p4_instruction(instruction: Instruction) -> None: + validate_instruction(instruction, "p4") + + +def validate_entry(entry: Entry, instruction_count: int, target: str = "p3", + table_payload_bytes: int = 0) -> None: if entry.format_id not in range(APUMC_ENTRY_COUNT): raise ValueError("entry format must be 0, 1, or 2") if not (0 <= entry.first_pc <= entry.entry_pc <= entry.last_pc < instruction_count): @@ -365,7 +533,7 @@ def validate_entry(entry: Entry, instruction_count: int) -> None: raise ValueError("maximum loop count must be 1..65535") if not 1 <= entry.max_retired <= 0xFFFFFF: raise ValueError("maximum retired count must be 1..16777215") - if any( + if target == "p3" and any( ( entry.scratch_base, entry.scratch_bytes, @@ -375,9 +543,25 @@ def validate_entry(entry: Entry, instruction_count: int) -> None: ) ): raise ValueError("P3 entries cannot use scratch, table, or primitives") + if target == "p4": + if entry.primitive_mask & ~APUMC_P4_CAPABILITY_MASK: + raise ValueError("P4 entry requires an unavailable primitive") + if entry.scratch_base & 3 or entry.scratch_bytes & 3: + raise ValueError("scratch range must be four-byte aligned") + scratch_end = entry.scratch_base + entry.scratch_bytes + if scratch_end > APUMC_P4_TABLE_SCRATCH_BYTES: + raise ValueError("scratch range exceeds the P4 codec workspace") + if entry.scratch_bytes and entry.scratch_base < table_payload_bytes: + raise ValueError("scratch range overlaps the active table payload") + if entry.table_offset & 3 or entry.table_bytes & 3: + raise ValueError("entry table range must be four-byte aligned") + if entry.table_offset + entry.table_bytes > table_payload_bytes: + raise ValueError("entry table range exceeds the active table payload") -def control_flow_report(instructions: list[Instruction], entry: Entry) -> dict[str, object]: +def control_flow_report( + instructions: list[Instruction], entry: Entry, target: str = "p3" +) -> dict[str, object]: """Prove bounded V1 control flow and return its deterministic report.""" loop_pairs: dict[int, int] = {} @@ -389,8 +573,8 @@ def control_flow_report(instructions: list[Instruction], entry: Entry) -> dict[s continue opcode = ControlOpcode(instruction.opcode) if opcode in (ControlOpcode.JUMP_FWD, ControlOpcode.CALL_FWD): - target = pc + 1 + instruction.immediate - if target > entry.last_pc: + branch_target = pc + 1 + instruction.immediate + if branch_target > entry.last_pc: raise ValueError(f"entry {entry.format_id} branch at PC {pc} leaves its range") elif opcode == ControlOpcode.LOOP_SETUP: slot = instruction.aux & 3 @@ -445,16 +629,16 @@ def control_flow_report(instructions: list[Instruction], entry: Entry) -> dict[s if opcode in (ControlOpcode.END, ControlOpcode.TRAP): terminals.add(pc) elif opcode == ControlOpcode.JUMP_FWD: - target = pc + 1 + instruction.immediate - successors.append((target, returns, loops)) - edges.add((pc, target, "jump_true")) + branch_target = pc + 1 + instruction.immediate + successors.append((branch_target, returns, loops)) + edges.add((pc, branch_target, "jump_true")) elif opcode == ControlOpcode.CALL_FWD: if len(returns) == 4: raise ValueError(f"entry {entry.format_id} can exceed four nested calls") - target = pc + 1 + instruction.immediate - successors.append((target, (*returns, pc + 1), loops)) + branch_target = pc + 1 + instruction.immediate + successors.append((branch_target, (*returns, pc + 1), loops)) maximum_call_depth = max(maximum_call_depth, len(returns) + 1) - edges.add((pc, target, "call_true")) + edges.add((pc, branch_target, "call_true")) elif opcode == ControlOpcode.RET: if not returns: raise ValueError(f"entry {entry.format_id} can return with an empty stack") @@ -481,8 +665,11 @@ def control_flow_report(instructions: list[Instruction], entry: Entry) -> dict[s elif opcode == ControlOpcode.NOP: successors.append((pc + 1, returns, loops)) edges.add((pc, pc + 1, "fallthrough")) + elif opcode == ControlOpcode.WAIT and target == "p4": + successors.append((pc + 1, returns, loops)) + edges.add((pc, pc + 1, "wait_ready")) else: - raise ValueError("WAIT requires a primitive unavailable in P3") + raise ValueError(f"WAIT requires a primitive unavailable in {target.upper()}") pending.extend(successors) if len(visited) > APUMC_MAX_INSTRUCTIONS * 64: raise ValueError(f"entry {entry.format_id} control-flow proof exceeded its bound") @@ -510,32 +697,64 @@ def control_flow_report(instructions: list[Instruction], entry: Entry) -> dict[s } -def validate_control_flow(instructions: list[Instruction], entry: Entry) -> None: +def validate_control_flow( + instructions: list[Instruction], entry: Entry, target: str = "p3" +) -> None: """Prove bounded V1 control flow for one entry descriptor.""" - control_flow_report(instructions, entry) + control_flow_report(instructions, entry, target) -def build_apumc(instructions: list[Instruction], entries: list[Entry], build_id: int = 0) -> bytes: +def build_apumc( + instructions: list[Instruction], + entries: list[Entry], + build_id: int = 0, + *, + target: str = "p3", + table_payload: bytes = b"", +) -> bytes: + target_mask = _target_mask(target) if not 1 <= len(instructions) <= APUMC_MAX_INSTRUCTIONS: raise ValueError("instruction count must be 1..2048") + if len(table_payload) & 3 or len(table_payload) > APUMC_P4_TABLE_SCRATCH_BYTES: + raise ValueError("table payload must be four-byte aligned and at most 24 KiB") + if target == "p3" and table_payload: + raise ValueError("P3 does not admit a table payload") if len(entries) != APUMC_ENTRY_COUNT: raise ValueError("APUMC V1 requires exactly three entries") for index, entry in enumerate(entries): if entry.format_id != index: raise ValueError("entry descriptor order must be WAV, MP3, FLAC") - validate_entry(entry, len(instructions)) + validate_entry(entry, len(instructions), target, len(table_payload)) for instruction in instructions: - validate_p3_instruction(instruction) + validate_instruction(instruction, target) for entry in entries: - validate_control_flow(instructions, entry) + validate_control_flow(instructions, entry, target) + for pc in range(entry.first_pc, entry.last_pc + 1): + required = instruction_required_mask(instructions[pc]) + if instructions[pc].instruction_class == InstructionClass.CONTROL and required: + if instructions[pc].aux == 1: + if not (entry.primitive_mask & required): + raise ValueError("kernel WAIT requires a class-5 primitive") + elif required & ~entry.primitive_mask: + raise ValueError("FIFO WAIT requires the local-FIFO primitive") + elif required & ~entry.primitive_mask: + raise ValueError(f"entry {entry.format_id} omits an instruction primitive") descriptor = b"".join(struct.pack("<8I", *entry.words()) for entry in entries) padding = bytes(APUMC_INSTRUCTION_OFFSET - APUMC_HEADER_BYTES - len(descriptor)) encoded = b"".join(struct.pack("> 32) & 0xFFFFFFFF, @@ -558,7 +777,10 @@ def build_apumc(instructions: list[Instruction], entries: list[Entry], build_id: return header + payload -def parse_apumc(bundle: bytes) -> tuple[list[Instruction], list[Entry], tuple[int, ...]]: +def parse_apumc( + bundle: bytes, target: str = "p3" +) -> tuple[list[Instruction], list[Entry], tuple[int, ...]]: + target_mask = _target_mask(target) if len(bundle) < APUMC_HEADER_BYTES: raise ValueError("bundle is smaller than the APUMC header") header = struct.unpack_from("<16I", bundle) @@ -582,7 +804,7 @@ def parse_apumc(bundle: bytes) -> tuple[list[Instruction], list[Entry], tuple[in entry_end = entry_offset + APUMC_ENTRY_COUNT * APUMC_ENTRY_BYTES if not 1 <= instruction_count <= APUMC_MAX_INSTRUCTIONS: raise ValueError("APUMC instruction count is invalid") - if ( + common_range_error = ( instruction_offset < APUMC_HEADER_BYTES or instruction_offset % 64 != 0 or instruction_end > len(bundle) @@ -591,28 +813,40 @@ def parse_apumc(bundle: bytes) -> tuple[list[Instruction], list[Entry], tuple[in or entry_end > len(bundle) or not (instruction_end <= entry_offset or entry_end <= instruction_offset) or table_offset % 4 != 0 - or table_bytes != 0 - or table_offset != 0 - or header[9] != 0 - or header[10] != 0 - ): + ) + table_end = table_offset + table_bytes + if common_range_error: + raise ValueError("APUMC range fields are invalid") + if target == "p3" and (table_bytes or table_offset or header[9] or header[10]): raise ValueError("APUMC range or P3 capability fields are invalid") + if target == "p4" and ( + table_bytes > APUMC_P4_TABLE_SCRATCH_BYTES + or table_bytes & 3 + or table_offset > len(bundle) + or (table_bytes and (table_offset < APUMC_HEADER_BYTES or table_end > len(bundle))) + or (table_bytes and not ( + (table_end <= instruction_offset or table_offset >= instruction_end) + and (table_end <= entry_offset or table_offset >= entry_end) + )) + or header[9] & ~target_mask + or header[10] > APUMC_P4_TABLE_SCRATCH_BYTES + ): + raise ValueError("APUMC range or P4 capability fields are invalid") if crc32_iso_hdlc(bundle[APUMC_HEADER_BYTES:]) != header[11]: raise ValueError("APUMC CRC mismatch") entries = [] for index in range(APUMC_ENTRY_COUNT): words = struct.unpack_from("<8I", bundle, entry_offset + index * APUMC_ENTRY_BYTES) - if ( + reserved_error = ( words[0] >> 15 or (words[1] & 0xF800F800) - or words[2] - or words[3] or words[4] >> 16 or words[5] >> 24 - or words[6] - or words[7] - ): - raise ValueError(f"APUMC entry {index} has invalid reserved or P3 fields") + or words[2] >> 17 + or words[3] >> 17 + ) + if reserved_error or (target == "p3" and any(words[item] for item in (2, 3, 6, 7))): + raise ValueError(f"APUMC entry {index} has invalid reserved or target fields") entries.append( Entry( format_id=words[0] & 0xF, @@ -638,14 +872,30 @@ def parse_apumc(bundle: bytes) -> tuple[list[Instruction], list[Entry], tuple[in occupied[instruction_offset:instruction_end] = bytes([1]) * ( instruction_end - instruction_offset ) + if table_bytes: + occupied[table_offset:table_end] = bytes([1]) * table_bytes if any(byte for index, byte in enumerate(bundle) if not occupied[index]): raise ValueError("APUMC padding must be zero") for index, entry in enumerate(entries): if entry.format_id != index: raise ValueError("entry descriptor order must be WAV, MP3, FLAC") - validate_entry(entry, instruction_count) + validate_entry(entry, instruction_count, target, table_bytes) for instruction in instructions: - validate_p3_instruction(instruction) + validate_instruction(instruction, target) for entry in entries: - validate_control_flow(instructions, entry) + validate_control_flow(instructions, entry, target) + for pc in range(entry.first_pc, entry.last_pc + 1): + required = instruction_required_mask(instructions[pc]) + if instructions[pc].instruction_class == InstructionClass.CONTROL and required: + if instructions[pc].aux == 1: + if not (entry.primitive_mask & required): + raise ValueError("kernel WAIT requires a class-5 primitive") + elif required & ~entry.primitive_mask: + raise ValueError("FIFO WAIT requires the local-FIFO primitive") + elif required & ~entry.primitive_mask: + raise ValueError(f"entry {entry.format_id} omits an instruction primitive") + if header[9] != (entries[0].primitive_mask | entries[1].primitive_mask | entries[2].primitive_mask): + raise ValueError("APUMC header primitive mask does not match entry masks") + if header[10] != max(entry.scratch_base + entry.scratch_bytes for entry in entries): + raise ValueError("APUMC scratch end does not match entry descriptors") return instructions, entries, header diff --git a/scripts/apu_mcasm.py b/scripts/apu_mcasm.py index ef882393..d4b2ef7c 100644 --- a/scripts/apu_mcasm.py +++ b/scripts/apu_mcasm.py @@ -1,21 +1,28 @@ -"""Assemble and statically verify frozen APU-P3 microcode bundles.""" +"""Assemble and statically verify frozen APU-P3/P4 microcode bundles.""" from __future__ import annotations import argparse import json import re +import struct from dataclasses import dataclass from pathlib import Path from apu_isa import ( CONTROL_OPCODES, + DEFERRED_OPCODES, PREDICATES, SCALAR_OPCODES, + APUMC_TARGETS, + BitstreamOpcode, ControlOpcode, + EntropyOpcode, Entry, Instruction, InstructionClass, + KernelOpcode, + LocalOpcode, ScalarOpcode, abi_manifest, build_apumc, @@ -31,6 +38,8 @@ class Assembly: instructions: list[Instruction] entries: list[Entry] build_id: int + target: str + table_payload: bytes def _number(token: str) -> int: @@ -91,10 +100,100 @@ def _encode_instruction(tokens: list[str], pc: int, labels: dict[str, int]) -> I values["aux"] = _number(args[0]) return Instruction(InstructionClass.CONTROL, opcode, **values) - if mnemonic not in SCALAR_OPCODES: + if mnemonic in SCALAR_OPCODES: + opcode = ScalarOpcode(SCALAR_OPCODES[mnemonic]) + values = {"predicate": predicate} + elif mnemonic in DEFERRED_OPCODES["bitstream"]: + opcode = BitstreamOpcode(DEFERRED_OPCODES["bitstream"].index(mnemonic)) + values = {"predicate": predicate} + if opcode in (BitstreamOpcode.REFILL, BitstreamOpcode.PEEK, BitstreamOpcode.GET): + if len(args) != 2: + raise ValueError(f"{mnemonic} takes a destination and bit width") + values.update(dst=_register(args[0]), immediate=_number(args[1])) + elif opcode == BitstreamOpcode.SKIP: + if len(args) != 1: + raise ValueError("skip takes one bit width") + values["immediate"] = _number(args[0]) + elif opcode == BitstreamOpcode.ALIGN: + if args: + raise ValueError("align takes no operands") + elif opcode == BitstreamOpcode.FRAME_SYNC: + if len(args) != 5: + raise ValueError("frame_sync takes dst, pattern, mask, width, and limit") + values.update( + dst=_register(args[0]), + src0=_register(args[1]), + src1=_register(args[2]), + aux=_number(args[3]), + immediate=_number(args[4]), + ) + else: + if len(args) != 3: + raise ValueError(f"{mnemonic} takes dst, accumulator, and byte registers") + values.update(dst=_register(args[0]), src0=_register(args[1]), src1=_register(args[2])) + return Instruction(InstructionClass.BITSTREAM, opcode, **values) + elif mnemonic in DEFERRED_OPCODES["entropy"]: + opcode = EntropyOpcode(DEFERRED_OPCODES["entropy"].index(mnemonic)) + values = {"predicate": predicate} + if opcode <= EntropyOpcode.HUFF_QUAD: + if len(args) != 4: + raise ValueError(f"{mnemonic} takes dst, table, entries, and maximum length") + values.update( + dst=_register(args[0]), + src0=_register(args[1]), + src1=_register(args[2]), + aux=_number(args[3]), + ) + elif opcode == EntropyOpcode.UNARY: + if len(args) != 3: + raise ValueError("unary takes dst, polarity, and maximum run") + values.update(dst=_register(args[0]), aux=_number(args[1]), immediate=_number(args[2])) + else: + if len(args) != 3: + raise ValueError(f"{mnemonic} takes three registers") + values.update(dst=_register(args[0]), src0=_register(args[1]), src1=_register(args[2])) + return Instruction(InstructionClass.ENTROPY, opcode, **values) + elif mnemonic in DEFERRED_OPCODES["local"]: + opcode = LocalOpcode(DEFERRED_OPCODES["local"].index(mnemonic)) + values = {"predicate": predicate} + if opcode == LocalOpcode.LD32: + if len(args) != 3: + raise ValueError("ld32 takes destination, address register, and offset") + values.update(dst=_register(args[0]), src0=_register(args[1]), immediate=_number(args[2]) & 0xFFFF) + elif opcode == LocalOpcode.ST32: + if len(args) != 3: + raise ValueError("st32 takes address, value, and offset") + values.update(src0=_register(args[0]), src1=_register(args[1]), immediate=_number(args[2]) & 0xFFFF) + elif opcode in (LocalOpcode.TABLE8, LocalOpcode.TABLE16, LocalOpcode.TABLE32): + if len(args) != 3: + raise ValueError(f"{mnemonic} takes destination, base, and index registers") + values.update(dst=_register(args[0]), src0=_register(args[1]), src1=_register(args[2])) + elif opcode == LocalOpcode.FIFO_POP: + if len(args) != 1: + raise ValueError("fifo_pop takes one destination") + values["dst"] = _register(args[0]) + else: + if len(args) != 2: + raise ValueError("fifo_push takes data and metadata registers") + values.update(src0=_register(args[0]), src1=_register(args[1])) + return Instruction(InstructionClass.LOCAL, opcode, **values) + elif mnemonic in DEFERRED_OPCODES["kernel"]: + opcode = KernelOpcode(DEFERRED_OPCODES["kernel"].index(mnemonic)) + if len(args) != 5: + raise ValueError(f"{mnemonic} takes output, input, parameter, aux, and count") + return Instruction( + InstructionClass.KERNEL, + opcode, + predicate=predicate, + dst=_register(args[0]), + src0=_register(args[1]), + src1=_register(args[2]), + aux=_number(args[3]), + immediate=_number(args[4]), + ) + else: raise ValueError(f"unknown mnemonic {mnemonic}") - opcode = ScalarOpcode(SCALAR_OPCODES[mnemonic]) - values = {"predicate": predicate} + if opcode == ScalarOpcode.MOV: if len(args) != 2: raise ValueError("mov takes two registers") @@ -127,7 +226,9 @@ def _encode_instruction(tokens: list[str], pc: int, labels: dict[str, int]) -> I return Instruction(InstructionClass.SCALAR, opcode, **values) -def assemble(source: str) -> Assembly: +def assemble(source: str, target: str = "p3") -> Assembly: + if target not in APUMC_TARGETS: + raise ValueError(f"unknown APU target {target}") lines = [] labels: dict[str, int] = {} directives: list[list[str]] = [] @@ -149,11 +250,14 @@ def assemble(source: str) -> Assembly: build_id = 0 entry_directives = [] + table_words: list[int] = [] for directive in directives: if directive[0] == ".build_id" and len(directive) == 2: build_id = _number(directive[1]) - elif directive[0] == ".entry" and len(directive) == 7: + elif directive[0] == ".entry" and len(directive) in (7, 12): entry_directives.append(directive[1:]) + elif directive[0] == ".table32" and len(directive) > 1: + table_words.extend(_number(value) & 0xFFFFFFFF for value in directive[1:]) else: raise ValueError(f"invalid assembler directive {' '.join(directive)}") if len(entry_directives) != 3: @@ -169,21 +273,26 @@ def assemble(source: str) -> Assembly: entries = [] for values in entry_directives: try: - entries.append( - Entry( - format_id=_number(values[0]), - entry_pc=labels[values[1]], - first_pc=labels[values[2]], - last_pc=labels[values[3]], - max_loop_count=_number(values[4]), - max_retired=_number(values[5]), - ) - ) + optional = [0, 0, 0, 0, 0] if len(values) == 6 else [_number(item) for item in values[6:]] + entries.append(Entry( + format_id=_number(values[0]), + entry_pc=labels[values[1]], + first_pc=labels[values[2]], + last_pc=labels[values[3]], + max_loop_count=_number(values[4]), + max_retired=_number(values[5]), + scratch_base=optional[0], + scratch_bytes=optional[1], + primitive_mask=optional[2], + table_offset=optional[3], + table_bytes=optional[4], + )) except KeyError as error: raise ValueError(f"unknown entry label {error.args[0]}") from error entries.sort(key=lambda entry: entry.format_id) - bundle = build_apumc(instructions, entries, build_id) - return Assembly(bundle, dict(sorted(labels.items())), instructions, entries, build_id) + table_payload = b"".join(struct.pack(" dict[str, object]: @@ -199,6 +308,7 @@ def _artifact_data(assembly: Assembly) -> dict[str, object]: def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("source", type=Path) + parser.add_argument("--target", choices=APUMC_TARGETS, default="p3") parser.add_argument("-o", "--output", type=Path, required=True) parser.add_argument("--symbols", type=Path) parser.add_argument("--cfg-report", type=Path) @@ -206,7 +316,7 @@ def main() -> int: parser.add_argument("--trace-input", type=Path) parser.add_argument("--abi-input-manifest", type=Path) args = parser.parse_args() - assembly = assemble(args.source.read_text(encoding="utf-8")) + assembly = assemble(args.source.read_text(encoding="utf-8"), args.target) args.output.write_bytes(assembly.bundle) artifacts = { args.symbols: assembly.symbols, @@ -214,12 +324,16 @@ def main() -> int: **_artifact_data(assembly), "entries": [entry.__dict__ for entry in assembly.entries], "control_flow": [ - control_flow_report(assembly.instructions, entry) for entry in assembly.entries + control_flow_report(assembly.instructions, entry, assembly.target) + for entry in assembly.entries ], }, args.primitive_manifest: { - "implemented_mask": 0, - "required_mask": 0, + "target": assembly.target, + "implemented_mask": 0 if assembly.target == "p3" else 0x0000FFFF, + "required_mask": __import__("functools").reduce( + int.__or__, (entry.primitive_mask for entry in assembly.entries), 0 + ), "entry_masks": [entry.primitive_mask for entry in assembly.entries], }, args.trace_input: { diff --git a/scripts/apu_primitives.py b/scripts/apu_primitives.py new file mode 100644 index 00000000..10c85fc7 --- /dev/null +++ b/scripts/apu_primitives.py @@ -0,0 +1,680 @@ +"""Bit-accurate APU-P4 primitive reference model.""" + +from __future__ import annotations + +from dataclasses import dataclass, field + +from apu_isa import ( + BitstreamOpcode, + EntropyOpcode, + Entry, + Instruction, + InstructionClass, + KernelOpcode, + LocalOpcode, +) + + +LOCAL_DATA_BYTES = 0x1C000 +CODEC_BYTES = 0xA000 +TABLE_SCRATCH_BYTES = 0x6000 +INPUT_BASE = 0x6000 +OUTPUT_BASE = 0x8000 +KWS_BASE = 0xA000 +INTERNAL_BASE = 0x1A000 + + +class PrimitiveFault(RuntimeError): + def __init__(self, code: int, stage: int, reason: int = 9): + self.code = code + self.stage = stage + self.reason = reason + super().__init__(f"APU primitive fault code={code} stage={stage} reason={reason}") + + +@dataclass +class LocalMemory: + data: bytearray = field(default_factory=lambda: bytearray(LOCAL_DATA_BYTES)) + valid_words: list[bool] = field(default_factory=lambda: [False] * (CODEC_BYTES // 4)) + table_bytes: int = 0 + image_valid: bool = False + + def publish_table(self, payload: bytes) -> None: + if len(payload) > TABLE_SCRATCH_BYTES or len(payload) & 3: + raise ValueError("P4 table payload is outside the frozen workspace") + self.data[: len(payload)] = payload + self.table_bytes = len(payload) + self.image_valid = True + + def invalidate_image(self) -> None: + self.image_valid = False + self.table_bytes = 0 + + def new_epoch(self) -> None: + self.valid_words = [False] * len(self.valid_words) + + def _check_range(self, address: int, size: int, entry: Entry, table: bool = False) -> None: + if address < 0 or size < 1 or address + size > LOCAL_DATA_BYTES: + raise PrimitiveFault(11, 11, 5) + if table: + first = entry.table_offset + last = first + entry.table_bytes + if not self.image_valid or address < first or address + size > last: + raise PrimitiveFault(11, 11, 5) + else: + first = entry.scratch_base + last = first + entry.scratch_bytes + if address < first or address + size > last: + raise PrimitiveFault(11, 11, 5) + + def read(self, address: int, size: int, entry: Entry, table: bool = False) -> bytes: + self._check_range(address, size, entry, table) + if not table: + first_word = address // 4 + last_word = (address + size - 1) // 4 + if any(not self.valid_words[index] for index in range(first_word, last_word + 1)): + raise PrimitiveFault(11, 11, 5) + return bytes(self.data[address : address + size]) + + def write(self, address: int, payload: bytes, entry: Entry) -> None: + self._check_range(address, len(payload), entry) + if address < self.table_bytes: + raise PrimitiveFault(11, 11, 5) + self.data[address : address + len(payload)] = payload + first_word = address // 4 + last_word = (address + len(payload) - 1) // 4 + for index in range(first_word, last_word + 1): + self.valid_words[index] = True + + def inject(self, address: int, payload: bytes) -> None: + if address < 0 or address + len(payload) > CODEC_BYTES: + raise ValueError("verification injection exceeds the P4 codec region") + self.data[address : address + len(payload)] = payload + first_word = address // 4 + last_word = (address + len(payload) - 1) // 4 + for index in range(first_word, last_word + 1): + self.valid_words[index] = True + + +@dataclass +class PrimitiveFifo: + words: list[int] = field(default_factory=list) + + @property + def full(self) -> bool: + return len(self.words) == 64 + + @property + def empty(self) -> bool: + return not self.words + + def push(self, word: int) -> None: + if self.full or word & (0x1F << 35) or not 1 <= ((word >> 32) & 7) <= 4: + raise PrimitiveFault(22, 7) + self.words.append(word & ((1 << 41) - 1)) + + def pop(self) -> int: + if self.empty: + raise PrimitiveFault(22, 4) + return self.words.pop(0) + + def flush(self) -> None: + self.words.clear() + + +@dataclass +class PrimitiveResult: + writes: dict[int, int] = field(default_factory=dict) + cycles: int = 1 + kernel_done: bool = False + + +class PrimitiveBam: + def __init__(self, memory: LocalMemory | None = None): + self.memory = memory if memory is not None else LocalMemory() + self.input_fifo = PrimitiveFifo() + self.output_fifo = PrimitiveFifo() + self.bits: list[int] = [] + self.eof = False + self.bit_position = 0 + self.kernel_done = False + self.resample_profile: int | None = None + + @staticmethod + def signed(value: int, width: int = 32) -> int: + value &= (1 << width) - 1 + return value - (1 << width) if value & (1 << (width - 1)) else value + + @staticmethod + def sat(value: int, width: int) -> int: + return min(max(value, -(1 << (width - 1))), (1 << (width - 1)) - 1) + + @staticmethod + def rne(value: int, shift: int) -> int: + if shift == 0: + return value + magnitude = abs(value) + quotient, remainder = divmod(magnitude, 1 << shift) + halfway = 1 << (shift - 1) + if remainder > halfway or (remainder == halfway and quotient & 1): + quotient += 1 + return -quotient if value < 0 else quotient + + @staticmethod + def _u32(value: int) -> int: + return value & 0xFFFFFFFF + + def reset_mutable(self) -> None: + self.memory.new_epoch() + self.input_fifo.flush() + self.output_fifo.flush() + self.bits.clear() + self.eof = False + self.bit_position = 0 + self.kernel_done = False + self.resample_profile = None + + def _refill(self, width: int) -> None: + while len(self.bits) < width and not self.input_fifo.empty: + word = self.input_fifo.pop() + count = (word >> 32) & 7 + for byte_index in range(count): + byte = (word >> (8 * byte_index)) & 0xFF + self.bits.extend((byte >> bit_index) & 1 for bit_index in range(7, -1, -1)) + if word & (1 << 40): + self.eof = True + + def _peek_bits(self, width: int, stage: int) -> int: + self._refill(width) + if len(self.bits) < width: + raise PrimitiveFault(5, stage) + value = 0 + for bit in self.bits[:width]: + value = (value << 1) | bit + return value + + def _get_bits(self, width: int, stage: int) -> int: + value = self._peek_bits(width, stage) + del self.bits[:width] + self.bit_position = (self.bit_position + width) & 7 + return value + + def _read_u32(self, address: int, entry: Entry, table: bool = False) -> int: + return int.from_bytes(self.memory.read(address, 4, entry, table), "little") + + def _read_s32(self, address: int, entry: Entry, table: bool = False) -> int: + return self.signed(self._read_u32(address, entry, table)) + + def _write_s32(self, address: int, value: int, entry: Entry) -> None: + self.memory.write(address, self._u32(value).to_bytes(4, "little"), entry) + + def _resolve_ref(self, word: int, entry: Entry, size: int, *, state: bool = False) -> tuple[int, bool]: + table = bool(word & 0x80000000) + if word & (0x7FFF0000 if table else 0x7FFE0000) or word & 3: + raise PrimitiveFault(11, 11, 5) + if state and table: + raise PrimitiveFault(11, 11, 5) + offset = word & (0xFFFF if table else 0x1FFFF) + address = (entry.table_offset if table else entry.scratch_base) + offset + self.memory._check_range(address, size, entry, table) + return address, table + + def _dot(self, left: list[int], right: list[int]) -> int: + total = sum(a * b for a, b in zip(left, right, strict=True)) + if total < -(1 << 71) or total > (1 << 71) - 1: + raise PrimitiveFault(22, 6) + return total + + def execute(self, instruction: Instruction, registers: list[int], entry: Entry) -> PrimitiveResult: + instruction_class = instruction.instruction_class + if instruction_class == InstructionClass.BITSTREAM: + return self._execute_bitstream(instruction, registers) + if instruction_class == InstructionClass.ENTROPY: + return self._execute_entropy(instruction, registers, entry) + if instruction_class == InstructionClass.LOCAL: + return self._execute_local(instruction, registers, entry) + if instruction_class == InstructionClass.KERNEL: + return self._execute_kernel(instruction, registers, entry) + raise ValueError("instruction is not an APU-P4 primitive") + + def _execute_bitstream(self, instruction: Instruction, registers: list[int]) -> PrimitiveResult: + opcode = BitstreamOpcode(instruction.opcode) + width = instruction.immediate & 0x3F + writes: dict[int, int] = {} + cycles = 1 + if opcode == BitstreamOpcode.REFILL: + self._refill(width) + writes[instruction.dst] = min(len(self.bits), 64) + cycles = 3 + elif opcode == BitstreamOpcode.PEEK: + writes[instruction.dst] = self._peek_bits(width, 4) + elif opcode == BitstreamOpcode.GET: + writes[instruction.dst] = self._get_bits(width, 4) + elif opcode == BitstreamOpcode.SKIP: + self._get_bits(width, 4) + elif opcode == BitstreamOpcode.ALIGN: + skip = (-self.bit_position) & 7 + if skip: + self._get_bits(skip, 4) + elif opcode == BitstreamOpcode.FRAME_SYNC: + if self.bit_position: + self._get_bits((-self.bit_position) & 7, 4) + pattern = registers[instruction.src0] & 0xFFFF + mask = registers[instruction.src1] & 0xFFFF + width = instruction.aux & 0x1F + skipped = 0 + while skipped < instruction.immediate: + if self._peek_bits(width, 4) & mask == pattern & mask: + writes[instruction.dst] = skipped + cycles = 2 + 2 * skipped + break + if skipped + 1 == instruction.immediate: + raise PrimitiveFault(4, 4) + self._get_bits(8, 4) + skipped += 1 + else: + raise PrimitiveFault(4, 4) + elif opcode == BitstreamOpcode.CRC8: + crc = registers[instruction.src0] & 0xFF + byte = registers[instruction.src1] & 0xFF + for bit_index in range(8): + feedback = ((crc >> 7) ^ (byte >> (7 - bit_index))) & 1 + crc = (crc << 1) & 0xFF + if feedback: + crc ^= 0x07 + writes[instruction.dst] = crc + else: + crc = registers[instruction.src0] & 0xFFFF + byte = registers[instruction.src1] & 0xFF + for bit_index in range(8): + feedback = ((crc >> 15) ^ (byte >> (7 - bit_index))) & 1 + crc = (crc << 1) & 0xFFFF + if feedback: + crc ^= 0x8005 + writes[instruction.dst] = crc + return PrimitiveResult(writes, cycles) + + def _execute_entropy( + self, instruction: Instruction, registers: list[int], entry: Entry + ) -> PrimitiveResult: + opcode = EntropyOpcode(instruction.opcode) + writes: dict[int, int] = {} + if opcode <= EntropyOpcode.HUFF_QUAD: + count = registers[instruction.src1] + if not 1 <= count <= 4096: + raise PrimitiveFault(7, 5) + start = entry.table_offset + registers[instruction.src0] + entries = [self._read_u32(start + index * 4, entry, True) for index in range(count)] + code = 0 + previous_length = 0 + match = None + for word in entries: + length = (word >> 16) & 0x1F + if not previous_length <= length <= instruction.aux or word >> 21: + raise PrimitiveFault(7, 5) + code <<= length - previous_length + if self._peek_bits(length, 5) == code: + match = (word & 0xFFFF, length) + break + code += 1 + previous_length = length + if match is None: + raise PrimitiveFault(7, 5) + symbol, length = match + self._get_bits(length, 5) + if opcode == EntropyOpcode.HUFF_SYMBOL: + writes[instruction.dst] = symbol + elif opcode == EntropyOpcode.HUFF_PAIR: + writes[instruction.dst] = (symbol >> 4) & 0xF + writes[instruction.dst + 1] = symbol & 0xF + else: + for index in range(4): + writes[instruction.dst + index] = (symbol >> (3 - index)) & 1 + return PrimitiveResult(writes, 2 + 24 + count) + if opcode == EntropyOpcode.UNARY: + polarity = instruction.aux & 1 + run = 0 + while run <= instruction.immediate: + bit = self._get_bits(1, 5) + if bit != polarity: + writes[instruction.dst] = run + return PrimitiveResult(writes, 2 + run) + run += 1 + raise PrimitiveFault(7, 5) + if opcode in (EntropyOpcode.RICE4, EntropyOpcode.RICE5): + width = 4 if opcode == EntropyOpcode.RICE4 else 5 + escape = 15 if width == 4 else 31 + parameter = registers[instruction.src0] & ((1 << width) - 1) + if parameter == escape: + raw_width = registers[instruction.src1] & 0x3F + if not 1 <= raw_width <= 32: + raise PrimitiveFault(7, 5) + value = self._get_bits(raw_width, 5) + writes[instruction.dst] = self._u32(self.signed(value, raw_width)) + return PrimitiveResult(writes, 4 + raw_width) + quotient = 0 + while self._get_bits(1, 5) == 0: + quotient += 1 + if quotient > 65535: + raise PrimitiveFault(7, 5) + remainder = self._get_bits(parameter, 5) if parameter else 0 + unsigned = (quotient << parameter) | remainder + writes[instruction.dst] = self._u32((unsigned >> 1) ^ -(unsigned & 1)) + return PrimitiveResult(writes, 4 + quotient + parameter) + magnitude = registers[instruction.src0] + if magnitude == 0: + writes[instruction.dst] = 0 + return PrimitiveResult(writes, 3) + linbits = registers[instruction.src1] & 0x1F + extra = self._get_bits(linbits, 5) if linbits else 0 + value = magnitude + extra + if self._get_bits(1, 5): + value = -value + writes[instruction.dst] = self._u32(value) + return PrimitiveResult(writes, 3 + linbits) + + def _execute_local( + self, instruction: Instruction, registers: list[int], entry: Entry + ) -> PrimitiveResult: + opcode = LocalOpcode(instruction.opcode) + writes: dict[int, int] = {} + if opcode in (LocalOpcode.LD32, LocalOpcode.ST32): + offset = self.signed(instruction.immediate, 16) + address = entry.scratch_base + registers[instruction.src0] + offset + if address & 3: + raise PrimitiveFault(11, 11, 5) + if opcode == LocalOpcode.LD32: + writes[instruction.dst] = self._read_u32(address, entry) + return PrimitiveResult(writes, 2) + self._write_s32(address, registers[instruction.src1], entry) + return PrimitiveResult(cycles=1) + if opcode in (LocalOpcode.TABLE8, LocalOpcode.TABLE16, LocalOpcode.TABLE32): + size = 1 << (instruction.opcode - LocalOpcode.TABLE8) + address = entry.table_offset + registers[instruction.src0] + registers[instruction.src1] * size + if address & (size - 1): + raise PrimitiveFault(11, 11, 5) + writes[instruction.dst] = int.from_bytes(self.memory.read(address, size, entry, True), "little") + return PrimitiveResult(writes, 2) + if opcode == LocalOpcode.FIFO_POP: + word = self.input_fifo.pop() + writes[instruction.dst] = word & 0xFFFFFFFF + writes[instruction.dst + 1] = ((word >> 32) & 7) | (((word >> 40) & 1) << 8) + return PrimitiveResult(writes) + metadata = registers[instruction.src1] + if metadata & ~0x107 or not 1 <= (metadata & 7) <= 4: + raise PrimitiveFault(22, 7) + self.output_fifo.push( + (registers[instruction.src0] & 0xFFFFFFFF) + | ((metadata & 7) << 32) + | (((metadata >> 8) & 1) << 40) + ) + return PrimitiveResult() + + def _kernel_arrays( + self, instruction: Instruction, registers: list[int], entry: Entry + ) -> tuple[int, int, int, int]: + count = instruction.immediate & 0xFFFF + input_address = entry.scratch_base + registers[instruction.src0] + parameter_address = entry.scratch_base + registers[instruction.src1] + output_address = entry.scratch_base + registers[instruction.dst] + output_alignment = ( + 2 + if instruction.opcode == KernelOpcode.PCM_PACK and (instruction.aux & 3) == 0 + else 4 + ) + if ( + entry.scratch_base & 3 + or input_address & 3 + or parameter_address & 3 + or output_address & (output_alignment - 1) + ): + raise PrimitiveFault(11, 11, 5) + self.memory._check_range(parameter_address, 4, entry) + return count, input_address, parameter_address, output_address + + def _execute_kernel( + self, instruction: Instruction, registers: list[int], entry: Entry + ) -> PrimitiveResult: + opcode = KernelOpcode(instruction.opcode) + count, input_address, parameter_address, output_address = self._kernel_arrays( + instruction, registers, entry + ) + produced = 0 + cycles = 8 + if opcode == KernelOpcode.REQUANT: + reference = self._read_u32(parameter_address, entry) + scale_address, scale_table = self._resolve_ref(reference, entry, count * 4) + self.memory._check_range(input_address, count * 4, entry) + width = (16, 24, 32)[instruction.aux >> 6] + shift = 30 + (instruction.aux & 0x1F) + for index in range(count): + sample = self._read_s32(input_address + index * 4, entry) + scale = self._read_s32(scale_address + index * 4, entry, scale_table) + product = sample * scale + value = self.rne(product, shift) if instruction.aux & 0x20 else product >> shift + self._write_s32(output_address + index * 4, self.sat(value, width), entry) + produced, cycles = count, 8 + 4 * count + elif opcode in (KernelOpcode.STEREO, KernelOpcode.DECORRELATE): + reference = self._read_u32(parameter_address, entry) + second_address, _ = self._resolve_ref(reference, entry, count * 4, state=True) + self.memory._check_range(input_address, count * 4, entry) + for index in range(count): + left = self._read_s32(input_address + index * 4, entry) + right = self._read_s32(second_address + index * 4, entry) + mode = instruction.aux & 3 + if mode == 0: + pair = left, right + elif mode == 1: + pair = left, left - right + elif mode == 2: + pair = left + right, right + else: + middle = left * 2 + (right & 1) + pair = (middle + right) >> 1, (middle - right) >> 1 + if opcode == KernelOpcode.DECORRELATE and any( + value < -(1 << 31) or value > (1 << 31) - 1 for value in pair + ): + raise PrimitiveFault(8, 6) + for channel, value in enumerate(pair): + self._write_s32(output_address + (channel * count + index) * 4, self.sat(value, 32), entry) + produced, cycles = 2 * count, 8 + 4 * count + elif opcode in (KernelOpcode.IMDCT6, KernelOpcode.IMDCT18): + order = 6 if opcode == KernelOpcode.IMDCT6 else 18 + reference = self._read_u32(parameter_address, entry) + matrix_address, matrix_table = self._resolve_ref(reference, entry, 2 * order * order * 4) + self.memory._check_range(input_address, count * order * 4, entry) + for block in range(count): + values = [self._read_s32(input_address + (block * order + item) * 4, entry) for item in range(order)] + for row in range(2 * order): + coefficients = [ + self._read_s32(matrix_address + (row * order + item) * 4, entry, matrix_table) + for item in range(order) + ] + value = self.sat(self.rne(self._dot(values, coefficients), 30), 32) + self._write_s32(output_address + (block * 2 * order + row) * 4, value, entry) + produced = count * 2 * order + cycles = 8 + (94 if order == 6 else 706) * count + elif opcode == KernelOpcode.FIXED: + order = instruction.aux & 7 + reference = self._read_u32(parameter_address, entry) + history_address, _ = self._resolve_ref(reference, entry, max(order, 1) * 4, state=True) + history = [self._read_s32(history_address + index * 4, entry) for index in range(order)] + self.memory._check_range(input_address, count * 4, entry) + for index in range(count): + if order == 0: + predictor = 0 + elif order == 1: + predictor = history[0] + elif order == 2: + predictor = 2 * history[0] - history[1] + elif order == 3: + predictor = 3 * history[0] - 3 * history[1] + history[2] + else: + predictor = 4 * history[0] - 6 * history[1] + 4 * history[2] - history[3] + value = predictor + self._read_s32(input_address + index * 4, entry) + if value < -(1 << 31) or value > (1 << 31) - 1: + raise PrimitiveFault(8, 6) + self._write_s32(output_address + index * 4, value, entry) + if order: + history = [value, *history[:-1]] + for index, value in enumerate(history): + self._write_s32(history_address + index * 4, value, entry) + produced, cycles = count, 8 + (order + 4) * count + elif opcode == KernelOpcode.LPC: + order = instruction.aux & 0x3F + coefficient_ref = self._read_u32(parameter_address, entry) + history_ref = self._read_u32(parameter_address + 4, entry) + shift = self.signed(self._read_u32(parameter_address + 8, entry), 32) + if not -31 <= shift <= 31: + raise PrimitiveFault(11, 11, 5) + coefficient_address, coefficient_table = self._resolve_ref(coefficient_ref, entry, order * 4) + history_address, _ = self._resolve_ref(history_ref, entry, order * 4, state=True) + coefficients = [ + self._read_s32(coefficient_address + index * 4, entry, coefficient_table) + for index in range(order) + ] + history = [self._read_s32(history_address + index * 4, entry) for index in range(order)] + for index in range(count): + predictor = self._dot(coefficients, history) + predictor = predictor >> shift if shift >= 0 else predictor << -shift + value = predictor + self._read_s32(input_address + index * 4, entry) + if value < -(1 << 31) or value > (1 << 31) - 1: + raise PrimitiveFault(8, 6) + self._write_s32(output_address + index * 4, value, entry) + history = [value, *history[:-1]] + for index, value in enumerate(history): + self._write_s32(history_address + index * 4, value, entry) + produced, cycles = count, 8 + (2 * order + 5) * count + elif opcode == KernelOpcode.PCM_PACK: + gain = self._read_s32(parameter_address, entry) + channels = self._read_u32(parameter_address + 4, entry) + if channels not in (1, 2) or (instruction.aux & 4 and channels != 1): + raise PrimitiveFault(8, 7) + samples = count * channels + output = bytearray() + for index in range(samples): + sample = self._read_s32(input_address + index * 4, entry) + value = self.rne(sample * gain, 30) + packed = self.sat(value, 16 if (instruction.aux & 3) == 0 else 24) + encoded = self._u32(packed).to_bytes(4, "little") + encoded = encoded[:2] if (instruction.aux & 3) == 0 else encoded + output.extend(encoded) + if instruction.aux & 4: + output.extend(encoded) + self.memory.write(output_address, output, entry) + produced, cycles = len(output), 8 + 5 * (count * (2 if instruction.aux & 4 else channels)) + elif opcode == KernelOpcode.RESAMPLE: + produced, cycles = self._resample(instruction, count, input_address, parameter_address, output_address, entry) + else: + produced, cycles = self._dct32(count, input_address, parameter_address, output_address, entry) + self.kernel_done = True + return PrimitiveResult({instruction.dst: produced}, cycles, True) + + def _dct32(self, count: int, input_address: int, parameter_address: int, + output_address: int, entry: Entry) -> tuple[int, int]: + refs = [self._read_u32(parameter_address + index * 4, entry) for index in range(3)] + phase = self._read_u32(parameter_address + 12, entry) + if phase >> 4: + raise PrimitiveFault(11, 11, 5) + matrix_address, matrix_table = self._resolve_ref(refs[0], entry, 32 * 32 * 4) + window_address, window_table = self._resolve_ref(refs[1], entry, 16 * 32 * 4) + history_address, _ = self._resolve_ref(refs[2], entry, 16 * 32 * 4, state=True) + for block in range(count): + values = [self._read_s32(input_address + (block * 32 + item) * 4, entry) for item in range(32)] + transformed = [] + for row in range(32): + coefficients = [ + self._read_s32(matrix_address + (row * 32 + item) * 4, entry, matrix_table) + for item in range(32) + ] + transformed.append(self.sat(self.rne(self._dot(values, coefficients), 30), 32)) + for item, value in enumerate(transformed): + self._write_s32(history_address + (phase * 32 + item) * 4, value, entry) + for item in range(32): + history = [ + self._read_s32(history_address + (((phase - tap) & 15) * 32 + item) * 4, entry) + for tap in range(16) + ] + window = [ + self._read_s32(window_address + (tap * 32 + item) * 4, entry, window_table) + for tap in range(16) + ] + self._write_s32( + output_address + (block * 32 + item) * 4, + self.sat(self.rne(self._dot(history, window), 30), 32), + entry, + ) + phase = (phase + 1) & 15 + self._write_s32(parameter_address + 12, phase, entry) + return 32 * count, 8 + 2304 * count + + def _resample(self, instruction: Instruction, count: int, input_address: int, + parameter_address: int, output_address: int, entry: Entry) -> tuple[int, int]: + ratios = ( + (1, 1), (1, 2), (80, 147), (160, 147), (3, 2), (2, 1), (3, 1), (4, 1), + (6, 1), (8, 1), (12, 1), (320, 147), (640, 147), (1280, 147), (2, 3), (4, 3), + ) + profile = instruction.aux & 15 + if self.resample_profile is None: + self.resample_profile = profile + elif profile != self.resample_profile: + raise PrimitiveFault(8, 7) + refs = [self._read_u32(parameter_address + index * 4, entry) for index in range(3)] + next_output = self._read_u32(parameter_address + 12, entry) | ( + self._read_u32(parameter_address + 16, entry) << 32 + ) + input_base = self._read_u32(parameter_address + 20, entry) | ( + self._read_u32(parameter_address + 24, entry) << 32 + ) + channels = self._read_u32(parameter_address + 28, entry) + if channels not in (1, 2): + raise PrimitiveFault(8, 7) + if profile == 0: + payload = self.memory.read(input_address, count * channels * 4, entry) + self.memory.write(output_address, payload, entry) + next_output += count + input_base += count + produced = count + cycles = 8 + 2 * count * channels + else: + bank_index = 2 if profile in (1, 2) else (1 if profile == 14 else 0) + coefficient_address, coefficient_table = self._resolve_ref(refs[bank_index], entry, 32 * 16 * 4) + numerator, denominator = ratios[profile] + produced = 0 + while next_output // numerator < input_base + count: + source_index, remainder = divmod(next_output, numerator) + quotient, phase_remainder = divmod(32 * remainder, numerator) + phase = quotient + doubled_remainder = 2 * phase_remainder + if doubled_remainder > numerator or ( + doubled_remainder == numerator and phase & 1 + ): + phase += 1 + if phase == 32: + phase = 0 + source_index += 1 + for channel in range(channels): + samples = [ + self._read_s32( + input_address + ((source_index + tap - 7 - input_base) * channels + channel) * 4, + entry, + ) + for tap in range(16) + ] + coefficients = [ + self._read_s32(coefficient_address + (phase * 16 + tap) * 4, entry, coefficient_table) + for tap in range(16) + ] + self._write_s32( + output_address + (produced * channels + channel) * 4, + self.sat(self.rne(self._dot(samples, coefficients), 30), 32), + entry, + ) + produced += 1 + next_output += denominator + input_base += count + cycles = 8 + 2 * count * channels + 36 * produced * channels + for index, value in enumerate((next_output, input_base)): + self._write_s32(parameter_address + 12 + index * 8, value & 0xFFFFFFFF, entry) + self._write_s32(parameter_address + 16 + index * 8, value >> 32, entry) + return produced, cycles diff --git a/tests/README.md b/tests/README.md index b0ea44ac..63d01284 100644 --- a/tests/README.md +++ b/tests/README.md @@ -19,7 +19,10 @@ tooling. - `test_apu.py` checks the fail-closed APU APB4/IRQ shell plus the P2 private DMA, ring scheduler, stream router, Gateway A, and verification-only backend; `test_apu_register_parity.py` keeps its handwritten RTL/C ABI and matrix - coverage synchronized. + coverage synchronized. `test_apu_primitives.py` compares the P4 assembler, + BAM, loader, sequencer, local SRAM, FIFOs, and production primitive engines + with Icarus and Verilator while keeping its injectors out of product + filelists. - `test_user_ip_register_parity.py` keeps the integrated slot 1 timer and slot 2 GPIO register offsets synchronized with their application-owned C definitions and checks their extension-manifest slot assignments. diff --git a/tests/rtl/apu_p3_microcode_tb.sv b/tests/rtl/apu_p3_microcode_tb.sv index 272522f8..778c1d96 100644 --- a/tests/rtl/apu_p3_microcode_tb.sv +++ b/tests/rtl/apu_p3_microcode_tb.sv @@ -221,6 +221,7 @@ module apu_p3_microcode_tb; .fault_addr_o (seq_fault_addr), .fault_detail_o (seq_fault_detail), .perf_retired_o (seq_perf_retired), + .launch_epoch_o (), .idle_o (seq_idle) ); diff --git a/tests/rtl/apu_p4_loader_tb.sv b/tests/rtl/apu_p4_loader_tb.sv new file mode 100644 index 00000000..8c620699 --- /dev/null +++ b/tests/rtl/apu_p4_loader_tb.sv @@ -0,0 +1,306 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +`include "apu_define.svh" + +module apu_p4_loader_tb; + logic clk_i = 1'b0; + logic rst_n_i = 1'b0; + logic start_i, abort_i, resource_reset_i, soft_reset_i, counter_clear_i; + logic [31:0] image_addr_i, image_size_i, expected_crc_i; + logic dma_request_valid, dma_request_ready, dma_ready, dma_done; + logic [31:0] dma_request_addr, dma_request_bytes, dma_data; + logic [3:0] dma_keep; + logic dma_last, dma_valid; + logic store_active, store_read, store_write, store_valid; + logic [10:0] store_addr, fetch_addr; + logic [63:0] store_write_data, store_read_data, fetch_data; + logic fetch, fetch_valid; + logic local_write; + logic [16:0] local_addr; + logic [31:0] local_data, local_read_data; + logic [3:0] local_strb; + logic local_read_req, local_read_valid, local_read_error; + logic [15:0] table_bytes; + logic [ 7:0] status; + logic [31:0] abi, actual_crc, load_count; + logic [63:0] build_id; + logic lock, load_done, abort_done, fault_valid, loader_idle; + logic [5:0] fault_code; + logic [3:0] fault_stage; + logic [1:0] fault_resp; + logic [31:0] fault_addr, fault_detail; + logic [2:0][10:0] entry_pc, entry_first, entry_last; + logic [2:0][15:0] entry_max_loop; + logic [2:0][23:0] entry_max_retired; + logic [2:0][16:0] entry_scratch_base, entry_scratch_bytes; + logic [2:0][31:0] entry_primitive_mask; + logic [2:0][15:0] entry_table_offset, entry_table_bytes; + logic [31:0] image [0:4095]; + logic transfer_active_q; + logic [11:0] transfer_word_q, transfer_beat_q; + logic [31:0] transfer_bytes_q; + logic [ 3:0] request_count_q; + logic [11:0] store_write_count_q, table_write_count_q; + string image_path, invalid_image_path; + + always #5 clk_i = ~clk_i; + + assign dma_request_ready = 1'b1; + assign dma_valid = transfer_active_q; + assign dma_data = image[transfer_word_q]; + assign dma_keep = 4'hf; + assign dma_last = transfer_beat_q == ((transfer_bytes_q >> 2) - 1'b1); + + always_ff @(posedge clk_i or negedge rst_n_i) begin + if (!rst_n_i) begin + transfer_active_q <= 1'b0; + transfer_word_q <= 12'd0; + transfer_beat_q <= 12'd0; + transfer_bytes_q <= 32'd0; + request_count_q <= 4'd0; + store_write_count_q <= 12'd0; + table_write_count_q <= 12'd0; + dma_done <= 1'b0; + end else begin + dma_done <= 1'b0; + if (dma_request_valid && dma_request_ready) begin + transfer_active_q <= 1'b1; + transfer_word_q <= 12'((dma_request_addr - image_addr_i) >> 2); + transfer_beat_q <= 12'd0; + transfer_bytes_q <= dma_request_bytes; + request_count_q <= request_count_q + 1'b1; + end + if (dma_valid && dma_ready) begin + if (dma_last) begin + transfer_active_q <= 1'b0; + dma_done <= 1'b1; + end else begin + transfer_word_q <= transfer_word_q + 1'b1; + transfer_beat_q <= transfer_beat_q + 1'b1; + end + end + if (store_write) begin + if (request_count_q < 4'd3) $fatal(1, "P4 store write preceded range admission"); + store_write_count_q <= store_write_count_q + 1'b1; + end + if (local_write) begin + if (request_count_q < 4'd3) $fatal(1, "P4 table write preceded range admission"); + if (local_addr >= table_bytes) $fatal(1, "P4 loader wrote outside table payload"); + table_write_count_q <= table_write_count_q + 1'b1; + end + end + end + + apu_microcode_loader #( + .EnableP4(1'b1) + ) u_loader ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .start_i (start_i), + .abort_i (abort_i), + .resource_reset_i (resource_reset_i), + .soft_reset_i (soft_reset_i), + .counter_clear_i (counter_clear_i), + .image_addr_i (image_addr_i), + .image_size_i (image_size_i), + .expected_crc_i (expected_crc_i), + .dma_request_valid_o (dma_request_valid), + .dma_request_ready_i (dma_request_ready), + .dma_request_addr_o (dma_request_addr), + .dma_request_bytes_o (dma_request_bytes), + .dma_data_i (dma_data), + .dma_keep_i (dma_keep), + .dma_last_i (dma_last), + .dma_valid_i (dma_valid), + .dma_ready_o (dma_ready), + .dma_done_i (dma_done), + .dma_err_i (1'b0), + .dma_err_code_i (6'd0), + .dma_err_stage_i (4'd0), + .dma_err_resp_i (2'd0), + .dma_err_addr_i (32'd0), + .store_active_o (store_active), + .store_read_o (store_read), + .store_write_o (store_write), + .store_addr_o (store_addr), + .store_data_o (store_write_data), + .store_data_i (store_read_data), + .store_valid_i (store_valid), + .local_write_o (local_write), + .local_addr_o (local_addr), + .local_data_o (local_data), + .local_strb_o (local_strb), + .table_bytes_o (table_bytes), + .stat_o (status), + .abi_o (abi), + .build_id_o (build_id), + .lock_o (lock), + .actual_crc_o (actual_crc), + .load_count_o (load_count), + .entry_pc_o (entry_pc), + .entry_first_o (entry_first), + .entry_last_o (entry_last), + .entry_max_loop_o (entry_max_loop), + .entry_max_retired_o (entry_max_retired), + .entry_scratch_base_o (entry_scratch_base), + .entry_scratch_bytes_o (entry_scratch_bytes), + .entry_primitive_mask_o(entry_primitive_mask), + .entry_table_offset_o (entry_table_offset), + .entry_table_bytes_o (entry_table_bytes), + .load_done_o (load_done), + .abort_done_o (abort_done), + .fault_valid_o (fault_valid), + .fault_code_o (fault_code), + .fault_stage_o (fault_stage), + .fault_resp_o (fault_resp), + .fault_addr_o (fault_addr), + .fault_detail_o (fault_detail), + .idle_o (loader_idle) + ); + + apu_control_store u_control_store ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .loader_active_i(store_active), + .loader_read_i (store_read), + .loader_write_i (store_write), + .loader_addr_i (store_addr), + .loader_data_i (store_write_data), + .loader_data_o (store_read_data), + .loader_valid_o (store_valid), + .image_valid_i (status[`APB4_APU__MC_STATUS_VALID]), + .fetch_i (fetch), + .fetch_addr_i (fetch_addr), + .fetch_data_o (fetch_data), + .fetch_valid_o (fetch_valid) + ); + + apu_local_sram u_local_sram ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .image_valid_i (status[`APB4_APU__MC_STATUS_VALID]), + .table_bytes_i (table_bytes), + .epoch_clear_i (1'b0), + .loader_active_i (!loader_idle), + .loader_req_i (local_write), + .loader_addr_i (local_addr), + .loader_data_i (local_data), + .loader_strb_i (local_strb), + .loader_ready_o (), + .codec_req_i (local_read_req), + .codec_write_i (1'b0), + .codec_addr_i (17'd0), + .codec_data_i (32'd0), + .codec_strb_i (4'd0), + .codec_ready_o (), + .codec_data_o (local_read_data), + .codec_valid_o (local_read_valid), + .codec_access_err_o(local_read_error) + ); + + task automatic hard_reset; + begin + rst_n_i = 1'b0; + repeat (3) @(posedge clk_i); + rst_n_i = 1'b1; + repeat (2) @(posedge clk_i); + end + endtask + + initial begin + if (!$value$plusargs("IMAGE=%s", image_path)) $fatal(1, "IMAGE plusarg missing"); + if (!$value$plusargs("INVALID_IMAGE=%s", invalid_image_path)) begin + $fatal(1, "INVALID_IMAGE plusarg missing"); + end + $readmemh(image_path, image); + start_i = 1'b0; + abort_i = 1'b0; + resource_reset_i = 1'b0; + soft_reset_i = 1'b0; + counter_clear_i = 1'b0; + image_addr_i = 32'h2000_0000; + image_size_i = image[2]; + expected_crc_i = image[11]; + fetch = 1'b0; + fetch_addr = 11'd0; + local_read_req = 1'b0; + hard_reset(); + + @(negedge clk_i); + start_i = 1'b1; + @(negedge clk_i); + start_i = 1'b0; + for (int cycle = 0; cycle < 30000; cycle++) begin + @(posedge clk_i); + if (loader_idle && status[`APB4_APU__MC_STATUS_VALID]) break; + if (fault_valid) $fatal(1, "P4 loader fault code=%0d detail=%08x", fault_code, fault_detail); + if (cycle == 29999) $fatal(1, "P4 loader timed out"); + end + if (!lock || !load_done || (abi != 32'h0001_0000) || (table_bytes != 16'd4) || + (store_write_count_q != 12'd2) || (table_write_count_q != 12'd1)) begin + $fatal(1, "P4 loader publication mismatch"); + end + if ((entry_scratch_base[0] != 17'h01000) || (entry_scratch_bytes[0] != 17'h01000) || + (entry_primitive_mask[0] != 32'h0000_0040) || + (entry_table_bytes[0] != 16'd4)) begin + $fatal(1, "P4 descriptor publication mismatch"); + end + + @(negedge clk_i); + fetch = 1'b1; + @(negedge clk_i); + fetch = 1'b0; + if (!fetch_valid || (fetch_data != 64'h0800_0001_0000_0000)) begin + $fatal(1, "P4 control-store publication mismatch"); + end + @(negedge clk_i); + local_read_req = 1'b1; + @(negedge clk_i); + local_read_req = 1'b0; + if (!local_read_valid || local_read_error || (local_read_data != 32'h0001_0001)) begin + $fatal(1, "P4 table publication mismatch"); + end + + @(negedge clk_i); + soft_reset_i = 1'b1; + @(negedge clk_i); + soft_reset_i = 1'b0; + @(posedge clk_i); + if (!status[`APB4_APU__MC_STATUS_VALID] || !lock || (table_bytes != 16'd4)) begin + $fatal(1, "P4 soft reset did not retain the locked image"); + end + + hard_reset(); + $readmemh(invalid_image_path, image); + image_size_i = image[2]; + expected_crc_i = image[11]; + @(negedge clk_i); + start_i = 1'b1; + @(negedge clk_i); + start_i = 1'b0; + for (int cycle = 0; cycle < 1000; cycle++) begin + @(posedge clk_i); + if (fault_valid) begin + if ((fault_code != `APB4_APU__ERROR_CODE_MICROCODE) || + (fault_stage != `APB4_APU__ERROR_STAGE_LOADER) || + (fault_addr != image_addr_i + 32'd92) || (fault_detail != 32'h0000_0008)) begin + $fatal(1, "P4 zero-byte table diagnostic mismatch"); + end + break; + end + if (cycle == 999) $fatal(1, "P4 invalid-table load timed out"); + end + if (status[`APB4_APU__MC_STATUS_VALID] || lock || !status[5] || + (store_write_count_q != 12'd0) || (table_write_count_q != 12'd0)) begin + $fatal(1, "P4 invalid descriptor mutated unpublished stores"); + end + $display("APU_P4_LOADER_PASS"); + $finish; + end + + logic s_unused; + assign s_unused = abort_done ^ ^actual_crc ^ ^load_count ^ ^build_id ^ ^entry_pc ^ + ^entry_first ^ ^entry_last ^ ^entry_max_loop ^ ^entry_max_retired ^ + ^entry_table_offset ^ fault_stage[0] ^ fault_resp[0] ^ fault_addr[0]; +endmodule diff --git a/tests/rtl/apu_p4_primitives_tb.sv b/tests/rtl/apu_p4_primitives_tb.sv new file mode 100644 index 00000000..033e84cc --- /dev/null +++ b/tests/rtl/apu_p4_primitives_tb.sv @@ -0,0 +1,766 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +`include "apu_define.svh" + +module apu_p4_primitives_tb; + logic clk_i = 1'b0; + logic rst_n_i = 1'b0; + logic flush_i, epoch_clear_i, image_valid_i; + logic req_valid, req_ready; + logic [63:0] instruction; + logic [31:0] source0, source1, destination; + logic result_valid, result_kernel, result_error, kernel_done, dispatcher_busy; + logic [ 3:0] result_dst; + logic [ 3:0][31:0] result_data; + logic [ 2:0] result_words; + logic [ 5:0] error_code; + logic [ 3:0] error_stage; + logic [ 7:0] error_reason; + logic [31:0] result_cycles; + logic input_exhausted, input_ready, output_ready; + logic [6:0] input_count, output_count; + logic input_valid, input_accept, output_valid, output_accept; + logic [40:0] input_data, output_data; + logic dispatcher_memory_req, dispatcher_memory_write; + logic [16:0] dispatcher_memory_addr; + logic [31:0] dispatcher_memory_data; + logic [ 3:0] dispatcher_memory_strb; + logic memory_valid, memory_error, memory_ready; + logic [31:0] memory_read_data; + logic loader_active, loader_req; + logic [16:0] loader_addr; + logic [31:0] loader_data; + logic [ 3:0] loader_strb; + logic inject_active, inject_req, inject_write; + logic [16:0] inject_addr; + logic [31:0] inject_data; + logic [ 3:0] inject_strb; + logic codec_req, codec_write; + logic [16:0] codec_addr; + logic [31:0] codec_data; + logic [ 3:0] codec_strb; + logic s_latency_active_q; + logic [31:0] s_latency_cycles_q; + logic [31:0] s_dispatcher_request_count_q; + logic [31:0] s_dispatcher_write_count_q; + logic [31:0] requant_vectors [0:255]; + integer requant_vector_count; + string requant_vector_path; + + always #5 clk_i = ~clk_i; + + always_ff @(posedge clk_i or negedge rst_n_i) begin + if (!rst_n_i) begin + s_latency_active_q <= 1'b0; + s_latency_cycles_q <= 32'd0; + end else if (flush_i) begin + s_latency_active_q <= 1'b0; + s_latency_cycles_q <= 32'd0; + end else if (result_valid) begin + if (s_latency_active_q && (s_latency_cycles_q > result_cycles)) begin + $fatal(1, "APU-P4 request latency exceeded bound actual=%0d bound=%0d instruction=%016x", + s_latency_cycles_q, result_cycles, instruction); + end + s_latency_active_q <= 1'b0; + s_latency_cycles_q <= 32'd0; + end else if (req_valid && req_ready) begin + s_latency_active_q <= 1'b1; + s_latency_cycles_q <= 32'd0; + end else if (s_latency_active_q) begin + s_latency_cycles_q <= s_latency_cycles_q + 1'b1; + end + end + + always_ff @(posedge clk_i or negedge rst_n_i) begin + if (!rst_n_i) begin + s_dispatcher_request_count_q <= 32'd0; + s_dispatcher_write_count_q <= 32'd0; + end else begin + if (dispatcher_memory_req && memory_ready) begin + s_dispatcher_request_count_q <= s_dispatcher_request_count_q + 1'b1; + end + if (dispatcher_memory_req && dispatcher_memory_write && memory_ready) begin + s_dispatcher_write_count_q <= s_dispatcher_write_count_q + 1'b1; + end + end + end + + assign codec_req = inject_active ? inject_req : dispatcher_memory_req; + assign codec_write = inject_active ? inject_write : dispatcher_memory_write; + assign codec_addr = inject_active ? inject_addr : dispatcher_memory_addr; + assign codec_data = inject_active ? inject_data : dispatcher_memory_data; + assign codec_strb = inject_active ? inject_strb : dispatcher_memory_strb; + + apu_local_sram u_local_sram ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .image_valid_i (image_valid_i), + .table_bytes_i (16'h1800), + .epoch_clear_i (epoch_clear_i), + .loader_active_i (loader_active), + .loader_req_i (loader_req), + .loader_addr_i (loader_addr), + .loader_data_i (loader_data), + .loader_strb_i (loader_strb), + .loader_ready_o (), + .codec_req_i (codec_req), + .codec_write_i (codec_write), + .codec_addr_i (codec_addr), + .codec_data_i (codec_data), + .codec_strb_i (codec_strb), + .codec_ready_o (memory_ready), + .codec_data_o (memory_read_data), + .codec_valid_o (memory_valid), + .codec_access_err_o(memory_error) + ); + + apu_primitive_dispatcher u_dut ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .flush_i (flush_i), + .req_valid_i (req_valid), + .req_ready_o (req_ready), + .instruction_i (instruction), + .source0_i (source0), + .source1_i (source1), + .destination_i (destination), + .scratch_base_i (17'h02000), + .scratch_bytes_i (17'h04000), + .table_offset_i (16'h0000), + .table_bytes_i (16'h1800), + .result_valid_o (result_valid), + .result_dst_o (result_dst), + .result_data_o (result_data), + .result_words_o (result_words), + .result_kernel_o (result_kernel), + .error_o (result_error), + .error_code_o (error_code), + .error_stage_o (error_stage), + .error_reason_o (error_reason), + .cycles_o (result_cycles), + .kernel_done_o (kernel_done), + .busy_o (dispatcher_busy), + .input_exhausted_o(input_exhausted), + .input_ready_o (input_ready), + .output_ready_o (output_ready), + .input_count_o (input_count), + .output_count_o (output_count), + .kernel_busy_o (), + .memory_req_o (dispatcher_memory_req), + .memory_write_o (dispatcher_memory_write), + .memory_addr_o (dispatcher_memory_addr), + .memory_data_o (dispatcher_memory_data), + .memory_strb_o (dispatcher_memory_strb), + .memory_valid_i (memory_valid), + .memory_data_i (memory_read_data), + .memory_error_i (memory_error), + .input_valid_i (input_valid), + .input_data_i (input_data), + .input_accept_o (input_accept), + .output_valid_o (output_valid), + .output_data_o (output_data), + .output_accept_i (output_accept) + ); + + function automatic logic [63:0] encode_instruction( + input logic [3:0] instruction_class_i, input logic [3:0] opcode_i, input logic [3:0] dst_i, + input logic [3:0] src0_i, input logic [3:0] src1_i, input logic [7:0] aux_i, + input logic [31:0] immediate_i); + return {instruction_class_i, opcode_i, 4'd0, dst_i, src0_i, src1_i, aux_i, immediate_i}; + endfunction + + function automatic logic [15:0] expected_resample_frames(input logic [3:0] profile_i); + begin + unique case (profile_i) + 4'd0, 4'd1, 4'd2: expected_resample_frames = 16'd1; + 4'd3: expected_resample_frames = 16'd2; + 4'd4: expected_resample_frames = 16'd2; + 4'd5: expected_resample_frames = 16'd2; + 4'd6: expected_resample_frames = 16'd3; + 4'd7: expected_resample_frames = 16'd4; + 4'd8: expected_resample_frames = 16'd6; + 4'd9: expected_resample_frames = 16'd8; + 4'd10: expected_resample_frames = 16'd12; + 4'd11: expected_resample_frames = 16'd3; + 4'd12: expected_resample_frames = 16'd5; + 4'd13: expected_resample_frames = 16'd9; + 4'd14: expected_resample_frames = 16'd1; + default: expected_resample_frames = 16'd2; + endcase + end + endfunction + + task automatic hard_reset; + begin + rst_n_i = 1'b0; + repeat (3) @(posedge clk_i); + rst_n_i = 1'b1; + repeat (2) @(posedge clk_i); + end + endtask + + task automatic publish_table_word(input logic [16:0] address_i, input logic [31:0] data_i); + begin + @(negedge clk_i); + loader_active = 1'b1; + loader_req = 1'b1; + loader_addr = address_i; + loader_data = data_i; + loader_strb = 4'hf; + @(negedge clk_i); + loader_active = 1'b0; + loader_req = 1'b0; + end + endtask + + task automatic inject_word(input logic [16:0] address_i, input logic [31:0] data_i); + begin + @(negedge clk_i); + inject_active = 1'b1; + inject_req = 1'b1; + inject_write = 1'b1; + inject_addr = address_i; + inject_data = data_i; + inject_strb = 4'hf; + @(negedge clk_i); + inject_active = 1'b0; + inject_req = 1'b0; + inject_write = 1'b0; + end + endtask + + task automatic read_word(input logic [16:0] address_i, output logic [31:0] data_o); + begin + @(negedge clk_i); + inject_active = 1'b1; + inject_req = 1'b1; + inject_write = 1'b0; + inject_addr = address_i; + @(negedge clk_i); + inject_req = 1'b0; + for (int cycle = 0; cycle < 4; cycle++) begin + @(posedge clk_i); + if (memory_valid) begin + data_o = memory_read_data; + inject_active = 1'b0; + return; + end + end + $fatal(1, "APU-P4 injected read timed out"); + end + endtask + + task automatic push_input(input logic [40:0] data_i); + begin + @(negedge clk_i); + input_data = data_i; + input_valid = 1'b1; + do @(posedge clk_i); while (!input_accept); + @(negedge clk_i); + input_valid = 1'b0; + end + endtask + + task automatic issue(input logic [63:0] instruction_i, input logic [31:0] source0_i, + input logic [31:0] source1_i, input logic [31:0] destination_i, + output logic [31:0] result0_o, output logic [31:0] cycles_o); + begin + int wall_cycles; + wall_cycles = 0; + @(negedge clk_i); + instruction = instruction_i; + source0 = source0_i; + source1 = source1_i; + destination = destination_i; + req_valid = 1'b1; + for (int cycle = 0; cycle < 10000; cycle++) begin + @(posedge clk_i); + if (req_ready) begin + wall_cycles = 1; + @(negedge clk_i); + req_valid = 1'b0; + break; + end + end + for (int cycle = 0; cycle < 20000; cycle++) begin + @(posedge clk_i); + wall_cycles++; + if (result_valid) begin + if (result_error) begin + $fatal(1, "APU-P4 operation failed code=%0d stage=%0d reason=%0d", error_code, + error_stage, error_reason); + end + result0_o = result_data[0]; + cycles_o = result_cycles; + if (result_kernel && (u_dut.u_kernel_engine.s_cycles_q > result_cycles)) begin + $fatal(1, "APU-P4 wall latency exceeded bound actual=%0d bound=%0d opcode=%0d", + u_dut.u_kernel_engine.s_cycles_q, result_cycles, instruction_i[59:56]); + end + if (result_kernel && (wall_cycles > result_cycles)) begin + $fatal(1, "APU-P4 external latency exceeded bound actual=%0d bound=%0d opcode=%0d", + wall_cycles, result_cycles, instruction_i[59:56]); + end + return; + end + end + $fatal(1, "APU-P4 operation timed out"); + end + endtask + + task automatic issue_expect_error( + input logic [63:0] instruction_i, input logic [31:0] source0_i, input logic [31:0] source1_i, + input logic [31:0] destination_i, input logic [5:0] expected_code_i, + input logic [3:0] expected_stage_i, input logic [7:0] expected_reason_i); + begin + logic [31:0] s_write_count_before; + s_write_count_before = s_dispatcher_write_count_q; + @(negedge clk_i); + instruction = instruction_i; + source0 = source0_i; + source1 = source1_i; + destination = destination_i; + req_valid = 1'b1; + for (int cycle = 0; cycle < 1000; cycle++) begin + @(posedge clk_i); + if (result_valid) begin + if (!result_error || (error_code != expected_code_i) || + (error_stage != expected_stage_i) || (error_reason != expected_reason_i)) begin + $fatal(1, "APU-P4 error tuple mismatch got=%0d/%0d/%0d expected=%0d/%0d/%0d", + error_code, error_stage, error_reason, expected_code_i, expected_stage_i, + expected_reason_i); + end + @(negedge clk_i); + req_valid = 1'b0; + if ((result_words != 3'd0) || (result_data != '0)) begin + $fatal(1, "APU-P4 error exposed destination data words=%0d data=%h", result_words, + result_data); + end + if (s_dispatcher_write_count_q != s_write_count_before) begin + $fatal( + 1, + "APU-P4 rejected operation wrote local SRAM before=%0d after=%0d instruction=%016x", + s_write_count_before, s_dispatcher_write_count_q, instruction_i); + end + return; + end + if (req_ready) begin + @(negedge clk_i); + req_valid = 1'b0; + end + end + $fatal(1, "APU-P4 expected error timed out"); + end + endtask + + task automatic issue_expect_corpus_error( + input logic [63:0] instruction_i, input logic [31:0] source0_i, input logic [31:0] source1_i, + input logic [31:0] destination_i, input logic [5:0] expected_code_i, + input logic [3:0] expected_stage_i, input logic [7:0] expected_reason_i, + input logic check_latency_i, input logic [31:0] maximum_cycles_i, + input logic check_no_memory_request_i); + begin + logic [31:0] s_request_count_before; + s_request_count_before = s_dispatcher_request_count_q; + issue_expect_error(instruction_i, source0_i, source1_i, destination_i, expected_code_i, + expected_stage_i, expected_reason_i); + if (check_latency_i && (result_cycles > maximum_cycles_i)) begin + $fatal(1, "APU-P4 fault latency exceeded bound actual=%0d bound=%0d instruction=%016x", + result_cycles, maximum_cycles_i, instruction_i); + end + if (check_no_memory_request_i && + (s_dispatcher_request_count_q != s_request_count_before)) begin + $fatal(1, "APU-P4 rejected local operation reached SRAM instruction=%016x", instruction_i); + end + end + endtask + + initial begin + logic [31:0] observed, cycles; + requant_vector_count = 0; + if ($value$plusargs("VECTOR_COUNT=%d", requant_vector_count)) begin + if (!$value$plusargs("VECTORS=%s", requant_vector_path)) begin + $fatal(1, "APU-P4 vector path missing"); + end + $readmemh(requant_vector_path, requant_vectors); + end + flush_i = 1'b0; + epoch_clear_i = 1'b0; + image_valid_i = 1'b0; + req_valid = 1'b0; + instruction = 64'd0; + source0 = 32'd0; + source1 = 32'd0; + destination = 32'd0; + input_valid = 1'b0; + input_data = 41'd0; + output_accept = 1'b0; + loader_active = 1'b0; + loader_req = 1'b0; + loader_addr = 17'd0; + loader_data = 32'd0; + loader_strb = 4'd0; + inject_active = 1'b0; + inject_req = 1'b0; + inject_write = 1'b0; + inject_addr = 17'd0; + inject_data = 32'd0; + inject_strb = 4'd0; + + hard_reset(); + publish_table_word(17'h00000, 32'h0001_0001); + image_valid_i = 1'b1; + epoch_clear_i = 1'b1; + @(posedge clk_i); + epoch_clear_i = 1'b0; + + inject_word(17'h1a000, 32'h1357_9bdf); + read_word(17'h1a000, observed); + if (observed != 32'h1357_9bdf) $fatal(1, "APU-P4 internal-bank mapping mismatch"); + + issue(encode_instruction(4'd4, 4'd1, 4'd0, 4'd0, 4'd1, 8'd0, 32'd0), 32'd0, 32'ha5a5_5a5a, + 32'd0, observed, cycles); + issue(encode_instruction(4'd4, 4'd0, 4'd2, 4'd0, 4'd0, 8'd0, 32'd0), 32'd0, 32'd0, 32'd0, + observed, cycles); + if ((observed != 32'ha5a5_5a5a) || (cycles != 32'd2)) begin + $fatal(1, "APU-P4 local load/store mismatch"); + end + + push_input({1'b0, 5'd0, 3'd4, 32'hdead_beef}); + issue(encode_instruction(4'd4, 4'd5, 4'd4, 4'd0, 4'd0, 8'd0, 32'd0), 32'd0, 32'd0, 32'd0, + observed, cycles); + if ((observed != 32'hdead_beef) || (result_data[1] != 32'd4)) begin + $fatal(1, "APU-P4 FIFO pop mismatch"); + end + issue(encode_instruction(4'd4, 4'd6, 4'd0, 4'd4, 4'd5, 8'd0, 32'd0), 32'h0102_0304, + 32'h0000_0104, 32'd0, observed, cycles); + if (!output_valid || (output_data != {1'b1, 5'd0, 3'd4, 32'h0102_0304})) begin + $fatal(1, "APU-P4 FIFO push mismatch"); + end + output_accept = 1'b1; + @(posedge clk_i); + output_accept = 1'b0; + + push_input({1'b1, 5'd0, 3'd4, 32'h4433_2211}); + issue(encode_instruction(4'd2, 4'd0, 4'd0, 4'd0, 4'd0, 8'd0, 32'd16), 32'd0, 32'd0, 32'd0, + observed, cycles); + if ((observed != 32'd32) || (cycles != 32'd3)) $fatal(1, "APU-P4 REFILL mismatch"); + issue(encode_instruction(4'd2, 4'd1, 4'd1, 4'd0, 4'd0, 8'd0, 32'd8), 32'd0, 32'd0, 32'd0, + observed, cycles); + if (observed != 32'h11) $fatal(1, "APU-P4 PEEK mismatch"); + issue(encode_instruction(4'd2, 4'd2, 4'd1, 4'd0, 4'd0, 8'd0, 32'd8), 32'd0, 32'd0, 32'd0, + observed, cycles); + if (observed != 32'h11) $fatal(1, "APU-P4 GET mismatch"); + issue(encode_instruction(4'd2, 4'd5, 4'd2, 4'd3, 4'd4, 8'd16, 32'd2), 32'h3344, 32'hffff, 32'd0, + observed, cycles); + if ((observed != 32'd1) || (cycles != 32'd4)) $fatal(1, "APU-P4 FRAME_SYNC mismatch"); + issue(encode_instruction(4'd2, 4'd3, 4'd0, 4'd0, 4'd0, 8'd0, 32'd4), 32'd0, 32'd0, 32'd0, + observed, cycles); + issue(encode_instruction(4'd2, 4'd4, 4'd0, 4'd0, 4'd0, 8'd0, 32'd0), 32'd0, 32'd0, 32'd0, + observed, cycles); + issue(encode_instruction(4'd2, 4'd6, 4'd5, 4'd6, 4'd7, 8'd0, 32'd0), 32'd0, 32'h31, 32'd0, + observed, cycles); + if (observed != 32'h97) $fatal(1, "APU-P4 CRC8 mismatch"); + issue(encode_instruction(4'd2, 4'd7, 4'd5, 4'd6, 4'd7, 8'd0, 32'd0), 32'd0, 32'h31, 32'd0, + observed, cycles); + if (observed != 32'h80a5) $fatal(1, "APU-P4 CRC16 mismatch"); + + flush_i = 1'b1; + @(posedge clk_i); + flush_i = 1'b0; + read_word(17'h00000, observed); + if (observed != 32'h0001_0001) $fatal(1, "APU-P4 table preload mismatch"); + push_input({1'b1, 5'd0, 3'd1, 32'h0000_0000}); + issue(encode_instruction(4'd3, 4'd0, 4'd2, 4'd0, 4'd1, 8'd1, 32'd0), 32'd0, 32'd1, 32'd0, + observed, cycles); + if (observed != 32'd1) begin + $fatal(1, "APU-P4 Huffman mismatch observed=%08x symbol=%08x memory=%08x", observed, + u_dut.u_entropy_engine.s_symbol_q, memory_read_data); + end + + flush_i = 1'b1; + @(posedge clk_i); + flush_i = 1'b0; + for (int index = 0; index < 32; index++) begin + publish_table_word(17'(index * 4), 32'((5 << 16) | index)); + end + push_input({1'b1, 5'd0, 3'd1, 32'h0000_00f8}); + issue(encode_instruction(4'd3, 4'd0, 4'd2, 4'd0, 4'd1, 8'd5, 32'd0), 32'd0, 32'd32, 32'd0, + observed, cycles); + if ((observed != 32'd31) || (cycles != 32'd58)) begin + $fatal(1, "APU-P4 32-entry Huffman mismatch observed=%0d cycles=%0d", observed, cycles); + end + + flush_i = 1'b1; + @(posedge clk_i); + flush_i = 1'b0; + publish_table_word(17'h00000, 32'h0001_0023); + push_input({1'b1, 5'd0, 3'd1, 32'h0000_0000}); + issue(encode_instruction(4'd3, 4'd1, 4'd2, 4'd0, 4'd1, 8'd1, 32'd0), 32'd0, 32'd1, 32'd0, + observed, cycles); + if ((observed != 32'd2) || (result_data[1] != 32'd3)) $fatal(1, "APU-P4 Huffman pair mismatch"); + + flush_i = 1'b1; + @(posedge clk_i); + flush_i = 1'b0; + publish_table_word(17'h00000, 32'h0001_000a); + push_input({1'b1, 5'd0, 3'd1, 32'h0000_0000}); + issue(encode_instruction(4'd3, 4'd2, 4'd2, 4'd0, 4'd1, 8'd1, 32'd0), 32'd0, 32'd1, 32'd0, + observed, cycles); + if ((observed != 32'd1) || (result_data[1] != 32'd0) || + (result_data[2] != 32'd1) || (result_data[3] != 32'd0)) begin + $fatal(1, "APU-P4 Huffman quad mismatch"); + end + + flush_i = 1'b1; + @(posedge clk_i); + flush_i = 1'b0; + push_input({1'b1, 5'd0, 3'd1, 32'h0000_0020}); + issue(encode_instruction(4'd3, 4'd3, 4'd2, 4'd0, 4'd0, 8'd0, 32'd8), 32'd0, 32'd0, 32'd0, + observed, cycles); + if (observed != 32'd2) $fatal(1, "APU-P4 unary mismatch"); + + flush_i = 1'b1; + @(posedge clk_i); + flush_i = 1'b0; + push_input({1'b1, 5'd0, 3'd1, 32'h0000_0040}); + issue(encode_instruction(4'd3, 4'd4, 4'd2, 4'd0, 4'd1, 8'd0, 32'd0), 32'd1, 32'd0, 32'd0, + observed, cycles); + if (observed != 32'd1) $fatal(1, "APU-P4 Rice4 mismatch"); + + flush_i = 1'b1; + @(posedge clk_i); + flush_i = 1'b0; + push_input({1'b1, 5'd0, 3'd1, 32'h0000_0040}); + issue(encode_instruction(4'd3, 4'd5, 4'd2, 4'd0, 4'd1, 8'd0, 32'd0), 32'd1, 32'd0, 32'd0, + observed, cycles); + if (observed != 32'd1) $fatal(1, "APU-P4 Rice5 mismatch"); + + flush_i = 1'b1; + @(posedge clk_i); + flush_i = 1'b0; + push_input({1'b1, 5'd0, 3'd1, 32'h0000_00c0}); + issue(encode_instruction(4'd3, 4'd6, 4'd2, 4'd0, 4'd1, 8'd0, 32'd0), 32'd4, 32'd1, 32'd0, + observed, cycles); + if (observed != 32'hffff_fffb) $fatal(1, "APU-P4 sign restore mismatch"); + + issue_expect_error(encode_instruction(4'd4, 4'd6, 4'd0, 4'd0, 4'd1, 8'd0, 32'd0), 32'h1234_5678, + 32'd0, 32'd0, `APB4_APU__ERROR_CODE_OVERFLOW, + `APB4_APU__ERROR_STAGE_RESAMPLER, 8'd9); + if (output_valid) $fatal(1, "APU-P4 rejected FIFO push mutated output FIFO"); + issue_expect_error(encode_instruction(4'd4, 4'd0, 4'd0, 4'd0, 4'd0, 8'd0, 32'd0), 32'd1, 32'd0, + 32'd0, `APB4_APU__ERROR_CODE_SEQUENCER, `APB4_APU__ERROR_STAGE_LIFECYCLE, + 8'd5); + issue_expect_error(encode_instruction(4'd3, 4'd0, 4'd0, 4'd0, 4'd1, 8'd1, 32'd0), 32'h1800, + 32'd1, 32'd0, `APB4_APU__ERROR_CODE_SEQUENCER, + `APB4_APU__ERROR_STAGE_LIFECYCLE, 8'd5); + + flush_i = 1'b1; + @(posedge clk_i); + flush_i = 1'b0; + push_input({1'b1, 5'd0, 3'd1, 32'h0000_0000}); + issue_expect_error(encode_instruction(4'd2, 4'd2, 4'd0, 4'd0, 4'd0, 8'd0, 32'd16), 32'd0, 32'd0, + 32'd0, `APB4_APU__ERROR_CODE_TRUNCATED, `APB4_APU__ERROR_STAGE_BITSTREAM, + 8'd9); + + flush_i = 1'b1; + @(posedge clk_i); + flush_i = 1'b0; + push_input({1'b1, 5'd0, 3'd1, 32'h0000_0000}); + issue_expect_error(encode_instruction(4'd2, 4'd5, 4'd0, 4'd1, 4'd2, 8'd16, 32'd1), 32'hffff, + 32'hffff, 32'd0, `APB4_APU__ERROR_CODE_MALFORMED, + `APB4_APU__ERROR_STAGE_BITSTREAM, 8'd9); + + flush_i = 1'b1; + @(posedge clk_i); + flush_i = 1'b0; + push_input({1'b1, 5'd0, 3'd1, 32'h0000_0000}); + issue_expect_error(encode_instruction(4'd3, 4'd3, 4'd0, 4'd0, 4'd0, 8'd0, 32'd1), 32'd0, 32'd0, + 32'd0, `APB4_APU__ERROR_CODE_DECODE, `APB4_APU__ERROR_STAGE_ENTROPY, 8'd9); + + inject_word(17'h02100, 32'd100); + inject_word(17'h02140, 32'h4000_0000); + inject_word(17'h02144, 32'd3); + issue_expect_error(encode_instruction(4'd5, 4'd9, 4'd2, 4'd0, 4'd1, 8'd0, 32'd1), 32'h100, + 32'h140, 32'h180, `APB4_APU__ERROR_CODE_RECONSTRUCTION, + `APB4_APU__ERROR_STAGE_RESAMPLER, 8'd9); + + inject_word(17'h02100, 32'd1000); + inject_word(17'h02140, 32'h4000_0000); + inject_word(17'h02180, 32'h0000_0140); + issue_expect_error(encode_instruction(4'd5, 4'd0, 4'd2, 4'd0, 4'd1, 8'h80, 32'd1), 32'h101, + 32'h180, 32'h1c0, `APB4_APU__ERROR_CODE_SEQUENCER, + `APB4_APU__ERROR_STAGE_LIFECYCLE, 8'd5); + issue_expect_error(encode_instruction(4'd5, 4'd0, 4'd2, 4'd0, 4'd1, 8'h80, 32'd1), 32'h100, + 32'h181, 32'h1c0, `APB4_APU__ERROR_CODE_SEQUENCER, + `APB4_APU__ERROR_STAGE_LIFECYCLE, 8'd5); + issue_expect_error(encode_instruction(4'd5, 4'd0, 4'd2, 4'd0, 4'd1, 8'h80, 32'd1), 32'h100, + 32'h180, 32'h1c1, `APB4_APU__ERROR_CODE_SEQUENCER, + `APB4_APU__ERROR_STAGE_LIFECYCLE, 8'd5); + inject_word(17'h02180, 32'h0000_0141); + issue_expect_error(encode_instruction(4'd5, 4'd0, 4'd2, 4'd0, 4'd1, 8'h80, 32'd1), 32'h100, + 32'h180, 32'h1c0, `APB4_APU__ERROR_CODE_SEQUENCER, + `APB4_APU__ERROR_STAGE_LIFECYCLE, 8'd5); + inject_word(17'h02180, 32'h0000_0140); + issue(encode_instruction(4'd5, 4'd0, 4'd2, 4'd0, 4'd1, 8'h80, 32'd1), 32'h100, 32'h180, 32'h1c0, + observed, cycles); + if ((observed != 32'd1) || (cycles != 32'd12) || !result_kernel || !kernel_done) begin + $fatal(1, "APU-P4 REQUANT completion mismatch result=%0d cycles=%0d kernel=%0d done=%0d", + observed, cycles, result_kernel, kernel_done); + end + read_word(17'h021c0, observed); + if (observed != 32'd1000) $fatal(1, "APU-P4 REQUANT data mismatch"); + + inject_word(17'h02200, 32'd10); + inject_word(17'h02240, 32'd3); + inject_word(17'h02280, 32'h0000_0240); + issue(encode_instruction(4'd5, 4'd1, 4'd2, 4'd0, 4'd1, 8'd1, 32'd1), 32'h200, 32'h280, 32'h300, + observed, cycles); + if ((observed != 32'd2) || (cycles != 32'd12)) $fatal(1, "APU-P4 STEREO mismatch"); + read_word(17'h02304, observed); + if (observed != 32'd7) $fatal(1, "APU-P4 STEREO output mismatch"); + + for (int index = 0; index < 1536; index++) begin + publish_table_word(17'(index * 4), 32'd0); + end + for (int row = 0; row < 6; row++) begin + publish_table_word(17'(((row * 6) + row) * 4), 32'h4000_0000); + inject_word(17'(17'h02400 + (row * 4)), 32'(row + 1)); + end + inject_word(17'h02480, 32'h8000_0000); + issue(encode_instruction(4'd5, 4'd2, 4'd2, 4'd0, 4'd1, 8'd0, 32'd1), 32'h400, 32'h480, 32'h500, + observed, cycles); + if ((observed != 32'd12) || (cycles != 32'd102)) $fatal(1, "APU-P4 IMDCT6 mismatch"); + read_word(17'h02514, observed); + if (observed != 32'd6) $fatal(1, "APU-P4 IMDCT6 output mismatch"); + + for (int index = 0; index < 648; index++) begin + publish_table_word(17'(index * 4), 32'd0); + end + for (int row = 0; row < 18; row++) begin + publish_table_word(17'(((row * 18) + row) * 4), 32'h4000_0000); + inject_word(17'(17'h02600 + (row * 4)), 32'(row + 1)); + end + inject_word(17'h02680, 32'h8000_0000); + issue(encode_instruction(4'd5, 4'd3, 4'd2, 4'd0, 4'd1, 8'd0, 32'd1), 32'h600, 32'h680, 32'h700, + observed, cycles); + if ((observed != 32'd36) || (cycles != 32'd714)) $fatal(1, "APU-P4 IMDCT18 mismatch"); + read_word(17'h02744, observed); + if (observed != 32'd18) $fatal(1, "APU-P4 IMDCT18 output mismatch"); + + for (int index = 0; index < 1536; index++) begin + publish_table_word(17'(index * 4), 32'd0); + end + for (int row = 0; row < 32; row++) begin + publish_table_word(17'(((row * 32) + row) * 4), 32'h4000_0000); + publish_table_word(17'(17'h01000 + (row * 4)), 32'h4000_0000); + inject_word(17'(17'h02000 + (row * 4)), 32'(row + 1)); + end + for (int index = 0; index < 512; index++) begin + inject_word(17'(17'h03000 + (index * 4)), 32'd0); + end + inject_word(17'h02800, 32'h8000_0000); + inject_word(17'h02804, 32'h8000_1000); + inject_word(17'h02808, 32'h0000_1000); + inject_word(17'h0280c, 32'd0); + issue(encode_instruction(4'd5, 4'd4, 4'd2, 4'd0, 4'd1, 8'd0, 32'd1), 32'h000, 32'h800, 32'h2000, + observed, cycles); + if ((observed != 32'd32) || (cycles != 32'd2312)) $fatal(1, "APU-P4 DCT32 mismatch"); + read_word(17'h0407c, observed); + if (observed != 32'd32) $fatal(1, "APU-P4 DCT32 output mismatch"); + + inject_word(17'h04200, 32'd1); + inject_word(17'h04204, 32'd1); + inject_word(17'h04240, 32'd2); + inject_word(17'h04244, 32'd1); + inject_word(17'h04280, 32'h0000_2240); + issue(encode_instruction(4'd5, 4'd5, 4'd2, 4'd0, 4'd1, 8'd2, 32'd2), 32'h2200, 32'h2280, + 32'h2300, observed, cycles); + if ((observed != 32'd2) || (cycles != 32'd20)) $fatal(1, "APU-P4 FIXED mismatch"); + read_word(17'h04304, observed); + if (observed != 32'd7) $fatal(1, "APU-P4 FIXED output mismatch"); + + inject_word(17'h04400, 32'd1); + inject_word(17'h04404, 32'd1); + inject_word(17'h04440, 32'd1); + inject_word(17'h04444, 32'd1); + inject_word(17'h04480, 32'd1); + inject_word(17'h04484, 32'd0); + inject_word(17'h044c0, 32'h0000_2440); + inject_word(17'h044c4, 32'h0000_2480); + inject_word(17'h044c8, 32'd0); + issue(encode_instruction(4'd5, 4'd6, 4'd2, 4'd0, 4'd1, 8'd2, 32'd2), 32'h2400, 32'h24c0, + 32'h2500, observed, cycles); + if ((observed != 32'd2) || (cycles != 32'd26)) $fatal(1, "APU-P4 LPC mismatch"); + read_word(17'h04504, observed); + if (observed != 32'd4) $fatal(1, "APU-P4 LPC output mismatch"); + + inject_word(17'h04600, 32'd10); + inject_word(17'h04640, 32'd3); + inject_word(17'h04680, 32'h0000_2640); + issue(encode_instruction(4'd5, 4'd7, 4'd2, 4'd0, 4'd1, 8'd1, 32'd1), 32'h2600, 32'h2680, + 32'h2700, observed, cycles); + if (observed != 32'd2) $fatal(1, "APU-P4 DECORRELATE mismatch"); + read_word(17'h04704, observed); + if (observed != 32'd7) $fatal(1, "APU-P4 DECORRELATE output mismatch"); + + for (int profile = 0; profile < 16; profile++) begin + flush_i = 1'b1; + @(posedge clk_i); + flush_i = 1'b0; + for (int index = 0; index < 17; index++) begin + inject_word(17'(17'h048e4 + (index * 4)), 32'd0); + end + inject_word(17'h04a00, 32'h8000_0000); + inject_word(17'h04a04, 32'h8000_0800); + inject_word(17'h04a08, 32'h8000_1000); + inject_word(17'h04a0c, 32'd0); + inject_word(17'h04a10, 32'd0); + inject_word(17'h04a14, 32'd0); + inject_word(17'h04a18, 32'd0); + inject_word(17'h04a1c, 32'd1); + issue(encode_instruction(4'd5, 4'd8, 4'd2, 4'd0, 4'd1, 8'(profile), 32'd1), 32'h2900, + 32'h2a00, 32'h2b00, observed, cycles); + if (observed != expected_resample_frames(4'(profile))) begin + $fatal(1, "APU-P4 RESAMPLE profile %0d mismatch result=%0d", profile, observed); + end + end + issue_expect_error(encode_instruction(4'd5, 4'd8, 4'd2, 4'd0, 4'd1, 8'd14, 32'd1), 32'h2900, + 32'h2a00, 32'h2b00, `APB4_APU__ERROR_CODE_RECONSTRUCTION, + `APB4_APU__ERROR_STAGE_RESAMPLER, 8'd9); + + inject_word(17'h04c00, 32'd100); + inject_word(17'h04c40, 32'h4000_0000); + inject_word(17'h04c44, 32'd1); + issue(encode_instruction(4'd5, 4'd9, 4'd2, 4'd0, 4'd1, 8'd4, 32'd1), 32'h2c00, 32'h2c40, + 32'h2d00, observed, cycles); + if ((observed != 32'd4) || (cycles != 32'd18)) $fatal(1, "APU-P4 PCM_PACK mismatch"); + read_word(17'h04d00, observed); + if (observed != 32'h0064_0064) $fatal(1, "APU-P4 PCM_PACK output mismatch"); + +`ifdef APU_P4_SHARED_CORPUS + `include "apu_p4_corpus_generated.svh" +`endif + + for (int vector_index = 0; vector_index < requant_vector_count; vector_index++) begin + inject_word(17'h02e00, requant_vectors[(vector_index*4)]); + inject_word(17'h02e40, requant_vectors[(vector_index*4)+1]); + inject_word(17'h02e80, 32'h0000_0e40); + issue(encode_instruction( + 4'd5, 4'd0, 4'd2, 4'd0, 4'd1, requant_vectors[(vector_index*4)+2][7:0], 32'd1), + 32'h0e00, 32'h0e80, 32'h0ec0, observed, cycles); + read_word(17'h02ec0, observed); + if (observed != requant_vectors[(vector_index*4)+3]) begin + $fatal(1, "APU-P4 requant differential mismatch vector=%0d got=%08x expected=%08x", + vector_index, observed, requant_vectors[(vector_index*4)+3]); + end + end + + $display("APU_P4_PRIMITIVES_PASS"); + $finish; + end + + logic s_unused; + assign s_unused = memory_ready ^ input_exhausted ^ input_ready ^ output_ready ^ + dispatcher_busy ^ ^result_dst ^ ^result_words; +endmodule diff --git a/tests/rtl/apu_p4_sequencer_tb.sv b/tests/rtl/apu_p4_sequencer_tb.sv new file mode 100644 index 00000000..673a6d41 --- /dev/null +++ b/tests/rtl/apu_p4_sequencer_tb.sv @@ -0,0 +1,380 @@ +// Copyright (c) 2026 Yuchi Miao +// SPDX-License-Identifier: MulanPSL-2.0 + +`include "apu_define.svh" + +module apu_p4_sequencer_tb; + logic clk_i = 1'b0; + logic rst_n_i = 1'b0; + logic soft_reset_i, resource_reset_i, abort_i, counter_clear_i, launch_i; + logic store_loader_active, store_loader_write; + logic [10:0] store_loader_addr; + logic [63:0] store_loader_data, store_loader_read_data; + logic store_loader_valid; + logic fetch, fetch_valid; + logic [10:0] fetch_addr; + logic [63:0] fetch_data; + logic [2:0][10:0] entry_pc, entry_first, entry_last; + logic [2:0][15:0] entry_max_loop; + logic [2:0][23:0] entry_max_retired; + logic [2:0][16:0] entry_scratch_base, entry_scratch_bytes; + logic [2:0][31:0] entry_primitive_mask; + logic [2:0][15:0] entry_table_offset, entry_table_bytes; + logic primitive_req_valid, primitive_req_ready, primitive_result_valid; + logic [63:0] primitive_instruction; + logic [31:0] primitive_source0, primitive_source1, primitive_destination; + logic [3:0] primitive_result_dst; + logic [3:0][31:0] primitive_result_data; + logic [2:0] primitive_result_words; + logic primitive_result_kernel, primitive_error, primitive_kernel_done, primitive_busy; + logic [5:0] primitive_error_code; + logic [3:0] primitive_error_stage; + logic [7:0] primitive_error_reason; + logic primitive_input_exhausted, primitive_input_ready, primitive_output_ready; + logic memory_req, memory_write, memory_valid, memory_error; + logic [16:0] memory_addr; + logic [31:0] memory_write_data, memory_read_data; + logic [3:0] memory_strb; + logic inject_active, inject_req, inject_write; + logic [16:0] inject_addr; + logic [31:0] inject_data; + logic [ 3:0] inject_strb; + logic codec_req, codec_write; + logic [16:0] codec_addr; + logic [31:0] codec_data; + logic [ 3:0] codec_strb; + logic [31:0] status, retired; + logic [15:0][31:0] gpr; + logic trapped, trap_event, abort_done, fault_valid, sequencer_idle; + logic [5:0] fault_code; + logic [3:0] fault_stage; + logic [1:0] fault_resp; + logic [7:0] fault_index; + logic [31:0] fault_addr, fault_detail; + logic [63:0] perf_retired; + logic [16:0] active_scratch_base, active_scratch_bytes; + logic [31:0] active_primitive_mask; + logic [15:0] active_table_offset, active_table_bytes; + logic launch_epoch; + + always #5 clk_i = ~clk_i; + + always_ff @(posedge clk_i) begin + if (rst_n_i && primitive_busy && primitive_req_valid && + (primitive_instruction[63:60] != `APB4_APU__MC_CLASS_KERNEL)) begin + $fatal(1, "APU-P4 issued a second primitive while the kernel owned local SRAM"); + end + end + + assign codec_req = inject_active ? inject_req : memory_req; + assign codec_write = inject_active ? inject_write : memory_write; + assign codec_addr = inject_active ? inject_addr : memory_addr; + assign codec_data = inject_active ? inject_data : memory_write_data; + assign codec_strb = inject_active ? inject_strb : memory_strb; + + apu_control_store u_control_store ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .loader_active_i(store_loader_active), + .loader_read_i (1'b0), + .loader_write_i (store_loader_write), + .loader_addr_i (store_loader_addr), + .loader_data_i (store_loader_data), + .loader_data_o (store_loader_read_data), + .loader_valid_o (store_loader_valid), + .image_valid_i (1'b1), + .fetch_i (fetch), + .fetch_addr_i (fetch_addr), + .fetch_data_o (fetch_data), + .fetch_valid_o (fetch_valid) + ); + + apu_local_sram u_local_sram ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .image_valid_i (1'b1), + .table_bytes_i (16'd0), + .epoch_clear_i (launch_epoch || soft_reset_i || resource_reset_i || abort_i), + .loader_active_i (1'b0), + .loader_req_i (1'b0), + .loader_addr_i (17'd0), + .loader_data_i (32'd0), + .loader_strb_i (4'd0), + .loader_ready_o (), + .codec_req_i (codec_req), + .codec_write_i (codec_write), + .codec_addr_i (codec_addr), + .codec_data_i (codec_data), + .codec_strb_i (codec_strb), + .codec_ready_o (), + .codec_data_o (memory_read_data), + .codec_valid_o (memory_valid), + .codec_access_err_o(memory_error) + ); + + apu_primitive_dispatcher u_dispatcher ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .flush_i (launch_epoch || soft_reset_i || resource_reset_i || abort_i), + .req_valid_i (primitive_req_valid), + .req_ready_o (primitive_req_ready), + .instruction_i (primitive_instruction), + .source0_i (primitive_source0), + .source1_i (primitive_source1), + .destination_i (primitive_destination), + .scratch_base_i (active_scratch_base), + .scratch_bytes_i (active_scratch_bytes), + .table_offset_i (active_table_offset), + .table_bytes_i (active_table_bytes), + .result_valid_o (primitive_result_valid), + .result_dst_o (primitive_result_dst), + .result_data_o (primitive_result_data), + .result_words_o (primitive_result_words), + .result_kernel_o (primitive_result_kernel), + .error_o (primitive_error), + .error_code_o (primitive_error_code), + .error_stage_o (primitive_error_stage), + .error_reason_o (primitive_error_reason), + .cycles_o (), + .kernel_done_o (primitive_kernel_done), + .busy_o (primitive_busy), + .input_exhausted_o(primitive_input_exhausted), + .input_ready_o (primitive_input_ready), + .output_ready_o (primitive_output_ready), + .input_count_o (), + .output_count_o (), + .kernel_busy_o (), + .memory_req_o (memory_req), + .memory_write_o (memory_write), + .memory_addr_o (memory_addr), + .memory_data_o (memory_write_data), + .memory_strb_o (memory_strb), + .memory_valid_i (memory_valid), + .memory_data_i (memory_read_data), + .memory_error_i (memory_error), + .input_valid_i (1'b0), + .input_data_i (41'd0), + .input_accept_o (), + .output_valid_o (), + .output_data_o (), + .output_accept_i (1'b0) + ); + + apu_codec_sequencer #( + .EnableP4(1'b1) + ) u_sequencer ( + .clk_i (clk_i), + .rst_n_i (rst_n_i), + .soft_reset_i (soft_reset_i), + .resource_reset_i (resource_reset_i), + .counter_clear_i (counter_clear_i), + .abort_i (abort_i), + .launch_i (launch_i), + .launch_entry_i (2'd0), + .image_valid_i (1'b1), + .timeout_i (32'd4096), + .entry_pc_i (entry_pc), + .entry_first_i (entry_first), + .entry_last_i (entry_last), + .entry_max_loop_i (entry_max_loop), + .entry_max_retired_i (entry_max_retired), + .entry_scratch_base_i (entry_scratch_base), + .entry_scratch_bytes_i (entry_scratch_bytes), + .entry_primitive_mask_i (entry_primitive_mask), + .entry_table_offset_i (entry_table_offset), + .entry_table_bytes_i (entry_table_bytes), + .input_exhausted_i (primitive_input_exhausted), + .input_ready_i (primitive_input_ready), + .output_ready_i (primitive_output_ready), + .kernel_done_i (primitive_kernel_done), + .transport_idle_success_i (1'b1), + .stall_i (1'b0), + .cause_valid_i (1'b0), + .cause_code_i (6'd0), + .cause_stage_i (4'd0), + .cause_resp_i (2'd0), + .cause_index_i (8'd0), + .cause_addr_i (32'd0), + .cause_detail_i (32'd0), + .primitive_req_valid_o (primitive_req_valid), + .primitive_req_ready_i (primitive_req_ready), + .primitive_instruction_o (primitive_instruction), + .primitive_source0_o (primitive_source0), + .primitive_source1_o (primitive_source1), + .primitive_destination_o (primitive_destination), + .primitive_result_valid_i (primitive_result_valid), + .primitive_result_dst_i (primitive_result_dst), + .primitive_result_data_i (primitive_result_data), + .primitive_result_words_i (primitive_result_words), + .primitive_result_kernel_i(primitive_result_kernel), + .primitive_error_i (primitive_error), + .primitive_error_code_i (primitive_error_code), + .primitive_error_stage_i (primitive_error_stage), + .primitive_error_reason_i (primitive_error_reason), + .fetch_o (fetch), + .fetch_addr_o (fetch_addr), + .fetch_data_i (fetch_data), + .fetch_valid_i (fetch_valid), + .stat_o (status), + .retired_o (retired), + .gpr_o (gpr), + .trapped_o (trapped), + .trap_event_o (trap_event), + .abort_done_o (abort_done), + .fault_valid_o (fault_valid), + .fault_code_o (fault_code), + .fault_stage_o (fault_stage), + .fault_resp_o (fault_resp), + .fault_index_o (fault_index), + .fault_addr_o (fault_addr), + .fault_detail_o (fault_detail), + .perf_retired_o (perf_retired), + .active_scratch_base_o (active_scratch_base), + .active_scratch_bytes_o (active_scratch_bytes), + .active_primitive_mask_o (active_primitive_mask), + .active_table_offset_o (active_table_offset), + .active_table_bytes_o (active_table_bytes), + .launch_epoch_o (launch_epoch), + .idle_o (sequencer_idle) + ); + + function automatic logic [63:0] encode_instruction( + input logic [3:0] instruction_class_i, input logic [3:0] opcode_i, input logic [3:0] dst_i, + input logic [3:0] src0_i, input logic [3:0] src1_i, input logic [7:0] aux_i, + input logic [31:0] immediate_i); + return {instruction_class_i, opcode_i, 4'd0, dst_i, src0_i, src1_i, aux_i, immediate_i}; + endfunction + + task automatic load_instruction(input logic [10:0] address_i, input logic [63:0] data_i); + begin + @(negedge clk_i); + store_loader_active = 1'b1; + store_loader_write = 1'b1; + store_loader_addr = address_i; + store_loader_data = data_i; + @(negedge clk_i); + store_loader_write = 1'b0; + end + endtask + + task automatic inject_word(input logic [16:0] address_i, input logic [31:0] data_i); + begin + @(negedge clk_i); + inject_active = 1'b1; + inject_req = 1'b1; + inject_write = 1'b1; + inject_addr = address_i; + inject_data = data_i; + inject_strb = 4'hf; + @(negedge clk_i); + inject_active = 1'b0; + inject_req = 1'b0; + inject_write = 1'b0; + end + endtask + + initial begin + soft_reset_i = 1'b0; + resource_reset_i = 1'b0; + abort_i = 1'b0; + counter_clear_i = 1'b0; + launch_i = 1'b0; + store_loader_active = 1'b0; + store_loader_write = 1'b0; + store_loader_addr = 11'd0; + store_loader_data = 64'd0; + inject_active = 1'b0; + inject_req = 1'b0; + inject_write = 1'b0; + inject_addr = 17'd0; + inject_data = 32'd0; + inject_strb = 4'd0; + entry_pc = {11'd0, 11'd0, 11'd0}; + entry_first = {11'd0, 11'd0, 11'd0}; + entry_last = {11'd10, 11'd10, 11'd10}; + entry_max_loop = {16'd4, 16'd4, 16'd4}; + entry_max_retired = {24'd32, 24'd32, 24'd32}; + entry_scratch_base = {17'd0, 17'd0, 17'd0}; + entry_scratch_bytes = {17'h06000, 17'h06000, 17'h06000}; + entry_primitive_mask = {32'h0000_0070, 32'h0000_0070, 32'h0000_0070}; + entry_table_offset = {16'd0, 16'd0, 16'd0}; + entry_table_bytes = {16'd0, 16'd0, 16'd0}; + + rst_n_i = 1'b0; + repeat (3) @(posedge clk_i); + rst_n_i = 1'b1; + repeat (2) @(posedge clk_i); + + load_instruction(11'd0, encode_instruction(4'd1, 4'd1, 4'd0, 4'd0, 4'd0, 8'd0, 32'h0000_0100)); + load_instruction(11'd1, encode_instruction(4'd1, 4'd1, 4'd1, 4'd0, 4'd0, 8'd0, 32'h0000_0180)); + load_instruction(11'd2, encode_instruction(4'd1, 4'd1, 4'd4, 4'd0, 4'd0, 8'd0, 32'h0000_01c0)); + load_instruction(11'd3, encode_instruction(4'd1, 4'd1, 4'd5, 4'd0, 4'd0, 8'd0, 32'h0000_0200)); + load_instruction(11'd4, encode_instruction(4'd1, 4'd1, 4'd6, 4'd0, 4'd0, 8'd0, 32'h1234_5678)); + load_instruction(11'd5, encode_instruction(4'd5, 4'd0, 4'd4, 4'd0, 4'd1, 8'h80, 32'd1)); + load_instruction(11'd6, encode_instruction(4'd1, 4'd1, 4'd7, 4'd0, 4'd0, 8'd0, 32'h0000_0055)); + load_instruction(11'd7, encode_instruction(4'd4, 4'd1, 4'd0, 4'd5, 4'd6, 8'd0, 32'd0)); + load_instruction(11'd8, encode_instruction(4'd0, 4'd8, 4'd0, 4'd0, 4'd0, 8'd1, 32'd0)); + load_instruction(11'd9, encode_instruction(4'd4, 4'd0, 4'd3, 4'd5, 4'd0, 8'd0, 32'd0)); + load_instruction(11'd10, encode_instruction(4'd0, 4'd1, 4'd0, 4'd0, 4'd0, 8'd0, 32'd0)); + + @(negedge clk_i); + launch_i = 1'b1; + @(negedge clk_i); + launch_i = 1'b0; + store_loader_active = 1'b0; + inject_word(17'h00100, 32'd1000); + inject_word(17'h00140, 32'h4000_0000); + inject_word(17'h00180, 32'h0000_0140); + for (int cycle = 0; cycle < 10000; cycle++) begin + @(posedge clk_i); + if (fault_valid || trapped) begin + $fatal( + 1, + "APU-P4 sequencer fault code=%0d detail=%08x status=%08x insn=%016x legal=%0d r5=%08x r6=%08x valid=%0d", + fault_code, fault_detail, status, u_sequencer.s_instruction_q, + apu_microcode_pkg::instruction_encoding_valid(u_sequencer.s_instruction_q), gpr[5], + gpr[6], u_local_sram.s_mutable_valid_q[17'h00200>>2]); + end + if (sequencer_idle && !primitive_busy && (retired != 32'd0)) break; + if (cycle == 9999) $fatal(1, "APU-P4 sequencer timed out"); + end + if ((gpr[3] != 32'h1234_5678) || (gpr[4] != 32'd1) || (gpr[7] != 32'h55) || + (retired != 32'd11) || (perf_retired != 64'd11)) begin + $fatal(1, "APU-P4 sequencer mismatch r3=%0d r4=%0d r7=%0d retired=%0d perf=%0d", gpr[3], + gpr[4], gpr[7], retired, perf_retired); + end + + @(negedge clk_i); + store_loader_active = 1'b1; + launch_i = 1'b1; + @(negedge clk_i); + launch_i = 1'b0; + store_loader_active = 1'b0; + @(posedge clk_i); + if (u_local_sram.s_mutable_valid_q[17'h00200>>2] || + u_dispatcher.u_kernel_engine.s_resample_profile_valid_q) begin + $fatal(1, "APU-P4 launch did not clear the mutable-data epoch"); + end + inject_word(17'h00100, 32'd2000); + inject_word(17'h00140, 32'h4000_0000); + inject_word(17'h00180, 32'h0000_0140); + for (int cycle = 0; cycle < 10000; cycle++) begin + @(posedge clk_i); + if (fault_valid || trapped) begin + $fatal(1, "APU-P4 second launch fault code=%0d detail=%08x", fault_code, fault_detail); + end + if (sequencer_idle && !primitive_busy && (retired != 32'd0)) break; + if (cycle == 9999) $fatal(1, "APU-P4 second launch timed out"); + end + if ((gpr[3] != 32'h1234_5678) || (gpr[4] != 32'd1) || (retired != 32'd11)) begin + $fatal(1, "APU-P4 second launch result mismatch"); + end + $display("APU_P4_SEQUENCER_PASS"); + $finish; + end + + logic s_unused; + assign s_unused = store_loader_read_data[0] ^ store_loader_valid ^ trap_event ^ abort_done ^ + fault_stage[0] ^ fault_resp[0] ^ fault_index[0] ^ fault_addr[0] ^ + active_primitive_mask[0]; +endmodule diff --git a/tests/rtl/apu_reg_tb.sv b/tests/rtl/apu_reg_tb.sv index 3d69728a..7fadf5c8 100644 --- a/tests/rtl/apu_reg_tb.sv +++ b/tests/rtl/apu_reg_tb.sv @@ -191,8 +191,8 @@ module apu_reg_tb; s_reg_count = 0; add_register(`APB4_APU__IP_ID, AccessRo, 32'h4150_5530, 1'b0); add_register(`APB4_APU__IP_VERSION, AccessRo, 32'h0001_0000, 1'b0); - add_register(`APB4_APU__CAPABILITY0, AccessRo, 32'h0000_0098, 1'b0); - add_register(`APB4_APU__CAPABILITY1, AccessRo, 32'h0000_0010, 1'b0); + add_register(`APB4_APU__CAPABILITY0, AccessRo, 32'h0000_0198, 1'b0); + add_register(`APB4_APU__CAPABILITY1, AccessRo, 32'h0182_7010, 1'b0); add_register(`APB4_APU__COMMAND, AccessWo, 32'd0, 1'b1); add_register(`APB4_APU__STATUS, AccessRo, 32'h0000_0100, 1'b0); add_register(`APB4_APU__IRQ_STATE, AccessRw, 32'd0, 1'b0); diff --git a/tests/test_apu.py b/tests/test_apu.py index ffbe8207..bd7f3bad 100644 --- a/tests/test_apu.py +++ b/tests/test_apu.py @@ -51,6 +51,14 @@ def test_apu_p1_apb_register_shell(tmp_path: Path) -> None: str(multimedia / "apu_stream_router.sv"), str(multimedia / "apu_control_store.sv"), str(multimedia / "apu_microcode_loader.sv"), + str(multimedia / "apu_local_sram.sv"), + str(multimedia / "apu_bitstream_engine.sv"), + str(multimedia / "apu_entropy_engine.sv"), + str(multimedia / "apu_reconstruction_engine.sv"), + str(multimedia / "apu_transform_engine.sv"), + str(multimedia / "apu_resampler.sv"), + str(multimedia / "apu_kernel_engine.sv"), + str(multimedia / "apu_primitive_dispatcher.sv"), str(multimedia / "apu_codec_sequencer.sv"), str(multimedia / "apu_reg.sv"), str(multimedia / "apb4_apu.sv"), @@ -128,6 +136,14 @@ def test_apu_p1_integrated_irq_ownership_topology(tmp_path: Path) -> None: str(multimedia / "apu_stream_router.sv"), str(multimedia / "apu_control_store.sv"), str(multimedia / "apu_microcode_loader.sv"), + str(multimedia / "apu_local_sram.sv"), + str(multimedia / "apu_bitstream_engine.sv"), + str(multimedia / "apu_entropy_engine.sv"), + str(multimedia / "apu_reconstruction_engine.sv"), + str(multimedia / "apu_transform_engine.sv"), + str(multimedia / "apu_resampler.sv"), + str(multimedia / "apu_kernel_engine.sv"), + str(multimedia / "apu_primitive_dispatcher.sv"), str(multimedia / "apu_codec_sequencer.sv"), str(multimedia / "apu_reg.sv"), str(multimedia / "apb4_apu.sv"), @@ -415,12 +431,12 @@ last: source_list = tmp_path / "apu_p3_microcode.fl" source_list.write_text( "\n".join( - [ - "+define+SV_ASSRT_DISABLE", - f"+incdir+{multimedia}", - f"+incdir+{common}", - str(ROOT / "rtl/tech/tc_sram.sv"), - str(multimedia / "apu_microcode_pkg.sv"), + [ + "+define+SV_ASSRT_DISABLE", + f"+incdir+{multimedia}", + f"+incdir+{common}", + str(ROOT / "rtl/tech/tc_sram.sv"), + str(multimedia / "apu_microcode_pkg.sv"), str(multimedia / "apu_control_store.sv"), str(multimedia / "apu_microcode_loader.sv"), str(multimedia / "apu_codec_sequencer.sv"), @@ -532,6 +548,14 @@ done: str(multimedia / "apu_stream_router.sv"), str(multimedia / "apu_control_store.sv"), str(multimedia / "apu_microcode_loader.sv"), + str(multimedia / "apu_local_sram.sv"), + str(multimedia / "apu_bitstream_engine.sv"), + str(multimedia / "apu_entropy_engine.sv"), + str(multimedia / "apu_reconstruction_engine.sv"), + str(multimedia / "apu_transform_engine.sv"), + str(multimedia / "apu_resampler.sv"), + str(multimedia / "apu_kernel_engine.sv"), + str(multimedia / "apu_primitive_dispatcher.sv"), str(multimedia / "apu_codec_sequencer.sv"), str(multimedia / "apu_reg.sv"), str(multimedia / "apb4_apu.sv"), @@ -575,7 +599,7 @@ done: assert "APU-P3 APB, DMA, loader integration passed" in result.stdout -def test_apu_p3_product_scope_remains_coreless_and_fail_closed() -> None: +def test_apu_p4_product_scope_remains_coreless_and_fail_closed() -> None: top = (ROOT / "rtl/ip/multimedia/apb4_apu.sv").read_text(encoding="utf-8") register_block = (ROOT / "rtl/ip/multimedia/apu_reg.sv").read_text(encoding="utf-8") product_filelist = (ROOT / "rtl/mini/filelist/ip.fl").read_text(encoding="utf-8") @@ -585,11 +609,23 @@ def test_apu_p3_product_scope_remains_coreless_and_fail_closed() -> None: assert "32'h0000_0029" in register_block assert "s_merged_write != 32'd0" in register_block assert "apu_interpreter" not in product_filelist + assert "apu_primitives.py" not in product_filelist + assert "apu_p4_primitives_tb" not in product_filelist + assert "apu_p4_loader_tb" not in product_filelist + assert "apu_p4_sequencer_tb" not in product_filelist assert "apu_p3_microcode_tb" not in product_filelist assert "VerificationCrcBypass" not in product_filelist assert ".VerificationCrcBypass" not in top assert "VerificationControlFlowBypass" not in product_filelist assert ".VerificationControlFlowBypass" not in top + for production_module in ( + "apu_local_sram.sv", + "apu_bitstream_engine.sv", + "apu_entropy_engine.sv", + "apu_kernel_engine.sv", + "apu_primitive_dispatcher.sv", + ): + assert production_module in product_filelist for forbidden in ("hazard3", "vexiiriscv", "rv32", "dsp_core"): assert forbidden not in top.lower() diff --git a/tests/test_apu_primitives.py b/tests/test_apu_primitives.py new file mode 100644 index 00000000..a3cafd9f --- /dev/null +++ b/tests/test_apu_primitives.py @@ -0,0 +1,1439 @@ +"""APU-P4 primitive BAM and target-profile coverage.""" + +from __future__ import annotations + +import binascii +import os +import random +import shutil +import struct +import subprocess +import sys +from pathlib import Path + +import pytest + + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "scripts")) + +from apu_isa import ( # noqa: E402 + APUMC_P4_CAPABILITY_MASK, + BitstreamOpcode, + ControlOpcode, + EntropyOpcode, + Entry, + Instruction, + InstructionClass, + KernelOpcode, + LocalOpcode, + build_apumc, + parse_apumc, + validate_p3_instruction, + validate_p4_instruction, +) +from apu_primitives import LocalMemory, PrimitiveBam, PrimitiveFault # noqa: E402 +from apu_mcasm import assemble # noqa: E402 +from apu_interpreter import Machine, SequencerTrap # noqa: E402 + + +ENTRY = Entry( + 0, + 0, + 0, + 0, + 1, + 1000, + scratch_base=0x1000, + scratch_bytes=0x5000, + primitive_mask=APUMC_P4_CAPABILITY_MASK, + table_offset=0, + table_bytes=0x1000, +) + + +def instruction(instruction_class: int, opcode: int, **values: int) -> Instruction: + return Instruction(instruction_class, opcode, **values) + + +def write_words(memory: LocalMemory, address: int, values: list[int]) -> None: + memory.inject(address, b"".join(struct.pack(" list[int]: + return list(struct.unpack_from(f"<{count}I", memory.data, address)) + + +def test_p4_target_is_separate_from_p3() -> None: + primitive = instruction(InstructionClass.BITSTREAM, BitstreamOpcode.GET, dst=0, immediate=8) + validate_p4_instruction(primitive) + with pytest.raises(ValueError, match="unavailable"): + validate_p3_instruction(primitive) + for source in (0, 4, 5): + with pytest.raises(ValueError, match="unavailable"): + validate_p4_instruction( + instruction(InstructionClass.CONTROL, ControlOpcode.WAIT, aux=source) + ) + for source in (1, 2, 3): + validate_p4_instruction(instruction(InstructionClass.CONTROL, ControlOpcode.WAIT, aux=source)) + with pytest.raises(ValueError, match="unavailable"): + validate_p4_instruction(instruction(InstructionClass.TRANSPORT, 0)) + + +def test_p4_bundle_table_scratch_and_mask() -> None: + program = [instruction(InstructionClass.CONTROL, ControlOpcode.END)] + entries = [ENTRY.__class__(index, 0, 0, 0, 1, 1, 0x1000, 0x1000, 0, 0, 0) for index in range(3)] + table = struct.pack("<4I", 1, 2, 3, 4) + bundle = build_apumc(program, entries, target="p4", table_payload=table) + _, parsed_entries, header = parse_apumc(bundle, "p4") + assert header[5] == 200 + assert header[6] == len(table) + assert header[9] == 0 + assert header[10] == 0x2000 + assert parsed_entries == entries + with pytest.raises(ValueError): + parse_apumc(bundle, "p3") + + +def test_p4_zero_byte_table_offset_stays_range_checked() -> None: + program = [instruction(InstructionClass.CONTROL, ControlOpcode.END)] + entries = [Entry(index, 0, 0, 0, 1, 1) for index in range(3)] + bundle = bytearray(build_apumc(program, entries, target="p4")) + struct.pack_into(" None: + assembly = assemble( + """ + .build_id 0x1234 + .entry 0 start start finish 4 32 0x1000 0x1000 0x21 0 4 + .entry 1 start start finish 4 32 0x1000 0x1000 0x21 0 4 + .entry 2 start start finish 4 32 0x1000 0x1000 0x21 0 4 + .table32 0x00010001 + start: + wait 2 + fifo_pop r2 + finish: + end + """, + target="p4", + ) + instructions, entries, header = parse_apumc(assembly.bundle, "p4") + assert assembly.target == "p4" + assert assembly.table_payload == struct.pack(" None: + program = [ + instruction(InstructionClass.BITSTREAM, BitstreamOpcode.GET, dst=0, immediate=8), + instruction(InstructionClass.CONTROL, ControlOpcode.END), + ] + entry = Entry(0, 0, 0, 1, 1, 16, 0, 0, 1, 0, 0) + machine = Machine( + program, + entry, + timeout=3, + fetch_no_retirement_cycles=0, + target="p4", + primitives=PrimitiveBam(), + ) + with pytest.raises(SequencerTrap) as caught: + machine.run() + assert caught.value.reason == 7 + + +def test_p4_local_sram_has_macro_and_inferred_profiles() -> None: + source = (ROOT / "rtl/ip/multimedia/apu_local_sram.sv").read_text(encoding="utf-8") + assert "localparam int unsigned BankCount = 28" in source + assert "tc_sram_1024x32 u_local_sram" in source + assert "parameterintunsignedMemoryWordCount=`APB4_APU__LOCAL_DATA_BYTES/4" in "".join( + source.split() + ) + assert "logic[31:0]mem[0:MemoryWordCount-1]" in "".join(source.split()) + assert "`ifdef HAVE_SRAM_MACRO" in source + assert "`else" in source + + +def test_bitstream_crc_and_frame_sync() -> None: + bam = PrimitiveBam() + bam.input_fifo.push(0x44332211 | (4 << 32)) + bam.input_fifo.push(0x55 | (1 << 32) | (1 << 40)) + registers = [0] * 16 + result = bam.execute(instruction(2, BitstreamOpcode.REFILL, dst=0, immediate=16), registers, ENTRY) + assert result.writes[0] == 32 + assert result.cycles == 3 + assert bam.execute(instruction(2, BitstreamOpcode.PEEK, dst=1, immediate=8), registers, ENTRY).writes[1] == 0x11 + assert bam.execute(instruction(2, BitstreamOpcode.GET, dst=1, immediate=8), registers, ENTRY).writes[1] == 0x11 + bam.execute(instruction(2, BitstreamOpcode.SKIP, immediate=4), registers, ENTRY) + bam.execute(instruction(2, BitstreamOpcode.ALIGN), registers, ENTRY) + registers[2], registers[3] = 0x4455, 0xFFFF + result = bam.execute( + instruction(2, BitstreamOpcode.FRAME_SYNC, dst=4, src0=2, src1=3, aux=16, immediate=4), + registers, + ENTRY, + ) + assert result.writes[4] == 1 + registers[5], registers[6] = 0, 0x31 + assert bam.execute(instruction(2, BitstreamOpcode.CRC8, dst=7, src0=5, src1=6), registers, ENTRY).writes[7] == 0x97 + assert bam.execute(instruction(2, BitstreamOpcode.CRC16, dst=7, src0=5, src1=6), registers, ENTRY).writes[7] == 0x80A5 + + +def test_entropy_modes_and_faults() -> None: + memory = LocalMemory() + table = struct.pack("<3I", 1 | (1 << 16), 0x23 | (2 << 16), 0xA | (2 << 16)) + memory.publish_table(table + bytes(0x1000 - len(table))) + bam = PrimitiveBam(memory) + registers = [0] * 16 + registers[0], registers[1] = 0, 3 + bam.input_fifo.push(0b01011000 | (1 << 32) | (1 << 40)) + assert bam.execute(instruction(3, EntropyOpcode.HUFF_SYMBOL, dst=2, src0=0, src1=1, aux=2), registers, ENTRY).writes[2] == 1 + assert bam.execute(instruction(3, EntropyOpcode.HUFF_PAIR, dst=2, src0=0, src1=1, aux=2), registers, ENTRY).writes == {2: 2, 3: 3} + + bam = PrimitiveBam(memory) + bam.input_fifo.push(0b00101011 | (1 << 32) | (1 << 40)) + assert bam.execute(instruction(3, EntropyOpcode.UNARY, dst=0, aux=0, immediate=8), registers, ENTRY).writes[0] == 2 + registers[0] = 1 + result = bam.execute(instruction(3, EntropyOpcode.RICE4, dst=2, src0=0, src1=1), registers, ENTRY) + assert result.writes[2] == 1 + registers[0], registers[1] = 4, 1 + assert bam.execute(instruction(3, EntropyOpcode.SIGN_RESTORE, dst=2, src0=0, src1=1), registers, ENTRY).writes[2] == 0xFFFFFFFB + bam = PrimitiveBam(memory) + bam.input_fifo.push(1 << 32 | 1 << 40) + with pytest.raises(PrimitiveFault) as caught: + bam.execute(instruction(3, EntropyOpcode.UNARY, dst=0, aux=0, immediate=1), registers, ENTRY) + assert (caught.value.code, caught.value.stage) == (7, 5) + + +def test_local_memory_and_fifo_epoch() -> None: + memory = LocalMemory() + memory.publish_table(struct.pack(" None: + memory = LocalMemory() + memory.publish_table(bytes(0x1000)) + memory.new_epoch() + bam = PrimitiveBam(memory) + registers = [0] * 16 + write_words(memory, 0x1000, [1000, -1000]) + write_words(memory, 0x1040, [1 << 30, 1 << 30]) + write_words(memory, 0x1080, [0x40]) + registers[0], registers[1], registers[2] = 0, 0x80, 0x100 + result = bam.execute(instruction(5, KernelOpcode.REQUANT, dst=2, src0=0, src1=1, aux=2 << 6, immediate=2), registers, ENTRY) + assert read_words(memory, 0x1100, 2) == [1000, 0xFFFFFC18] + assert result.cycles == 16 + + write_words(memory, 0x1200, [3, 4]) + write_words(memory, 0x1240, [1, 2]) + write_words(memory, 0x1280, [0x240]) + registers[0], registers[1], registers[2] = 0x200, 0x280, 0x300 + result = bam.execute(instruction(5, KernelOpcode.STEREO, dst=2, src0=0, src1=1, aux=1, immediate=2), registers, ENTRY) + assert read_words(memory, 0x1300, 4) == [3, 4, 2, 2] + assert result.writes[2] == 4 + + write_words(memory, 0x1400, [1, 1]) + write_words(memory, 0x1440, [2, 1]) + write_words(memory, 0x1480, [0x440]) + registers[0], registers[1], registers[2] = 0x400, 0x480, 0x500 + bam.execute(instruction(5, KernelOpcode.FIXED, dst=2, src0=0, src1=1, aux=2, immediate=2), registers, ENTRY) + assert read_words(memory, 0x1500, 2) == [4, 7] + + write_words(memory, 0x1600, [1, 1]) + write_words(memory, 0x1640, [1, 1]) + write_words(memory, 0x1680, [1, 0]) + write_words(memory, 0x16C0, [0x640, 0x680, 0]) + registers[0], registers[1], registers[2] = 0x600, 0x6C0, 0x700 + bam.execute(instruction(5, KernelOpcode.LPC, dst=2, src0=0, src1=1, aux=2, immediate=2), registers, ENTRY) + assert read_words(memory, 0x1700, 2) == [2, 4] + + write_words(memory, 0x1800, [100, -100]) + write_words(memory, 0x1840, [1 << 30, 2]) + registers[0], registers[1], registers[2] = 0x800, 0x840, 0x900 + result = bam.execute(instruction(5, KernelOpcode.PCM_PACK, dst=2, src0=0, src1=1, aux=0, immediate=1), registers, ENTRY) + assert memory.data[0x1900:0x1904] == struct.pack(" None: + memory = LocalMemory() + memory.publish_table(b"") + memory.new_epoch() + bam = PrimitiveBam(memory) + registers = [0] * 16 + registers[misaligned_register] = 1 + with pytest.raises(PrimitiveFault) as caught: + bam.execute( + instruction(5, opcode, dst=2, src0=0, src1=1, immediate=1), + registers, + ENTRY, + ) + assert (caught.value.code, caught.value.stage, caught.value.reason) == (11, 11, 5) + + +def test_kernel_reference_rejects_misalignment_before_output() -> None: + memory = LocalMemory() + memory.publish_table(b"") + memory.new_epoch() + bam = PrimitiveBam(memory) + registers = [0] * 16 + write_words(memory, 0x1000, [100]) + write_words(memory, 0x1100, [1]) + registers[0], registers[1], registers[2] = 0, 0x100, 0x200 + with pytest.raises(PrimitiveFault) as caught: + bam.execute( + instruction(5, KernelOpcode.REQUANT, dst=2, src0=0, src1=1, immediate=1), + registers, + ENTRY, + ) + assert (caught.value.code, caught.value.stage, caught.value.reason) == (11, 11, 5) + + +def test_interpreter_models_pending_kernel_scoreboard_and_abort() -> None: + program = [ + instruction(5, KernelOpcode.REQUANT, dst=2, src0=0, src1=1, aux=2 << 6, immediate=1), + instruction(1, 1, dst=3, immediate=0x55), + instruction(1, 0, dst=4, src0=2), + instruction(0, ControlOpcode.WAIT, aux=1), + instruction(0, ControlOpcode.END), + ] + entry = Entry(0, 0, 0, 4, 1, 32, 0x1000, 0x1000, 0x41, 0, 0) + primitives = PrimitiveBam() + machine = Machine( + program, + entry, + timeout=64, + fetch_no_retirement_cycles=0, + target="p4", + primitives=primitives, + ) + write_words(primitives.memory, 0x1000, [1000]) + write_words(primitives.memory, 0x1040, [1 << 30]) + write_words(primitives.memory, 0x1080, [0x40]) + machine.registers[0], machine.registers[1], machine.registers[2] = 0, 0x80, 0x100 + + assert not machine.step() + assert machine.retired == 1 + assert machine.kernel_pending_dst == 2 + assert machine.registers[2] == 0x100 + assert not machine.step() + assert machine.registers[3] == 0x55 + retired_before = machine.retired + assert not machine.step() + assert machine.retired == retired_before + while machine.kernel_pending_dst is not None: + assert not machine.step() + assert machine.registers[2] == 1 + assert machine.step() is False + assert machine.registers[4] == 1 + + machine.begin_entry(entry) + write_words(primitives.memory, 0x1000, [1000]) + write_words(primitives.memory, 0x1040, [1 << 30]) + write_words(primitives.memory, 0x1080, [0x40]) + machine.registers[0], machine.registers[1], machine.registers[2] = 0, 0x80, 0x100 + assert not machine.step() + machine.abort() + assert machine.kernel_pending_dst is None + assert not primitives.kernel_done + assert not primitives.memory.valid_words[0x1000 // 4] + + +def test_interpreter_pending_kernel_watchdog_and_error_boundary() -> None: + requant_program = [ + instruction(5, KernelOpcode.REQUANT, dst=2, src0=0, src1=1, aux=2 << 6, immediate=1), + instruction(1, 0, dst=4, src0=2), + instruction(0, ControlOpcode.END), + ] + entry = Entry(0, 0, 0, 2, 1, 16, 0x1000, 0x1000, 0x40, 0, 0) + primitives = PrimitiveBam() + machine = Machine( + requant_program, + entry, + timeout=1, + fetch_no_retirement_cycles=0, + target="p4", + primitives=primitives, + ) + write_words(primitives.memory, 0x1000, [1000]) + write_words(primitives.memory, 0x1040, [1 << 30]) + write_words(primitives.memory, 0x1080, [0x40]) + machine.registers[0], machine.registers[1], machine.registers[2] = 0, 0x80, 0x100 + assert not machine.step() + with pytest.raises(SequencerTrap) as caught: + machine.step() + assert caught.value.reason == 7 + + error_program = [ + instruction(5, KernelOpcode.DECORRELATE, dst=2, src0=0, src1=1, aux=1, immediate=1), + instruction(1, 1, dst=3, immediate=0x55), + instruction(0, ControlOpcode.END), + ] + primitives = PrimitiveBam() + machine = Machine( + error_program, + entry, + timeout=16, + fetch_no_retirement_cycles=0, + target="p4", + primitives=primitives, + ) + write_words(primitives.memory, 0x1000, [0x7FFFFFFF]) + write_words(primitives.memory, 0x1040, [0xFFFFFFFF]) + write_words(primitives.memory, 0x1080, [0x40]) + machine.registers[0], machine.registers[1], machine.registers[2] = 0, 0x80, 0x100 + assert not machine.step() + assert machine.retired == 1 + with pytest.raises(SequencerTrap) as caught: + machine.step() + assert caught.value.reason == 9 + + +def test_transform_and_resampler_profiles() -> None: + memory = LocalMemory() + table = bytearray(0x1000) + for index in range(72): + struct.pack_into(" None: + memory = LocalMemory() + table = bytearray(0x1800) + for row in range(32): + struct.pack_into(" None: + memory = LocalMemory() + memory.publish_table(bytes(0x1800)) + memory.new_epoch() + entry = Entry(0, 0, 0, 0, 1, 20000, 0x2000, 0x4000, 0xFFFF, 0, 0x1800) + bam = PrimitiveBam(memory) + registers = [0] * 16 + write_words(memory, 0x20E4, [0] * 17) + write_words( + memory, + 0x2400, + [0x80000000, 0x80000800, 0x80001000, 0, 0, 0, 0, 1], + ) + registers[0], registers[1], registers[2] = 0x100, 0x400, 0x800 + result = bam.execute( + instruction(5, KernelOpcode.RESAMPLE, dst=2, src0=0, src1=1, aux=profile, immediate=1), + registers, + entry, + ) + expected_frames = (numerator + denominator - 1) // denominator + assert result.writes[2] == expected_frames + expected_cycles = 10 if profile == 0 else 10 + 36 * expected_frames + assert result.cycles == expected_cycles + assert read_words(memory, 0x2800, expected_frames) == [0] * expected_frames + + +def _p4_rtl_sources() -> list[Path]: + multimedia = ROOT / "rtl/ip/multimedia" + return [ + ROOT / "rtl/managed/clusterip/common/rtl/utils/fifo.sv", + multimedia / "apu_local_sram.sv", + multimedia / "apu_bitstream_engine.sv", + multimedia / "apu_entropy_engine.sv", + multimedia / "apu_reconstruction_engine.sv", + multimedia / "apu_transform_engine.sv", + multimedia / "apu_resampler.sv", + multimedia / "apu_kernel_engine.sv", + multimedia / "apu_primitive_dispatcher.sv", + ROOT / "tests/rtl/apu_p4_primitives_tb.sv", + ] + + +def _requant_corpus(tmp_path: Path) -> tuple[Path, int]: + random_source = random.Random(0xA4C0DE) + cases = [ + (-0x80000000, -0x80000000, 0x80), + (-0x80000000, 0x7FFFFFFF, 0xA0), + (0x7FFFFFFF, -0x80000000, 0x41), + (0x7FFFFFFF, 0x7FFFFFFF, 0x61), + (1, 1 << 29, 0xA0), + (3, 1 << 29, 0xA0), + (-1, 1 << 29, 0xA0), + (-3, 1 << 29, 0xA0), + ] + for _ in range(24): + sample = random_source.randint(-0x80000000, 0x7FFFFFFF) + scale = random_source.randint(-0x80000000, 0x7FFFFFFF) + shift = random_source.randrange(32) + rounding = random_source.randrange(2) + width = random_source.randrange(3) + cases.append((sample, scale, shift | (rounding << 5) | (width << 6))) + + words: list[int] = [] + entry = Entry(0, 0, 0, 0, 1, 1000, 0x2000, 0x4000, 0x40, 0, 0) + for sample, scale, aux in cases: + memory = LocalMemory() + memory.publish_table(b"") + memory.new_epoch() + bam = PrimitiveBam(memory) + registers = [0] * 16 + write_words(memory, 0x2E00, [sample]) + write_words(memory, 0x2E40, [scale]) + write_words(memory, 0x2E80, [0xE40]) + registers[0], registers[1], registers[2] = 0xE00, 0xE80, 0xEC0 + bam.execute( + instruction( + 5, + KernelOpcode.REQUANT, + dst=2, + src0=0, + src1=1, + aux=aux, + immediate=1, + ), + registers, + entry, + ) + expected = read_words(memory, 0x2EC0, 1)[0] + words.extend((sample & 0xFFFFFFFF, scale & 0xFFFFFFFF, aux, expected)) + + vector_path = tmp_path / "apu_p4_requant_vectors.hex" + vector_path.write_text("\n".join(f"{word:08x}" for word in words) + "\n", encoding="utf-8") + return vector_path, len(cases) + + +def _fifo_word(payload: bytes, *, last: bool = True) -> int: + if not 1 <= len(payload) <= 4: + raise ValueError("P4 corpus FIFO payload must contain one to four bytes") + return int.from_bytes(payload.ljust(4, b"\0"), "little") | (len(payload) << 32) | ( + int(last) << 40 + ) + + +class _SharedCorpus: + def __init__(self) -> None: + self.lines: list[str] = [] + self.covered: set[tuple[int, int]] = set() + self.case_index = 0 + + @staticmethod + def _word(value: int) -> str: + return f"32'h{value & 0xFFFFFFFF:08x}" + + def add( + self, + name: str, + operations: list[Instruction], + *, + registers: dict[int, int] | None = None, + table_words: dict[int, int] | None = None, + scratch_words: dict[int, int] | None = None, + input_words: list[int] | None = None, + check_words: list[int] | None = None, + check_output_fifo: bool = False, + ) -> None: + registers = {} if registers is None else registers + table_words = {} if table_words is None else table_words + scratch_words = {} if scratch_words is None else scratch_words + input_words = [] if input_words is None else input_words + check_words = [] if check_words is None else check_words + + table = bytearray(0x1800) + for address, value in table_words.items(): + struct.pack_into(" int | None: + instruction_class = int(operation.instruction_class) + opcode = int(operation.opcode) + if instruction_class == InstructionClass.BITSTREAM: + if opcode in (BitstreamOpcode.PEEK, BitstreamOpcode.GET, BitstreamOpcode.SKIP): + return 1 + if opcode == BitstreamOpcode.FRAME_SYNC: + return 2 + 2 * operation.immediate + if instruction_class == InstructionClass.ENTROPY: + if opcode == EntropyOpcode.UNARY: + return 2 + operation.immediate + if opcode == EntropyOpcode.SIGN_RESTORE: + return 3 + (registers[operation.src1] & 0x1F) + if instruction_class == InstructionClass.LOCAL: + return 2 if opcode in ( + LocalOpcode.LD32, + LocalOpcode.TABLE8, + LocalOpcode.TABLE16, + LocalOpcode.TABLE32, + ) else 1 + return None + + def _append_stream_state_checks(self, name: str, bam: PrimitiveBam) -> None: + self.lines.extend( + ( + " @(negedge clk_i);", + f" if (input_count != 7'd{len(bam.input_fifo.words)} || " + f"output_count != 7'd{len(bam.output_fifo.words)}) " + f"$fatal(1, \"shared corpus {name} FIFO mutation mismatch\");", + f" if (u_dut.s_available_bits != 7'd{len(bam.bits)} || " + f"u_dut.u_bitstream_engine.s_eof_q != 1'b{int(bam.eof)} || " + f"u_dut.u_bitstream_engine.s_bit_position_q != 3'd{bam.bit_position}) " + f"$fatal(1, \"shared corpus {name} reservoir mutation mismatch " + f"got=%0d/%0d/%0d expected={len(bam.bits)}/{int(bam.eof)}/" + f"{bam.bit_position}\", u_dut.s_available_bits, " + "u_dut.u_bitstream_engine.s_eof_q, " + "u_dut.u_bitstream_engine.s_bit_position_q);", + ) + ) + + def write(self, path: Path) -> None: + path.write_text("\n".join(self.lines) + "\n", encoding="utf-8") + + +RTL_CORPUS_ENTRY = Entry(0, 0, 0, 0, 1, 0xFFFFFF, 0x2000, 0x4000, 0xFFFF, 0, 0x1800) + + +def _word_map(base: int, values: list[int]) -> dict[int, int]: + return {base + index * 4: value for index, value in enumerate(values)} + + +def _shared_primitive_corpus(tmp_path: Path) -> tuple[Path, int]: + random_source = random.Random(0x5044434F) + corpus = _SharedCorpus() + + random_word = random_source.getrandbits(32) + input_word = _fifo_word(random_word.to_bytes(4, "little")) + corpus.add("refill_boundary", [instruction(2, BitstreamOpcode.REFILL, dst=0, immediate=32)], input_words=[input_word]) + corpus.add("peek_random", [instruction(2, BitstreamOpcode.PEEK, dst=1, immediate=32)], input_words=[input_word]) + corpus.add("get_random", [instruction(2, BitstreamOpcode.GET, dst=1, immediate=32)], input_words=[input_word]) + corpus.add("skip_boundary", [instruction(2, BitstreamOpcode.SKIP, immediate=32)], input_words=[input_word]) + corpus.add( + "align_after_partial_get", + [ + instruction(2, BitstreamOpcode.GET, dst=0, immediate=3), + instruction(2, BitstreamOpcode.ALIGN), + ], + input_words=[input_word], + ) + sync_prefix = random_source.randrange(0x100) + corpus.add( + "frame_sync_masked", + [instruction(2, BitstreamOpcode.FRAME_SYNC, dst=4, src0=2, src1=3, aux=16, immediate=3)], + registers={2: 0xABCD, 3: 0xFFFF}, + input_words=[_fifo_word(bytes((sync_prefix, 0xAB, 0xCD)))], + ) + corpus.add( + "crc8_random", + [instruction(2, BitstreamOpcode.CRC8, dst=7, src0=5, src1=6)], + registers={5: random_source.randrange(0x100), 6: 0xFF}, + ) + corpus.add( + "crc16_random", + [instruction(2, BitstreamOpcode.CRC16, dst=7, src0=5, src1=6)], + registers={5: random_source.randrange(0x10000), 6: 0xFF}, + ) + for opcode in (BitstreamOpcode.PEEK, BitstreamOpcode.GET, BitstreamOpcode.SKIP): + corpus.add( + f"{opcode.name.lower()}_truncated", + [instruction(2, opcode, dst=1 if opcode != BitstreamOpcode.SKIP else 0, immediate=16)], + input_words=[_fifo_word(bytes((random_source.randrange(0x100),)))], + ) + corpus.add( + "frame_sync_not_found", + [instruction(2, BitstreamOpcode.FRAME_SYNC, dst=4, src0=2, src1=3, aux=8, immediate=1)], + registers={2: 0xFF, 3: 0xFF}, + input_words=[_fifo_word(b"\x00")], + ) + + for opcode in ( + EntropyOpcode.HUFF_SYMBOL, + EntropyOpcode.HUFF_PAIR, + EntropyOpcode.HUFF_QUAD, + ): + symbol = random_source.getrandbits(16) + corpus.add( + f"{opcode.name.lower()}_canonical", + [instruction(3, opcode, dst=4, src0=0, src1=1, aux=1)], + registers={0: 0, 1: 1}, + table_words={0: symbol | (1 << 16)}, + input_words=[_fifo_word(bytes((random_source.randrange(0x80),)))], + ) + corpus.add( + f"{opcode.name.lower()}_empty_table", + [instruction(3, opcode, dst=4, src0=0, src1=1, aux=1)], + registers={0: 0, 1: 0}, + ) + corpus.add( + "unary_boundary", + [instruction(3, EntropyOpcode.UNARY, dst=2, aux=0, immediate=5)], + input_words=[_fifo_word(bytes((0x04 | random_source.randrange(4),)))], + ) + corpus.add( + "rice4_random", + [instruction(3, EntropyOpcode.RICE4, dst=2, src0=0, src1=1)], + registers={0: 2}, + input_words=[_fifo_word(bytes((0x28 | random_source.randrange(8),)))], + ) + corpus.add( + "rice5_random", + [instruction(3, EntropyOpcode.RICE5, dst=2, src0=0, src1=1)], + registers={0: 3}, + input_words=[_fifo_word(bytes((0x68 | random_source.randrange(8),)))], + ) + corpus.add( + "sign_restore_random", + [instruction(3, EntropyOpcode.SIGN_RESTORE, dst=2, src0=0, src1=1)], + registers={0: 0x7FFFFFF8, 1: 3}, + input_words=[_fifo_word(bytes((0xB0 | random_source.randrange(16),)))], + ) + for opcode, escape in ((EntropyOpcode.RICE4, 15), (EntropyOpcode.RICE5, 31)): + corpus.add( + f"{opcode.name.lower()}_escape_extreme", + [instruction(3, opcode, dst=2, src0=0, src1=1)], + registers={0: escape, 1: 32}, + input_words=[_fifo_word(b"\x80\x00\x00\x00")], + ) + corpus.add( + "unary_declared_limit", + [instruction(3, EntropyOpcode.UNARY, dst=2, aux=0, immediate=1)], + input_words=[_fifo_word(b"\x00")], + ) + for opcode, escape in ((EntropyOpcode.RICE4, 15), (EntropyOpcode.RICE5, 31)): + corpus.add( + f"{opcode.name.lower()}_invalid_escape_width", + [instruction(3, opcode, dst=2, src0=0, src1=1)], + registers={0: escape, 1: 0}, + ) + corpus.add( + "sign_restore_truncated", + [instruction(3, EntropyOpcode.SIGN_RESTORE, dst=2, src0=0, src1=1)], + registers={0: 7, 1: 8}, + input_words=[_fifo_word(b"\xa5")], + ) + + local_value = random_source.getrandbits(32) + corpus.add( + "local_load_boundary", + [instruction(4, LocalOpcode.LD32, dst=2, src0=0)], + registers={0: 0x3FFC}, + scratch_words={0x5FFC: local_value}, + ) + corpus.add( + "local_store_boundary", + [instruction(4, LocalOpcode.ST32, src0=0, src1=1)], + registers={0: 0x3FFC, 1: local_value}, + check_words=[0x5FFC], + ) + table_value = random_source.getrandbits(32) + for opcode, index, expected_address in ( + (LocalOpcode.TABLE8, 3, 0x103), + (LocalOpcode.TABLE16, 1, 0x102), + (LocalOpcode.TABLE32, 0, 0x100), + ): + corpus.add( + f"{opcode.name.lower()}_random", + [instruction(4, opcode, dst=2, src0=0, src1=1)], + registers={0: 0x100, 1: index}, + table_words={0x100: table_value}, + ) + corpus.add( + f"{opcode.name.lower()}_range_fault", + [instruction(4, opcode, dst=2, src0=0, src1=1)], + registers={0: 0x1800, 1: index}, + ) + assert expected_address < 0x1800 + corpus.add( + "fifo_pop_boundary", + [instruction(4, LocalOpcode.FIFO_POP, dst=4)], + input_words=[_fifo_word(random_source.getrandbits(32).to_bytes(4, "little"))], + ) + corpus.add( + "fifo_push_random", + [instruction(4, LocalOpcode.FIFO_PUSH, src0=4, src1=5)], + registers={4: random_source.getrandbits(32), 5: 0x104}, + check_output_fifo=True, + ) + corpus.add( + "local_load_range_fault", + [instruction(4, LocalOpcode.LD32, dst=2, src0=0)], + registers={0: 0x4000}, + ) + corpus.add( + "local_store_range_fault", + [instruction(4, LocalOpcode.ST32, src0=0, src1=1)], + registers={0: 0x4000, 1: local_value}, + ) + corpus.add( + "local_load_address_overflow", + [instruction(4, LocalOpcode.LD32, dst=2, src0=0, immediate=1)], + registers={0: 0xFFFFFFFF}, + ) + corpus.add( + "local_store_address_overflow", + [instruction(4, LocalOpcode.ST32, src0=0, src1=1, immediate=1)], + registers={0: 0xFFFFFFFF, 1: local_value}, + ) + corpus.add( + "table8_address_overflow", + [instruction(4, LocalOpcode.TABLE8, dst=2, src0=0, src1=1)], + registers={0: 0xFFFFFFFF, 1: 0x2001}, + ) + corpus.add( + "table16_scaled_address_overflow", + [instruction(4, LocalOpcode.TABLE16, dst=2, src0=0, src1=1)], + registers={0: 2, 1: 0xFFFFFFFF}, + ) + corpus.add( + "table32_scaled_address_overflow", + [instruction(4, LocalOpcode.TABLE32, dst=2, src0=0, src1=1)], + registers={0: 0, 1: 0x80000000}, + ) + corpus.add( + "fifo_push_metadata_fault", + [instruction(4, LocalOpcode.FIFO_PUSH, src0=4, src1=5)], + registers={4: local_value, 5: 0}, + ) + + sample0 = random_source.randint(-0x80000000, 0x7FFFFFFF) + sample1 = random_source.randint(-0x80000000, 0x7FFFFFFF) + corpus.add( + "requant_shared_extreme", + [instruction(5, KernelOpcode.REQUANT, dst=2, src0=0, src1=1, aux=0xA0, immediate=2)], + registers={0: 0x100, 1: 0x180, 2: 0x200}, + scratch_words={ + **_word_map(0x2100, [sample0, -0x80000000]), + **_word_map(0x2180, [0x300]), + **_word_map(0x2300, [1 << 29, 0x7FFFFFFF]), + }, + check_words=[0x2200, 0x2204], + ) + corpus.add( + "stereo_shared_extreme", + [instruction(5, KernelOpcode.STEREO, dst=2, src0=0, src1=1, aux=2, immediate=2)], + registers={0: 0x100, 1: 0x180, 2: 0x200}, + scratch_words={ + **_word_map(0x2100, [sample0, 0x7FFFFFFF]), + **_word_map(0x2180, [0x300]), + **_word_map(0x2300, [sample1, 1]), + }, + check_words=[0x2200, 0x2204, 0x2208, 0x220C], + ) + + imdct6_matrix = [0] * 72 + for index in range(6): + imdct6_matrix[index * 6 + index] = 1 << 30 + corpus.add( + "imdct6_shared_extreme", + [instruction(5, KernelOpcode.IMDCT6, dst=2, src0=0, src1=1, immediate=1)], + registers={0: 0x100, 1: 0x180, 2: 0x200}, + table_words=_word_map(0, imdct6_matrix), + scratch_words={ + **_word_map(0x2100, [sample0, sample1, -0x80000000, 0x7FFFFFFF, -1, 1]), + **_word_map(0x2180, [0x80000000]), + }, + check_words=[0x2200 + index * 4 for index in range(12)], + ) + + imdct18_matrix = [0] * 648 + for index in range(18): + imdct18_matrix[index * 18 + index] = 1 << 30 + imdct18_inputs = [ + -0x80000000, + 0x7FFFFFFF, + *(random_source.randint(-0x80000000, 0x7FFFFFFF) for _ in range(16)), + ] + corpus.add( + "imdct18_shared_random", + [instruction(5, KernelOpcode.IMDCT18, dst=2, src0=0, src1=1, immediate=1)], + registers={0: 0x100, 1: 0x180, 2: 0x200}, + table_words=_word_map(0x200, imdct18_matrix), + scratch_words={ + **_word_map(0x2100, imdct18_inputs), + **_word_map(0x2180, [0x80000200]), + }, + check_words=[0x2200 + index * 4 for index in range(36)], + ) + + dct_matrix = [0] * 1024 + dct_window = [0] * 512 + for index in range(32): + dct_matrix[index * 32 + index] = 1 << 30 + dct_window[index] = 1 << 30 + dct_inputs = [ + -0x80000000, + 0x7FFFFFFF, + *(random_source.randint(-0x100000, 0x100000) for _ in range(30)), + ] + corpus.add( + "dct32_shared_random", + [instruction(5, KernelOpcode.DCT32_POLY, dst=2, src0=0, src1=1, immediate=1)], + registers={0: 0x100, 1: 0x180, 2: 0x200}, + table_words={**_word_map(0, dct_matrix), **_word_map(0x1000, dct_window)}, + scratch_words={ + **_word_map(0x2100, dct_inputs), + **_word_map(0x2180, [0x80000000, 0x80001000, 0x300, 0]), + **_word_map(0x2300, [0] * 512), + }, + check_words=[0x2200 + index * 4 for index in range(32)] + [0x218C], + ) + + corpus.add( + "fixed_shared_random", + [instruction(5, KernelOpcode.FIXED, dst=2, src0=0, src1=1, aux=0, immediate=3)], + registers={0: 0x100, 1: 0x180, 2: 0x200}, + scratch_words={ + **_word_map(0x2100, [-0x80000000, 0x7FFFFFFF, sample0]), + **_word_map(0x2180, [0x300]), + **_word_map(0x2300, [random_source.getrandbits(32)]), + }, + check_words=[0x2200, 0x2204, 0x2208], + ) + corpus.add( + "fixed_shared_history_retention", + [ + instruction(5, KernelOpcode.FIXED, dst=2, src0=0, src1=1, aux=2, immediate=2), + instruction(5, KernelOpcode.FIXED, dst=4, src0=3, src1=1, aux=2, immediate=2), + ], + registers={0: 0x100, 1: 0x180, 2: 0x200, 3: 0x400, 4: 0x500}, + scratch_words={ + **_word_map(0x2100, [13, -7]), + **_word_map(0x2180, [0x300]), + **_word_map(0x2300, [5, -3]), + **_word_map(0x2400, [1, -2]), + }, + check_words=[0x2200, 0x2204, 0x2300, 0x2304, 0x2500, 0x2504], + ) + corpus.add( + "lpc_shared_random", + [instruction(5, KernelOpcode.LPC, dst=2, src0=0, src1=1, aux=2, immediate=2)], + registers={0: 0x100, 1: 0x180, 2: 0x200}, + table_words=_word_map(0x1400, [1, -1]), + scratch_words={ + **_word_map(0x2100, [sample0 >> 4, sample1 >> 4]), + **_word_map(0x2180, [0x80001400, 0x300, 0]), + **_word_map(0x2300, [5, -3]), + }, + check_words=[0x2200, 0x2204, 0x2300, 0x2304], + ) + corpus.add( + "lpc_shared_extreme", + [instruction(5, KernelOpcode.LPC, dst=2, src0=0, src1=1, aux=2, immediate=2)], + registers={0: 0x100, 1: 0x180, 2: 0x200}, + table_words=_word_map(0x1400, [0, 0]), + scratch_words={ + **_word_map(0x2100, [-0x80000000, 0x7FFFFFFF]), + **_word_map(0x2180, [0x80001400, 0x300, 0]), + **_word_map(0x2300, [sample0, sample1]), + }, + check_words=[0x2200, 0x2204, 0x2300, 0x2304], + ) + corpus.add( + "decorrelate_shared_extreme", + [instruction(5, KernelOpcode.DECORRELATE, dst=2, src0=0, src1=1, aux=1, immediate=3)], + registers={0: 0x100, 1: 0x180, 2: 0x200}, + scratch_words={ + **_word_map(0x2100, [-0x80000000, 0x7FFFFFFF, sample0]), + **_word_map(0x2180, [0x300]), + **_word_map(0x2300, [0, 0, 0]), + }, + check_words=[0x2200 + index * 4 for index in range(6)], + ) + + resample_coefficients = [0] * 512 + for phase in range(32): + resample_coefficients[phase * 16 + 7] = 1 << 30 + corpus.add( + "resample_shared_profile3", + [instruction(5, KernelOpcode.RESAMPLE, dst=2, src0=0, src1=1, aux=3, immediate=1)], + registers={0: 0x100, 1: 0x180, 2: 0x200}, + table_words=_word_map(0, resample_coefficients), + scratch_words={ + **_word_map( + 0x20E4, + [0] * 7 + + [-0x80000000] + + [random_source.randint(-1000, 1000) for _ in range(9)], + ), + **_word_map(0x2180, [0x80000000, 0x80000800, 0x80001000, 0, 0, 0, 0, 1]), + }, + check_words=[0x2200, 0x2204, 0x218C, 0x2190, 0x2194, 0x2198], + ) + corpus.add( + "pcm_pack_shared_extreme", + [instruction(5, KernelOpcode.PCM_PACK, dst=2, src0=0, src1=1, aux=0, immediate=2)], + registers={0: 0x100, 1: 0x180, 2: 0x200}, + scratch_words={ + **_word_map(0x2100, [-0x80000000, 0x7FFFFFFF, sample0, sample1]), + **_word_map(0x2180, [1 << 30, 2]), + }, + check_words=[0x2200, 0x2204], + ) + + parameter_words = { + KernelOpcode.REQUANT: [0], + KernelOpcode.STEREO: [0], + KernelOpcode.IMDCT6: [0], + KernelOpcode.IMDCT18: [0], + KernelOpcode.DCT32_POLY: [0, 0, 0, 0], + KernelOpcode.FIXED: [0], + KernelOpcode.LPC: [0, 0, 0], + KernelOpcode.DECORRELATE: [0], + KernelOpcode.RESAMPLE: [0, 0, 0, 0, 0, 0, 0, 1], + KernelOpcode.PCM_PACK: [1 << 30, 1], + } + fault_aux = { + KernelOpcode.REQUANT: 0x80, + KernelOpcode.STEREO: 0, + KernelOpcode.IMDCT6: 0, + KernelOpcode.IMDCT18: 0, + KernelOpcode.DCT32_POLY: 0, + KernelOpcode.FIXED: 2, + KernelOpcode.LPC: 2, + KernelOpcode.DECORRELATE: 0, + KernelOpcode.RESAMPLE: 0, + KernelOpcode.PCM_PACK: 0, + } + for opcode in KernelOpcode: + corpus.add( + f"{opcode.name.lower()}_alignment_fault", + [instruction(5, opcode, dst=2, src0=0, src1=1, aux=fault_aux[opcode], immediate=1)], + registers={0: 0x101, 1: 0x180, 2: 0x200}, + scratch_words=_word_map(0x2180, parameter_words[opcode]), + ) + + expected_opcodes = { + *((2, int(opcode)) for opcode in BitstreamOpcode), + *((3, int(opcode)) for opcode in EntropyOpcode), + *((4, int(opcode)) for opcode in LocalOpcode), + *((5, int(opcode)) for opcode in KernelOpcode), + } + assert corpus.covered == expected_opcodes + path = tmp_path / "apu_p4_corpus_generated.svh" + corpus.write(path) + return path, corpus.case_index + + +def test_p4_production_primitives_icarus_and_verilator(tmp_path: Path) -> None: + iverilog = shutil.which("iverilog") + vvp = shutil.which("vvp") + sv2v = shutil.which("sv2v") + verilator = shutil.which("verilator") + sources = _p4_rtl_sources() + include = ROOT / "rtl/ip/multimedia" + vector_path, vector_count = _requant_corpus(tmp_path) + shared_corpus_path, shared_case_count = _shared_primitive_corpus(tmp_path) + assert shared_case_count >= 32 + assert shared_corpus_path.is_file() + + if iverilog is not None and vvp is not None and sv2v is not None: + source_list = tmp_path / "apu_p4_primitives.fl" + source_list.write_text( + "\n".join( + [ + "+define+APU_P4_SHARED_CORPUS", + f"+incdir+{include}", + f"+incdir+{tmp_path}", + *(str(path) for path in sources), + "", + ] + ), + encoding="utf-8", + ) + converted = tmp_path / "apu_p4_primitives.v" + subprocess.run( + [ + sys.executable, + str(ROOT / "rtl/mini/script/convt_sv2v.py"), + "-f", + str(source_list), + "--output", + str(converted), + ], + check=True, + ) + simulation = tmp_path / "apu_p4_iverilog" + subprocess.run( + [iverilog, "-g2012", "-s", "apu_p4_primitives_tb", "-o", str(simulation), str(converted)], + check=True, + ) + result = subprocess.run( + [vvp, str(simulation), f"+VECTORS={vector_path}", f"+VECTOR_COUNT={vector_count}"], + check=True, + text=True, + capture_output=True, + ) + assert "APU_P4_PRIMITIVES_PASS" in result.stdout + + if verilator is not None: + object_dir = tmp_path / "verilator_obj" + ccache_dir = tmp_path / "ccache" + ccache_dir.mkdir() + subprocess.run( + [ + verilator, + "--binary", + "--timing", + "-Wno-fatal", + "--top-module", + "apu_p4_primitives_tb", + "-DAPU_P4_SHARED_CORPUS", + f"-I{include}", + f"-I{tmp_path}", + "-Mdir", + str(object_dir), + "-o", + "simv", + *(str(path) for path in sources), + ], + check=True, + text=True, + capture_output=True, + env={ + **os.environ, + "CCACHE_DIR": str(ccache_dir), + "CCACHE_TEMPDIR": str(ccache_dir), + }, + ) + result = subprocess.run( + [ + str(object_dir / "simv"), + f"+VECTORS={vector_path}", + f"+VECTOR_COUNT={vector_count}", + ], + check=True, + text=True, + capture_output=True, + ) + assert "APU_P4_PRIMITIVES_PASS" in result.stdout + + +def test_p4_loader_publishes_control_and_table_atomically(tmp_path: Path) -> None: + iverilog = shutil.which("iverilog") + vvp = shutil.which("vvp") + sv2v = shutil.which("sv2v") + if iverilog is None or vvp is None or sv2v is None: + return + + program = [ + instruction(InstructionClass.CONTROL, ControlOpcode.WAIT, aux=1), + instruction(InstructionClass.CONTROL, ControlOpcode.END), + ] + entries = [ + Entry(index, 0, 0, 1, 1, 2, 0x1000, 0x1000, 0x40, 0, 4) + for index in range(3) + ] + bundle = build_apumc( + program, + entries, + build_id=0x1234, + target="p4", + table_payload=struct.pack(" None: + iverilog = shutil.which("iverilog") + vvp = shutil.which("vvp") + sv2v = shutil.which("sv2v") + if iverilog is None or vvp is None or sv2v is None: + return + multimedia = ROOT / "rtl/ip/multimedia" + sources = [ + ROOT / "rtl/managed/clusterip/common/rtl/utils/fifo.sv", + multimedia / "apu_microcode_pkg.sv", + multimedia / "apu_control_store.sv", + multimedia / "apu_local_sram.sv", + multimedia / "apu_bitstream_engine.sv", + multimedia / "apu_entropy_engine.sv", + multimedia / "apu_reconstruction_engine.sv", + multimedia / "apu_transform_engine.sv", + multimedia / "apu_resampler.sv", + multimedia / "apu_kernel_engine.sv", + multimedia / "apu_primitive_dispatcher.sv", + multimedia / "apu_codec_sequencer.sv", + ROOT / "tests/rtl/apu_p4_sequencer_tb.sv", + ] + source_list = tmp_path / "apu_p4_sequencer.fl" + source_list.write_text( + "\n".join([f"+incdir+{multimedia}", *(str(path) for path in sources), ""]), + encoding="utf-8", + ) + converted = tmp_path / "apu_p4_sequencer.v" + subprocess.run( + [ + sys.executable, + str(ROOT / "rtl/mini/script/convt_sv2v.py"), + "-f", + str(source_list), + "--output", + str(converted), + ], + check=True, + ) + simulation = tmp_path / "apu_p4_sequencer" + subprocess.run( + [iverilog, "-g2012", "-s", "apu_p4_sequencer_tb", "-o", str(simulation), str(converted)], + check=True, + ) + result = subprocess.run([vvp, str(simulation)], check=True, text=True, capture_output=True) + assert "APU_P4_SEQUENCER_PASS" in result.stdout diff --git a/tests/test_apu_register_parity.py b/tests/test_apu_register_parity.py index 1e69c254..c412060c 100644 --- a/tests/test_apu_register_parity.py +++ b/tests/test_apu_register_parity.py @@ -1,4 +1,4 @@ -"""Keep the handwritten APU RTL and SDK register ABI synchronized through P3.""" +"""Keep the handwritten APU RTL, SDK, and P4 ISA definitions synchronized.""" from __future__ import annotations @@ -72,7 +72,7 @@ def test_apu_register_matrix_covers_each_offset_once() -> None: assert set(covered) == set(expected) -def test_apu_p3_tool_isa_matches_handwritten_rtl_and_c() -> None: +def test_apu_p4_tool_isa_matches_handwritten_rtl_and_c() -> None: rtl = _rtl_values() manifest = abi_manifest() exact = { @@ -82,6 +82,10 @@ def test_apu_p3_tool_isa_matches_handwritten_rtl_and_c() -> None: "APUMC_ENTRY_BYTES": manifest["apumc"]["entry_bytes"], "APUMC_ENTRY_COUNT": manifest["apumc"]["entry_count"], "APUMC_MAX_INSTRUCTIONS": manifest["apumc"]["max_instructions"], + "APUMC_P3_PRIMITIVE_MASK": manifest["p3_implemented_primitive_mask"], + "APUMC_P4_PRIMITIVE_MASK": manifest["p4_implemented_primitive_mask"], + "LOCAL_DATA_BYTES": manifest["p4_local_data_bytes"], + "LOCAL_TABLE_SCRATCH_BYTES": manifest["p4_local_table_scratch_bytes"], } for name, value in manifest["classes"].items(): exact[f"MC_CLASS_{name.upper()}"] = value diff --git a/tests/test_script_tools.py b/tests/test_script_tools.py index 043589ed..048b1c63 100644 --- a/tests/test_script_tools.py +++ b/tests/test_script_tools.py @@ -330,6 +330,7 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non onchip_ram_filelist = tmp_path / "onchip_ram.fl" opipsram_filelist = tmp_path / "opipsram.fl" apu_filelist = tmp_path / "apu.fl" + apu_primitives_filelist = tmp_path / "apu_primitives.fl" apu_loader_filelist = tmp_path / "apu_loader.fl" apu_sequencer_filelist = tmp_path / "apu_sequencer.fl" gateway_a_filelist = tmp_path / "gateway_a.fl" @@ -359,6 +360,9 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non "opipsram", opipsram_filelist, memory_map, topology, user_extensions ) assert generate_formal_filelist("apu", apu_filelist, memory_map, topology, user_extensions) + assert generate_formal_filelist( + "apu_primitives", apu_primitives_filelist, memory_map, topology, user_extensions + ) assert generate_formal_filelist( "apu_loader", apu_loader_filelist, memory_map, topology, user_extensions ) @@ -381,6 +385,7 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non onchip_ram = parse_filelists([onchip_ram_filelist], require_files=False) opipsram = parse_filelists([opipsram_filelist], require_files=False) apu = parse_filelists([apu_filelist], require_files=False) + apu_primitives = parse_filelists([apu_primitives_filelist], require_files=False) apu_loader = parse_filelists([apu_loader_filelist], require_files=False) apu_sequencer = parse_filelists([apu_sequencer_filelist], require_files=False) gateway_a = parse_filelists([gateway_a_filelist], require_files=False) @@ -441,6 +446,22 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non assert ROOT / "rtl/mini/formal/opipsram_formal.sv" in opipsram.files assert ROOT / "rtl/ip/multimedia/apu_dma.sv" in apu.files assert ROOT / "rtl/mini/formal/apu_formal.sv" in apu.files + assert ROOT / "rtl/ip/multimedia/apu_resampler.sv" in apu_primitives.files + assert ROOT / "rtl/ip/multimedia/apu_local_sram.sv" in apu_primitives.files + assert ROOT / "rtl/ip/multimedia/apu_kernel_engine.sv" in apu_primitives.files + assert ROOT / "rtl/ip/multimedia/apu_primitive_dispatcher.sv" in apu_primitives.files + assert ROOT / "rtl/managed/clusterip/common/rtl/utils/fifo.sv" in apu_primitives.files + assert ROOT / "rtl/mini/formal/apu_primitives_formal.sv" in apu_primitives.files + formal_makefile = (ROOT / "rtl/mini/mk/formal.mk").read_text(encoding="utf-8") + for scenario in ( + "apu_primitives_invalid_read", + "apu_primitives_local", + "apu_primitives_kernel", + "apu_primitives_input_fifo", + "apu_primitives_alignment", + "apu_primitives_output_fifo", + ): + assert scenario in formal_makefile assert ROOT / "rtl/ip/multimedia/apu_microcode_loader.sv" in apu_loader.files assert ROOT / "rtl/mini/formal/apu_loader_formal.sv" in apu_loader.files assert ROOT / "rtl/ip/multimedia/apu_codec_sequencer.sv" in apu_sequencer.files