mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
feat: add RTL unit tests for NPU-P3 job execution
This commit introduces a comprehensive set of unit tests for the NPU-P3 job execution, covering the DMA engine, job decoder, and core functionalities. The tests utilize Icarus and Verilator for simulation and include descriptor validation, fault handling, and various operational scenarios. Key features include: - Descriptor construction and validation mirroring ABI 1.0. - Detailed test cases for valid and invalid descriptors. - Core testbench scenarios for various operations including convolution, pooling, and fully connected layers. - Integration of golden model comparisons for output verification. These tests aim to ensure robust functionality and adherence to specifications for the NPU-P3 architecture.
This commit is contained in:
20 files changed
+10304
-206
No files matched your search
@@ -1,18 +1,57 @@
|
||||
# NPU-P1 isolated core STA constraints: single 72 MHz HP clock at the
|
||||
# 1000000/72 ps target period, mirroring generate_sdc.py conventions.
|
||||
# NOTE: the locked OpenSTA build has no remove_from_collection/filter_collection,
|
||||
# so the input-delay exclusion of clock/reset ports is done by name iteration.
|
||||
create_clock -name clk_i -period 13.888889 [get_ports clk_i]
|
||||
set_clock_uncertainty -setup 0.2 [get_clocks clk_i]
|
||||
set_clock_uncertainty -hold 0.1 [get_clocks clk_i]
|
||||
set_clock_transition 0.1 [get_clocks clk_i]
|
||||
set_false_path -from [get_ports rst_n_i]
|
||||
# NPU isolated core STA constraints, mirroring generate_sdc.py conventions.
|
||||
# Single-clock targets (P1 compute and the P3 engine modules) run at the
|
||||
# 1000000/72 ps HP period regardless of the port's local name; the integrated
|
||||
# shell (apb4_npu) also carries PCLK at its 48 MHz platform period and the two
|
||||
# domains are grouped asynchronously. The locked OpenSTA build lacks
|
||||
# remove_from_collection/filter_collection, so ports are selected by name
|
||||
# iteration.
|
||||
set npu_clk_ports [list]
|
||||
set npu_rst_ports [list]
|
||||
set npu_io_inputs [list]
|
||||
foreach npu_port [all_inputs] {
|
||||
set npu_port_name [get_property $npu_port name]
|
||||
if {$npu_port_name ne "clk_i" && $npu_port_name ne "rst_n_i"} {
|
||||
if {$npu_port_name eq "clk_i" || $npu_port_name eq "clk_hp_i"} {
|
||||
lappend npu_clk_ports $npu_port
|
||||
} elseif {$npu_port_name eq "rst_n_i" || $npu_port_name eq "rst_hp_n_i"} {
|
||||
lappend npu_rst_ports $npu_port
|
||||
} else {
|
||||
lappend npu_io_inputs $npu_port
|
||||
}
|
||||
}
|
||||
set_input_delay -clock clk_i 2.0 $npu_io_inputs
|
||||
set_output_delay -clock clk_i 2.0 [all_outputs]
|
||||
set npu_hp_found 0
|
||||
set npu_pclk_found 0
|
||||
foreach npu_clk_port $npu_clk_ports {
|
||||
set npu_clk_name [get_property $npu_clk_port name]
|
||||
if {$npu_clk_name eq "clk_hp_i" || [llength $npu_clk_ports] == 1} {
|
||||
# The 72 MHz HP fabric period (13.888889 ns).
|
||||
create_clock -name npu_hp_clk -period 13.888889 $npu_clk_port
|
||||
set npu_hp_found 1
|
||||
} else {
|
||||
# PCLK at its 48 MHz platform period (20.833333 ns).
|
||||
create_clock -name npu_pclk -period 20.833333 $npu_clk_port
|
||||
set npu_pclk_found 1
|
||||
}
|
||||
}
|
||||
if {$npu_hp_found} {
|
||||
set_clock_uncertainty -setup 0.2 [get_clocks npu_hp_clk]
|
||||
set_clock_uncertainty -hold 0.1 [get_clocks npu_hp_clk]
|
||||
set_clock_transition 0.1 [get_clocks npu_hp_clk]
|
||||
}
|
||||
if {$npu_pclk_found} {
|
||||
set_clock_uncertainty -setup 0.2 [get_clocks npu_pclk]
|
||||
set_clock_uncertainty -hold 0.1 [get_clocks npu_pclk]
|
||||
set_clock_transition 0.1 [get_clocks npu_pclk]
|
||||
}
|
||||
if {$npu_hp_found && $npu_pclk_found} {
|
||||
set_clock_groups -asynchronous -group [get_clocks npu_hp_clk] -group [get_clocks npu_pclk]
|
||||
}
|
||||
if {[llength $npu_rst_ports] > 0} {
|
||||
set_false_path -from $npu_rst_ports
|
||||
}
|
||||
# Async-reset check arcs are not timed here: assertion/deassertion discipline
|
||||
# is provided by the Common reset synchronizers/barriers, and this locked
|
||||
# OpenSTA build does not cut RESET_B recovery arcs via set_clock_groups.
|
||||
# Physical CDC/RDC signoff remains a separate commercial-delivery gap.
|
||||
set_false_path -to [get_pins -hierarchical * -filter {name =~ */RESET_B}]
|
||||
set_input_delay -clock npu_hp_clk 2.0 $npu_io_inputs
|
||||
set_output_delay -clock npu_hp_clk 2.0 [all_outputs]
|
||||
+197
-74
@@ -1,12 +1,13 @@
|
||||
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
|
||||
// SPDX-License-Identifier: MulanPSL-2.0
|
||||
|
||||
// NPU Phase 2 top shell: the PCLK register file (npu_reg), the shell/HP
|
||||
// control CDC with epoch reconciliation (npu_control_cdc), and the minimal HP
|
||||
// lifecycle endpoint (npu_core). The private AXI4 master is tied safely idle:
|
||||
// no AW/W/AR request is ever issued, the R/B receivers stay ready as required
|
||||
// by the reserved-burst rule, and W never precedes AW by construction. The
|
||||
// production DMA that will drive this master is Phase 3 scope.
|
||||
// NPU top shell: the PCLK register file (npu_reg), the shell/HP control CDC
|
||||
// with epoch reconciliation (npu_control_cdc), the HP job controller
|
||||
// (npu_core), and the production AXI4 DMA engine (npu_dma) driving the private
|
||||
// master directly (HP-native, no payload CDC or width conversion). At Phase 3
|
||||
// the APB shell still rejects START (EXECUTION_READY stays 0), so the master
|
||||
// issues nothing from software; the engine runs only through the production
|
||||
// launch interface exercised by directed testbenches.
|
||||
`include "npu_define.svh"
|
||||
|
||||
module apb4_npu (
|
||||
@@ -74,7 +75,47 @@ module apb4_npu (
|
||||
logic s_hp_busy_pclk;
|
||||
logic s_hp_draining_pclk;
|
||||
logic s_clock_paused;
|
||||
logic s_abort_unused;
|
||||
// software ABORT (PCLK pulse) crosses into the HP domain as a toggle
|
||||
logic s_abort_toggle_d;
|
||||
logic s_abort_toggle_q;
|
||||
logic s_abort_toggle_hp;
|
||||
logic s_abort_hp_d;
|
||||
logic s_abort_hp_q;
|
||||
logic s_abort_hp_pulse;
|
||||
// discrete npu_dma engine interface of the job controller
|
||||
logic s_dma_clear;
|
||||
logic s_dma_read_req_valid;
|
||||
logic s_dma_read_req_ready;
|
||||
logic [ 31:0] s_dma_read_addr;
|
||||
logic [ 31:0] s_dma_read_bytes;
|
||||
logic s_dma_read_data_valid;
|
||||
logic s_dma_read_data_ready;
|
||||
logic [ 63:0] s_dma_read_data;
|
||||
logic [ 7:0] s_dma_read_keep;
|
||||
logic s_dma_read_last;
|
||||
logic s_dma_write_req_valid;
|
||||
logic s_dma_write_req_ready;
|
||||
logic [ 31:0] s_dma_write_addr;
|
||||
logic [ 31:0] s_dma_write_bytes;
|
||||
logic s_dma_write_data_valid;
|
||||
logic s_dma_write_data_ready;
|
||||
logic [ 63:0] s_dma_write_data;
|
||||
logic [ 7:0] s_dma_write_keep;
|
||||
logic s_dma_write_last;
|
||||
logic s_dma_write_done;
|
||||
logic s_dma_busy;
|
||||
logic s_dma_read_busy;
|
||||
logic s_dma_write_busy;
|
||||
logic s_dma_pause_ack;
|
||||
logic s_dma_fault;
|
||||
logic [ 3:0] s_dma_fault_code;
|
||||
logic [ 31:0] s_dma_fault_addr;
|
||||
logic [ 1:0] s_dma_fault_resp;
|
||||
logic [ 63:0] s_dma_read_bytes_cnt;
|
||||
logic [ 63:0] s_dma_write_bytes_cnt;
|
||||
logic [ 63:0] s_dma_stall_cycles;
|
||||
logic s_dma_read_cmd_err;
|
||||
logic s_dma_write_cmd_err;
|
||||
|
||||
// PCLK shell idle for the resource controller: no launch slot or snapshot
|
||||
// handshake pending, the link reconciled, and the HP side quiet.
|
||||
@@ -159,78 +200,160 @@ module apb4_npu (
|
||||
.clock_paused_o (s_clock_paused)
|
||||
);
|
||||
|
||||
npu_core u_npu_core (
|
||||
.clk_hp_i (clk_hp_i),
|
||||
.rst_hp_n_i (rst_hp_n_i),
|
||||
.launch_valid_i (s_hp_launch_valid),
|
||||
.launch_ready_o (s_hp_launch_ready),
|
||||
.launch_data_i (s_hp_launch_payload),
|
||||
.result_valid_o (s_hp_result_valid),
|
||||
.result_ready_i (s_hp_result_ready),
|
||||
.result_data_o (s_hp_result_payload),
|
||||
.snapshot_req_valid_i (s_hp_snap_req_valid),
|
||||
.snapshot_req_ready_o (s_hp_snap_req_ready),
|
||||
.snapshot_resp_valid_o(s_hp_snap_resp_valid),
|
||||
.snapshot_resp_ready_i(s_hp_snap_resp_ready),
|
||||
.snapshot_resp_data_o (s_hp_snap_resp_payload),
|
||||
.epoch_req_i (s_hp_epoch_req),
|
||||
.epoch_ack_o (s_hp_epoch_ack),
|
||||
.quiesce_req_i (s_hp_quiesce_req),
|
||||
.quiesce_ack_o (s_hp_quiesce_ack),
|
||||
.busy_o (s_hp_busy),
|
||||
.draining_o (s_hp_draining),
|
||||
.block_new_i (hp_block_new_i),
|
||||
.clock_pause_ack_o (hp_pause_ack_o),
|
||||
.flush_i (hp_flush_i),
|
||||
.flush_busy_o (hp_flush_busy_o),
|
||||
.pause_active_o (s_hp_pause_active),
|
||||
.idle_o (hp_idle_o)
|
||||
npu_core #(
|
||||
.ExecutionReady(1'b1)
|
||||
) u_npu_core (
|
||||
.clk_hp_i (clk_hp_i),
|
||||
.rst_hp_n_i (rst_hp_n_i),
|
||||
.launch_valid_i (s_hp_launch_valid),
|
||||
.launch_ready_o (s_hp_launch_ready),
|
||||
.launch_data_i (s_hp_launch_payload),
|
||||
.result_valid_o (s_hp_result_valid),
|
||||
.result_ready_i (s_hp_result_ready),
|
||||
.result_data_o (s_hp_result_payload),
|
||||
.snapshot_req_valid_i (s_hp_snap_req_valid),
|
||||
.snapshot_req_ready_o (s_hp_snap_req_ready),
|
||||
.snapshot_resp_valid_o (s_hp_snap_resp_valid),
|
||||
.snapshot_resp_ready_i (s_hp_snap_resp_ready),
|
||||
.snapshot_resp_data_o (s_hp_snap_resp_payload),
|
||||
.epoch_req_i (s_hp_epoch_req),
|
||||
.epoch_ack_o (s_hp_epoch_ack),
|
||||
.quiesce_req_i (s_hp_quiesce_req),
|
||||
.quiesce_ack_o (s_hp_quiesce_ack),
|
||||
.busy_o (s_hp_busy),
|
||||
.draining_o (s_hp_draining),
|
||||
.block_new_i (hp_block_new_i),
|
||||
.clock_pause_ack_o (hp_pause_ack_o),
|
||||
.flush_i (hp_flush_i),
|
||||
.flush_busy_o (hp_flush_busy_o),
|
||||
.pause_active_o (s_hp_pause_active),
|
||||
.idle_o (hp_idle_o),
|
||||
.abort_i (s_abort_hp_pulse),
|
||||
.dma_clear_o (s_dma_clear),
|
||||
.dma_read_req_valid_o (s_dma_read_req_valid),
|
||||
.dma_read_req_ready_i (s_dma_read_req_ready),
|
||||
.dma_read_addr_o (s_dma_read_addr),
|
||||
.dma_read_bytes_o (s_dma_read_bytes),
|
||||
.dma_read_data_valid_i (s_dma_read_data_valid),
|
||||
.dma_read_data_ready_o (s_dma_read_data_ready),
|
||||
.dma_read_data_i (s_dma_read_data),
|
||||
.dma_read_keep_i (s_dma_read_keep),
|
||||
.dma_read_last_i (s_dma_read_last),
|
||||
.dma_write_req_valid_o (s_dma_write_req_valid),
|
||||
.dma_write_req_ready_i (s_dma_write_req_ready),
|
||||
.dma_write_addr_o (s_dma_write_addr),
|
||||
.dma_write_bytes_o (s_dma_write_bytes),
|
||||
.dma_write_data_valid_o(s_dma_write_data_valid),
|
||||
.dma_write_data_ready_i(s_dma_write_data_ready),
|
||||
.dma_write_data_o (s_dma_write_data),
|
||||
.dma_write_keep_o (s_dma_write_keep),
|
||||
.dma_write_last_o (s_dma_write_last),
|
||||
.dma_write_done_i (s_dma_write_done),
|
||||
.dma_busy_i (s_dma_busy),
|
||||
.dma_read_busy_i (s_dma_read_busy),
|
||||
.dma_write_busy_i (s_dma_write_busy),
|
||||
.dma_pause_ack_i (s_dma_pause_ack),
|
||||
.dma_fault_i (s_dma_fault),
|
||||
.dma_fault_code_i (s_dma_fault_code),
|
||||
.dma_fault_addr_i (s_dma_fault_addr),
|
||||
.dma_fault_resp_i (s_dma_fault_resp),
|
||||
.dma_read_bytes_i (s_dma_read_bytes_cnt),
|
||||
.dma_write_bytes_i (s_dma_write_bytes_cnt),
|
||||
.dma_stall_cycles_i (s_dma_stall_cycles),
|
||||
.dma_read_cmd_err_i (s_dma_read_cmd_err),
|
||||
.dma_write_cmd_err_i (s_dma_write_cmd_err)
|
||||
);
|
||||
|
||||
// Phase 2: the AXI4 master is tied safely idle. No request is ever issued
|
||||
// and the response receivers remain ready; production DMA is Phase 3.
|
||||
assign npu_axi4.awid = '0;
|
||||
assign npu_axi4.awaddr = '0;
|
||||
assign npu_axi4.awlen = '0;
|
||||
assign npu_axi4.awsize = '0;
|
||||
assign npu_axi4.awburst = '0;
|
||||
assign npu_axi4.awlock = 1'b0;
|
||||
assign npu_axi4.awcache = '0;
|
||||
assign npu_axi4.awprot = '0;
|
||||
assign npu_axi4.awqos = '0;
|
||||
assign npu_axi4.awregion = '0;
|
||||
assign npu_axi4.awuser = '0;
|
||||
assign npu_axi4.awvalid = 1'b0;
|
||||
assign npu_axi4.wdata = '0;
|
||||
assign npu_axi4.wstrb = '0;
|
||||
assign npu_axi4.wlast = 1'b0;
|
||||
assign npu_axi4.wuser = '0;
|
||||
assign npu_axi4.wvalid = 1'b0;
|
||||
assign npu_axi4.bready = 1'b1;
|
||||
assign npu_axi4.arid = '0;
|
||||
assign npu_axi4.araddr = '0;
|
||||
assign npu_axi4.arlen = '0;
|
||||
assign npu_axi4.arsize = '0;
|
||||
assign npu_axi4.arburst = '0;
|
||||
assign npu_axi4.arlock = 1'b0;
|
||||
assign npu_axi4.arcache = '0;
|
||||
assign npu_axi4.arprot = '0;
|
||||
assign npu_axi4.arqos = '0;
|
||||
assign npu_axi4.arregion = '0;
|
||||
assign npu_axi4.aruser = '0;
|
||||
assign npu_axi4.arvalid = 1'b0;
|
||||
assign npu_axi4.rready = 1'b1;
|
||||
// The P3 AXI4 data plane: the production npu_dma engine drives the master
|
||||
// port directly (HP-native, no payload CDC or width conversion). Its
|
||||
// receivers are live whenever an accepted obligation exists, and presented
|
||||
// but unaccepted requests stay stable through pause and abort per the
|
||||
// frozen contract.
|
||||
npu_dma u_npu_dma (
|
||||
.clk_hp_i (clk_hp_i),
|
||||
.rst_hp_n_i (rst_hp_n_i),
|
||||
.clear_i (hp_flush_i || s_dma_clear),
|
||||
.block_new_i (hp_block_new_i),
|
||||
.pause_ack_o (s_dma_pause_ack),
|
||||
.read_req_valid_i (s_dma_read_req_valid),
|
||||
.read_req_ready_o (s_dma_read_req_ready),
|
||||
.read_addr_i (s_dma_read_addr),
|
||||
.read_bytes_i (s_dma_read_bytes),
|
||||
.read_data_valid_o (s_dma_read_data_valid),
|
||||
.read_data_ready_i (s_dma_read_data_ready),
|
||||
.read_data_o (s_dma_read_data),
|
||||
.read_keep_o (s_dma_read_keep),
|
||||
.read_last_o (s_dma_read_last),
|
||||
.write_req_valid_i (s_dma_write_req_valid),
|
||||
.write_req_ready_o (s_dma_write_req_ready),
|
||||
.write_addr_i (s_dma_write_addr),
|
||||
.write_bytes_i (s_dma_write_bytes),
|
||||
.write_data_valid_i(s_dma_write_data_valid),
|
||||
.write_data_ready_o(s_dma_write_data_ready),
|
||||
.write_data_i (s_dma_write_data),
|
||||
.write_keep_i (s_dma_write_keep),
|
||||
.write_last_i (s_dma_write_last),
|
||||
.write_done_o (s_dma_write_done),
|
||||
.busy_o (s_dma_busy),
|
||||
.read_busy_o (s_dma_read_busy),
|
||||
.write_busy_o (s_dma_write_busy),
|
||||
.read_bytes_o (s_dma_read_bytes_cnt),
|
||||
.write_bytes_o (s_dma_write_bytes_cnt),
|
||||
.stall_cycles_o (s_dma_stall_cycles),
|
||||
.fault_o (s_dma_fault),
|
||||
.fault_code_o (s_dma_fault_code),
|
||||
.fault_addr_o (s_dma_fault_addr),
|
||||
.fault_resp_o (s_dma_fault_resp),
|
||||
.read_cmd_err_o (s_dma_read_cmd_err),
|
||||
.write_cmd_err_o (s_dma_write_cmd_err),
|
||||
.axi4 (npu_axi4)
|
||||
);
|
||||
|
||||
// The HP abort route is Phase 3 scope; the Phase 2 ABORT command on an idle
|
||||
// NPU is a successful no-op and the busy path is unreachable.
|
||||
assign s_abort_unused = s_abort;
|
||||
// Software ABORT route: the PCLK pulse toggles a shell register that crosses
|
||||
// through cdc_sync; the HP edge detector produces the abort pulse. A second
|
||||
// ABORT arriving within the synchronization window is idempotent (the job is
|
||||
// already cancelling), so no toggle can be lost that matters.
|
||||
assign s_abort_toggle_d = s_abort ? !s_abort_toggle_q : s_abort_toggle_q;
|
||||
dffr #(
|
||||
.DATA_WIDTH(1)
|
||||
) u_abort_toggle (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i(rst_n_i),
|
||||
.dat_i (s_abort_toggle_d),
|
||||
.dat_o (s_abort_toggle_q)
|
||||
);
|
||||
cdc_sync #(
|
||||
.STAGE(2)
|
||||
) u_abort_sync (
|
||||
.clk_i (clk_hp_i),
|
||||
.rst_n_i(rst_hp_n_i),
|
||||
.dat_i (s_abort_toggle_q),
|
||||
.dat_o (s_abort_toggle_hp)
|
||||
);
|
||||
assign s_abort_hp_d = s_abort_toggle_hp;
|
||||
dffr #(
|
||||
.DATA_WIDTH(1)
|
||||
) u_abort_hp (
|
||||
.clk_i (clk_hp_i),
|
||||
.rst_n_i(rst_hp_n_i),
|
||||
.dat_i (s_abort_hp_d),
|
||||
.dat_o (s_abort_hp_q)
|
||||
);
|
||||
assign s_abort_hp_pulse = s_abort_toggle_hp ^ s_abort_hp_q;
|
||||
|
||||
`ifndef SV_ASSRT_DISABLE
|
||||
always_ff @(posedge clk_hp_i) begin
|
||||
if (rst_hp_n_i) begin
|
||||
// The tied-off master never issues a request and stays ready to drain.
|
||||
assert (!npu_axi4.awvalid && !npu_axi4.wvalid && !npu_axi4.arvalid);
|
||||
assert (npu_axi4.bready && npu_axi4.rready);
|
||||
logic s_p2_idle_master_q;
|
||||
|
||||
// The shell-level Phase 2 contract that the software-visible master stays
|
||||
// silent while no job can be launched (START is still rejected at P3).
|
||||
always_ff @(posedge clk_hp_i or negedge rst_hp_n_i) begin
|
||||
if (!rst_hp_n_i) begin
|
||||
s_p2_idle_master_q <= 1'b1;
|
||||
end else begin
|
||||
s_p2_idle_master_q <= !s_hp_busy;
|
||||
if (s_p2_idle_master_q) begin
|
||||
assert (!npu_axi4.awvalid && !npu_axi4.wvalid && !npu_axi4.arvalid);
|
||||
end
|
||||
end
|
||||
end
|
||||
`endif
|
||||
|
||||
+762
-100
@@ -1,20 +1,45 @@
|
||||
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
|
||||
// SPDX-License-Identifier: MulanPSL-2.0
|
||||
|
||||
// NPU Phase 2 HP-domain lifecycle endpoint. It terminates the launch, result,
|
||||
// and snapshot mailboxes of npu_control_cdc, participates in the epoch
|
||||
// reconciliation handshake, and answers idle snapshots with a zero counter
|
||||
// bank. Phase 2 has no jobs and no DMA: launches can never be accepted by the
|
||||
// PCLK shell (START never passes), the sink below exists only to keep the
|
||||
// mailbox protocol complete, no result is ever produced, and there is no AXI
|
||||
// work to pause, drain, or flush.
|
||||
// NPU HP-domain job execution core. With ExecutionReady=1'b0 this is exactly
|
||||
// the Phase 2 lifecycle endpoint: it terminates the launch, result, and
|
||||
// snapshot mailboxes of npu_control_cdc, participates in the epoch
|
||||
// reconciliation handshake, answers idle snapshots with a zero counter bank,
|
||||
// sinks the (shell-unreachable) launch, and produces no result. With
|
||||
// ExecutionReady=1'b1 it is the Phase 3 job controller: an accepted launch
|
||||
// latches {JOB_BASE, JOB_ID, TIMEOUT_CYCLES, JOB_COUNT}, resets the private
|
||||
// DMA and the per-job counters, and runs npu_job_decoder (fetch + ABI-1.0
|
||||
// validation) feeding npu_scheduler (transport) record by record until every
|
||||
// descriptor retires (DONE), the first fault drains (ERROR), software abort
|
||||
// or resource quiesce cancels (ABORTED), or a reset/flush epoch recovery
|
||||
// cancels (RESET_CANCELLED, published after the fresh epoch is
|
||||
// re-acknowledged). The npu_dma engine itself lives in apb4_npu (the Phase 2
|
||||
// control-CDC testbench instantiates this module without an AXI fabric, so
|
||||
// the engine interface is a discrete signal boundary); the P2 branch never
|
||||
// references the P3 ports.
|
||||
//
|
||||
// Lifecycle invariants (both configurations): an epoch restart forces the
|
||||
// endpoint to a safe state before re-acknowledging (at P3 the drain completes
|
||||
// first: no new work, accepted transport obligations retire, presented but
|
||||
// unaccepted VALID stays stable); the quiesce acknowledge is a registered
|
||||
// round trip and at P3 additionally requires the terminal result latched for
|
||||
// delivery; a flushed or cancelled job never reports DONE; busy_o/draining_o
|
||||
// are truthful. The first-fault record follows the spec's deterministic
|
||||
// same-cycle priority AXI_PROTOCOL > AXI_READ > AXI_WRITE > LOCAL_STATE >
|
||||
// ARITHMETIC > DESCRIPTOR > UNSUPPORTED > RANGE > NO_PROGRESS; reset
|
||||
// cancellation never overwrites a captured earlier fault.
|
||||
`include "npu_define.svh"
|
||||
|
||||
module npu_core #(
|
||||
parameter int unsigned LaunchPayloadWidth = `APB4_NPU__LAUNCH_PAYLOAD_WIDTH,
|
||||
parameter int unsigned ResultPayloadWidth = `APB4_NPU__RESULT_PAYLOAD_WIDTH,
|
||||
parameter int unsigned SnapshotPayloadWidth = `APB4_NPU__SNAPSHOT_PAYLOAD_WIDTH,
|
||||
parameter int unsigned EpochWidth = `APB4_NPU__EPOCH_WIDTH
|
||||
parameter int unsigned EpochWidth = `APB4_NPU__EPOCH_WIDTH,
|
||||
// Phase 3 execution compile switch: the PCLK shell keeps START rejected at
|
||||
// P3, so apb4_npu instantiates the live job controller while the software
|
||||
// launch path stays closed; the P2 shell testbench elaborates the default
|
||||
// (ExecutionReady=0) endpoint.
|
||||
parameter bit ExecutionReady = 1'b0
|
||||
) (
|
||||
// verilog_format: off -- preserve the HP lifecycle boundary columns
|
||||
input logic clk_hp_i,
|
||||
@@ -41,19 +66,49 @@ module npu_core #(
|
||||
input logic flush_i,
|
||||
output logic flush_busy_o,
|
||||
output logic pause_active_o,
|
||||
output logic idle_o
|
||||
output logic idle_o,
|
||||
// Phase 3: software abort pulse (HP domain, toggle-synchronized in
|
||||
// apb4_npu) and the discrete npu_dma engine interface
|
||||
input logic abort_i,
|
||||
output logic dma_clear_o,
|
||||
output logic dma_read_req_valid_o,
|
||||
input logic dma_read_req_ready_i,
|
||||
output logic [31:0] dma_read_addr_o,
|
||||
output logic [31:0] dma_read_bytes_o,
|
||||
input logic dma_read_data_valid_i,
|
||||
output logic dma_read_data_ready_o,
|
||||
input logic [63:0] dma_read_data_i,
|
||||
input logic [ 7:0] dma_read_keep_i,
|
||||
input logic dma_read_last_i,
|
||||
output logic dma_write_req_valid_o,
|
||||
input logic dma_write_req_ready_i,
|
||||
output logic [31:0] dma_write_addr_o,
|
||||
output logic [31:0] dma_write_bytes_o,
|
||||
output logic dma_write_data_valid_o,
|
||||
input logic dma_write_data_ready_i,
|
||||
output logic [63:0] dma_write_data_o,
|
||||
output logic [ 7:0] dma_write_keep_o,
|
||||
output logic dma_write_last_o,
|
||||
input logic dma_write_done_i,
|
||||
input logic dma_busy_i,
|
||||
input logic dma_read_busy_i,
|
||||
input logic dma_write_busy_i,
|
||||
input logic dma_pause_ack_i,
|
||||
input logic dma_fault_i,
|
||||
input logic [ 3:0] dma_fault_code_i,
|
||||
input logic [31:0] dma_fault_addr_i,
|
||||
input logic [ 1:0] dma_fault_resp_i,
|
||||
input logic [63:0] dma_read_bytes_i,
|
||||
input logic [63:0] dma_write_bytes_i,
|
||||
input logic [63:0] dma_stall_cycles_i,
|
||||
input logic dma_read_cmd_err_i,
|
||||
input logic dma_write_cmd_err_i
|
||||
// verilog_format: on
|
||||
);
|
||||
typedef enum logic [0:0] {
|
||||
SnapIdle,
|
||||
SnapRespond
|
||||
} state_e;
|
||||
|
||||
state_e s_state_d, s_state_q;
|
||||
logic [EpochWidth-1:0] s_epoch_ack_d, s_epoch_ack_q;
|
||||
logic s_quiesce_ack_d, s_quiesce_ack_q;
|
||||
logic s_epoch_restart;
|
||||
logic s_unused;
|
||||
} snap_state_e;
|
||||
|
||||
`ifndef SYNTHESIS
|
||||
initial begin
|
||||
@@ -64,105 +119,712 @@ module npu_core #(
|
||||
end
|
||||
`endif
|
||||
|
||||
// Phase 2 protocol-complete sinks and constant sources: a launch is sunk
|
||||
// (unreachable), no terminal result exists, the idle snapshot returns the
|
||||
// zero bank with a zero job token, and no AXI activity ever needs draining.
|
||||
assign launch_ready_o = 1'b1;
|
||||
assign result_valid_o = 1'b0;
|
||||
assign result_data_o = '0;
|
||||
assign busy_o = 1'b0;
|
||||
assign draining_o = 1'b0;
|
||||
assign flush_busy_o = 1'b0;
|
||||
assign snapshot_resp_data_o = '0;
|
||||
if (!ExecutionReady) begin : gen_p2_endpoint
|
||||
// Phase 2 protocol-complete sinks and constant sources: a launch is sunk
|
||||
// (unreachable), no terminal result exists, the idle snapshot returns the
|
||||
// zero bank with a zero job token, and no AXI activity ever needs
|
||||
// draining. The P3 engine interface is tied off and its inputs are
|
||||
// unread by construction.
|
||||
snap_state_e s_state_d, s_state_q;
|
||||
logic [EpochWidth-1:0] s_epoch_ack_d, s_epoch_ack_q;
|
||||
logic s_quiesce_ack_d, s_quiesce_ack_q;
|
||||
logic s_epoch_restart;
|
||||
logic s_unused;
|
||||
|
||||
// With zero accepted AXI obligations and no internal work, the clock-pause
|
||||
// acknowledge is immediate and the HP idle contribution follows the
|
||||
// platform formula !block_new_i || clock_pause_ack_o.
|
||||
assign clock_pause_ack_o = block_new_i;
|
||||
assign pause_active_o = block_new_i && clock_pause_ack_o;
|
||||
assign idle_o = !block_new_i || clock_pause_ack_o;
|
||||
assign launch_ready_o = 1'b1;
|
||||
assign result_valid_o = 1'b0;
|
||||
assign result_data_o = '0;
|
||||
assign busy_o = 1'b0;
|
||||
assign draining_o = 1'b0;
|
||||
assign flush_busy_o = 1'b0;
|
||||
assign snapshot_resp_data_o = '0;
|
||||
|
||||
assign epoch_ack_o = s_epoch_ack_q;
|
||||
assign quiesce_ack_o = s_quiesce_ack_q;
|
||||
// With zero accepted AXI obligations and no internal work, the
|
||||
// clock-pause acknowledge is immediate and the HP idle contribution
|
||||
// follows the platform formula !block_new_i || clock_pause_ack_o.
|
||||
assign clock_pause_ack_o = block_new_i;
|
||||
assign pause_active_o = block_new_i && clock_pause_ack_o;
|
||||
assign idle_o = !block_new_i || clock_pause_ack_o;
|
||||
|
||||
assign s_epoch_restart = (epoch_req_i != s_epoch_ack_q);
|
||||
assign s_unused = ^{launch_data_i, result_ready_i, flush_i, 1'b0};
|
||||
assign epoch_ack_o = s_epoch_ack_q;
|
||||
assign quiesce_ack_o = s_quiesce_ack_q;
|
||||
|
||||
always_comb begin
|
||||
s_state_d = s_state_q;
|
||||
s_epoch_ack_d = s_epoch_ack_q;
|
||||
s_quiesce_ack_d = quiesce_req_i;
|
||||
snapshot_req_ready_o = 1'b0;
|
||||
snapshot_resp_valid_o = 1'b0;
|
||||
unique case (s_state_q)
|
||||
SnapIdle: begin
|
||||
snapshot_req_ready_o = 1'b1;
|
||||
if (snapshot_req_valid_i) begin
|
||||
s_state_d = SnapRespond;
|
||||
assign s_epoch_restart = (epoch_req_i != s_epoch_ack_q);
|
||||
assign s_unused = ^{launch_data_i, result_ready_i, flush_i, 1'b0};
|
||||
|
||||
assign dma_clear_o = 1'b0;
|
||||
assign dma_read_req_valid_o = 1'b0;
|
||||
assign dma_read_addr_o = '0;
|
||||
assign dma_read_bytes_o = '0;
|
||||
assign dma_read_data_ready_o = 1'b0;
|
||||
assign dma_write_req_valid_o = 1'b0;
|
||||
assign dma_write_addr_o = '0;
|
||||
assign dma_write_bytes_o = '0;
|
||||
assign dma_write_data_valid_o = 1'b0;
|
||||
assign dma_write_data_o = '0;
|
||||
assign dma_write_keep_o = '0;
|
||||
assign dma_write_last_o = 1'b0;
|
||||
|
||||
always_comb begin
|
||||
s_state_d = s_state_q;
|
||||
s_epoch_ack_d = s_epoch_ack_q;
|
||||
s_quiesce_ack_d = quiesce_req_i;
|
||||
snapshot_req_ready_o = 1'b0;
|
||||
snapshot_resp_valid_o = 1'b0;
|
||||
unique case (s_state_q)
|
||||
SnapIdle: begin
|
||||
snapshot_req_ready_o = 1'b1;
|
||||
if (snapshot_req_valid_i) begin
|
||||
s_state_d = SnapRespond;
|
||||
end
|
||||
end
|
||||
end
|
||||
SnapRespond: begin
|
||||
snapshot_resp_valid_o = 1'b1;
|
||||
if (snapshot_resp_ready_i) begin
|
||||
s_state_d = SnapIdle;
|
||||
SnapRespond: begin
|
||||
snapshot_resp_valid_o = 1'b1;
|
||||
if (snapshot_resp_ready_i) begin
|
||||
s_state_d = SnapIdle;
|
||||
end
|
||||
end
|
||||
default: s_state_d = SnapIdle;
|
||||
endcase
|
||||
// A fresh epoch restarts the endpoint bookkeeping before it is
|
||||
// acknowledged: any in-flight mailbox item is being flushed by the link
|
||||
// reset held for the whole reconciliation, so the endpoint returns to
|
||||
// SnapIdle first and only then publishes the new epoch.
|
||||
if (s_epoch_restart) begin
|
||||
s_state_d = SnapIdle;
|
||||
s_epoch_ack_d = epoch_req_i;
|
||||
end
|
||||
default: s_state_d = SnapIdle;
|
||||
endcase
|
||||
// A fresh epoch restarts the endpoint bookkeeping before it is
|
||||
// acknowledged: any in-flight mailbox item is being flushed by the link
|
||||
// reset held for the whole reconciliation, so the endpoint returns to
|
||||
// SnapIdle first and only then publishes the new epoch.
|
||||
if (s_epoch_restart) begin
|
||||
s_state_d = SnapIdle;
|
||||
s_epoch_ack_d = epoch_req_i;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk_hp_i or negedge rst_hp_n_i) begin
|
||||
if (!rst_hp_n_i) begin
|
||||
s_state_q <= SnapIdle;
|
||||
end else begin
|
||||
s_state_q <= s_state_d;
|
||||
always_ff @(posedge clk_hp_i or negedge rst_hp_n_i) begin
|
||||
if (!rst_hp_n_i) begin
|
||||
s_state_q <= SnapIdle;
|
||||
end else begin
|
||||
s_state_q <= s_state_d;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
dffr #(
|
||||
.DATA_WIDTH(EpochWidth)
|
||||
) u_epoch_ack (
|
||||
.clk_i (clk_hp_i),
|
||||
.rst_n_i(rst_hp_n_i),
|
||||
.dat_i (s_epoch_ack_d),
|
||||
.dat_o (s_epoch_ack_q)
|
||||
);
|
||||
dffr #(
|
||||
.DATA_WIDTH(1)
|
||||
) u_quiesce_ack (
|
||||
.clk_i (clk_hp_i),
|
||||
.rst_n_i(rst_hp_n_i),
|
||||
.dat_i (s_quiesce_ack_d),
|
||||
.dat_o (s_quiesce_ack_q)
|
||||
);
|
||||
dffr #(
|
||||
.DATA_WIDTH(EpochWidth)
|
||||
) u_epoch_ack (
|
||||
.clk_i (clk_hp_i),
|
||||
.rst_n_i(rst_hp_n_i),
|
||||
.dat_i (s_epoch_ack_d),
|
||||
.dat_o (s_epoch_ack_q)
|
||||
);
|
||||
dffr #(
|
||||
.DATA_WIDTH(1)
|
||||
) u_quiesce_ack (
|
||||
.clk_i (clk_hp_i),
|
||||
.rst_n_i(rst_hp_n_i),
|
||||
.dat_i (s_quiesce_ack_d),
|
||||
.dat_o (s_quiesce_ack_q)
|
||||
);
|
||||
|
||||
`ifndef SV_ASSRT_DISABLE
|
||||
logic s_resp_wait_q;
|
||||
logic s_epoch_restart_q;
|
||||
logic s_resp_wait_q;
|
||||
logic s_epoch_restart_q;
|
||||
|
||||
always_ff @(posedge clk_hp_i or negedge rst_hp_n_i) begin
|
||||
if (!rst_hp_n_i) begin
|
||||
s_resp_wait_q <= 1'b0;
|
||||
s_epoch_restart_q <= 1'b0;
|
||||
end else begin
|
||||
s_resp_wait_q <= snapshot_resp_valid_o && !snapshot_resp_ready_i;
|
||||
s_epoch_restart_q <= s_epoch_restart;
|
||||
// The snapshot response holds valid and its payload stable until the
|
||||
// mailbox accepts it; only an epoch restart may withdraw it.
|
||||
if (s_resp_wait_q) begin
|
||||
assert ((snapshot_resp_valid_o && $stable(snapshot_resp_data_o)) || s_epoch_restart_q);
|
||||
always_ff @(posedge clk_hp_i or negedge rst_hp_n_i) begin
|
||||
if (!rst_hp_n_i) begin
|
||||
s_resp_wait_q <= 1'b0;
|
||||
s_epoch_restart_q <= 1'b0;
|
||||
end else begin
|
||||
s_resp_wait_q <= snapshot_resp_valid_o && !snapshot_resp_ready_i;
|
||||
s_epoch_restart_q <= s_epoch_restart;
|
||||
// The snapshot response holds valid and its payload stable until the
|
||||
// mailbox accepts it; only an epoch restart may withdraw it.
|
||||
if (s_resp_wait_q) begin
|
||||
assert ((snapshot_resp_valid_o && $stable(snapshot_resp_data_o)) || s_epoch_restart_q);
|
||||
end
|
||||
// No terminal result can ever appear at Phase 2.
|
||||
assert (!result_valid_o);
|
||||
end
|
||||
// No terminal result can ever appear at Phase 2.
|
||||
assert (!result_valid_o);
|
||||
end
|
||||
end
|
||||
`endif
|
||||
end else begin : gen_p3_core
|
||||
// -----------------------------------------------------------------
|
||||
// Phase 3 job controller
|
||||
// -----------------------------------------------------------------
|
||||
typedef enum logic [2:0] {
|
||||
CoreIdle,
|
||||
CoreRun,
|
||||
CoreDrainFault,
|
||||
CoreDrainAbort,
|
||||
CoreDrainReset,
|
||||
CorePublish
|
||||
} core_state_e;
|
||||
|
||||
core_state_e s_state_d, s_state_q;
|
||||
snap_state_e s_snap_state_d, s_snap_state_q;
|
||||
logic [EpochWidth-1:0] s_epoch_ack_d, s_epoch_ack_q;
|
||||
logic s_quiesce_ack_d, s_quiesce_ack_q;
|
||||
logic s_epoch_restart;
|
||||
// job context
|
||||
logic [31:0] s_job_base_q;
|
||||
logic [31:0] s_job_id_q;
|
||||
logic [31:0] s_timeout_q;
|
||||
logic [15:0] s_job_count_q;
|
||||
logic [31:0] s_term_job_id_q;
|
||||
// first-fault record
|
||||
logic s_fault_seen_d, s_fault_seen_q;
|
||||
logic [ 3:0] s_fault_code_d, s_fault_code_q;
|
||||
logic [31:0] s_fault_addr_d, s_fault_addr_q;
|
||||
logic [31:0] s_fault_info_d, s_fault_info_q;
|
||||
logic [31:0] s_fault_desc_d, s_fault_desc_q;
|
||||
// terminal record
|
||||
logic [ 2:0] s_res_code_d, s_res_code_q;
|
||||
logic [ 2:0] s_res_irq_d, s_res_irq_q;
|
||||
logic [31:0] s_res_completed_d, s_res_completed_q;
|
||||
// snapshot capture
|
||||
logic [SnapshotPayloadWidth-1:0] s_snap_payload_d, s_snap_payload_q;
|
||||
logic s_launch_accept;
|
||||
logic s_job_busy;
|
||||
logic s_dec_busy;
|
||||
logic s_sch_busy;
|
||||
logic s_sch_drained;
|
||||
logic s_drained;
|
||||
logic s_compose_terminal;
|
||||
logic s_stop;
|
||||
logic s_clear;
|
||||
// decoder wires
|
||||
logic s_dec_job_start;
|
||||
logic s_dec_read_req_valid;
|
||||
logic s_dec_read_req_ready;
|
||||
logic [31:0] s_dec_read_addr;
|
||||
logic [31:0] s_dec_read_bytes;
|
||||
logic s_dec_read_data_valid;
|
||||
logic s_dec_read_data_ready;
|
||||
logic s_dec_pause_ok;
|
||||
logic s_dec_all_done;
|
||||
logic s_all_done_q;
|
||||
logic s_dec_progress;
|
||||
logic s_dec_fault_req;
|
||||
logic [ 3:0] s_dec_fault_code;
|
||||
logic [31:0] s_dec_fault_addr;
|
||||
logic [31:0] s_dec_fault_info;
|
||||
logic [31:0] s_dec_fault_desc;
|
||||
logic [15:0] s_dec_cur_index;
|
||||
logic s_dec_retired;
|
||||
// record wires decoder -> scheduler
|
||||
logic s_rec_valid;
|
||||
logic s_rec_ready;
|
||||
logic [ 3:0] s_rec_opcode;
|
||||
logic [12:0] s_rec_h, s_rec_w, s_rec_cin, s_rec_cout, s_rec_oh, s_rec_ow;
|
||||
logic [31:0] s_rec_input0_base, s_rec_output_base;
|
||||
logic [31:0] s_rec_weight_base, s_rec_param_base;
|
||||
logic [31:0] s_rec_irow, s_rec_orow;
|
||||
logic [ 7:0] s_rec_kh, s_rec_kw, s_rec_sh, s_rec_sw;
|
||||
logic [ 7:0] s_rec_pad_top, s_rec_pad_left, s_rec_in0_zero;
|
||||
logic [31:0] s_rec_weight_bytes, s_rec_param_bytes;
|
||||
logic [15:0] s_rec_desc_index;
|
||||
// scheduler wires
|
||||
logic s_sch_launch;
|
||||
logic s_sch_retired;
|
||||
logic [31:0] s_sch_completed;
|
||||
logic s_sch_read_req_valid;
|
||||
logic s_sch_read_req_ready;
|
||||
logic [31:0] s_sch_read_addr;
|
||||
logic [31:0] s_sch_read_bytes;
|
||||
logic s_sch_read_data_valid;
|
||||
logic s_sch_read_data_ready;
|
||||
logic s_sch_write_req_valid;
|
||||
logic [31:0] s_sch_write_addr;
|
||||
logic [31:0] s_sch_write_bytes;
|
||||
logic s_sch_write_data_valid;
|
||||
logic [63:0] s_sch_write_data;
|
||||
logic [ 7:0] s_sch_write_keep;
|
||||
logic s_sch_write_last;
|
||||
logic s_sch_pause_ok;
|
||||
logic s_sch_terminal;
|
||||
logic [639:0] s_sch_counters;
|
||||
logic [639:0] s_sch_term_counters;
|
||||
logic s_sch_fault_req;
|
||||
logic [ 3:0] s_sch_fault_code;
|
||||
logic [31:0] s_sch_fault_addr;
|
||||
logic [31:0] s_sch_fault_info;
|
||||
logic [31:0] s_sch_fault_desc;
|
||||
// fault arbitration
|
||||
logic s_evt_dec;
|
||||
logic s_evt_sch;
|
||||
logic s_evt_cmd_err;
|
||||
logic [ 3:0] s_evt_code;
|
||||
logic [31:0] s_evt_addr;
|
||||
logic [31:0] s_evt_info;
|
||||
logic [31:0] s_evt_desc;
|
||||
logic s_evt_any;
|
||||
|
||||
// Deterministic same-cycle first-fault priority: lower rank wins.
|
||||
function automatic logic [3:0] fault_rank(input logic [3:0] code_i);
|
||||
logic [3:0] rank;
|
||||
begin
|
||||
unique case (code_i)
|
||||
`APB4_NPU__FAULT_CODE_AXI_PROTOCOL: rank = 4'd0;
|
||||
`APB4_NPU__FAULT_CODE_AXI_READ: rank = 4'd1;
|
||||
`APB4_NPU__FAULT_CODE_AXI_WRITE: rank = 4'd2;
|
||||
`APB4_NPU__FAULT_CODE_LOCAL_STATE: rank = 4'd3;
|
||||
`APB4_NPU__FAULT_CODE_ARITHMETIC: rank = 4'd4;
|
||||
`APB4_NPU__FAULT_CODE_DESCRIPTOR: rank = 4'd5;
|
||||
`APB4_NPU__FAULT_CODE_UNSUPPORTED: rank = 4'd6;
|
||||
`APB4_NPU__FAULT_CODE_RANGE: rank = 4'd7;
|
||||
`APB4_NPU__FAULT_CODE_NO_PROGRESS: rank = 4'd8;
|
||||
default: rank = 4'd9;
|
||||
endcase
|
||||
return rank;
|
||||
end
|
||||
endfunction
|
||||
|
||||
assign s_evt_dec = s_dec_fault_req;
|
||||
assign s_evt_sch = s_sch_fault_req;
|
||||
// A rejected DMA command means a scheduler/decoder programming bug.
|
||||
assign s_evt_cmd_err = (dma_read_cmd_err_i || dma_write_cmd_err_i) && s_job_busy &&
|
||||
!dma_fault_i && !s_fault_seen_q;
|
||||
|
||||
always_comb begin
|
||||
s_evt_any = s_evt_dec || s_evt_sch || s_evt_cmd_err;
|
||||
s_evt_code = `APB4_NPU__FAULT_CODE_LOCAL_STATE;
|
||||
s_evt_addr = 32'd0;
|
||||
s_evt_info = {16'd0, 8'd255, 4'd0, 2'd0, 2'd0};
|
||||
s_evt_desc = 32'hffff_ffff;
|
||||
if (s_evt_dec) begin
|
||||
s_evt_code = s_dec_fault_code;
|
||||
s_evt_addr = s_dec_fault_addr;
|
||||
s_evt_info = s_dec_fault_info;
|
||||
s_evt_desc = s_dec_fault_desc;
|
||||
end
|
||||
if (s_evt_sch && (!s_evt_dec ||
|
||||
(fault_rank(s_sch_fault_code) <= fault_rank(s_dec_fault_code)))) begin
|
||||
s_evt_code = s_sch_fault_code;
|
||||
s_evt_addr = s_sch_fault_addr;
|
||||
s_evt_info = s_sch_fault_info;
|
||||
// A scheduler fault while it is idle (watchdog during the fetch
|
||||
// phase) belongs to the descriptor the decoder holds.
|
||||
s_evt_desc = s_dec_busy ? {16'd0, s_dec_cur_index} : s_sch_fault_desc;
|
||||
end
|
||||
if (s_evt_cmd_err && (!s_evt_dec && !s_evt_sch ||
|
||||
(fault_rank(`APB4_NPU__FAULT_CODE_LOCAL_STATE) <=
|
||||
fault_rank(s_evt_code)))) begin
|
||||
s_evt_code = `APB4_NPU__FAULT_CODE_LOCAL_STATE;
|
||||
s_evt_addr = 32'd0;
|
||||
s_evt_info = {16'd0, 8'd255, 4'd0, 2'd0, 2'd0};
|
||||
s_evt_desc = s_dec_busy ? {16'd0, s_dec_cur_index} : s_sch_fault_desc;
|
||||
end
|
||||
end
|
||||
|
||||
assign s_job_busy = (s_state_q != CoreIdle);
|
||||
assign s_drained = !s_dec_busy && s_sch_drained && !dma_busy_i;
|
||||
|
||||
assign s_epoch_restart = (epoch_req_i != s_epoch_ack_q);
|
||||
assign s_launch_accept = launch_valid_i && launch_ready_o;
|
||||
assign s_stop = (s_state_q == CoreDrainFault) || (s_state_q == CoreDrainAbort) ||
|
||||
(s_state_q == CoreDrainReset) || flush_i;
|
||||
assign s_clear = flush_i ||
|
||||
((s_state_q == CorePublish) && result_valid_o && result_ready_i) ||
|
||||
((s_state_q == CoreDrainReset) && s_drained);
|
||||
assign s_compose_terminal = ((s_state_q == CoreRun) && s_all_done_q) ||
|
||||
(((s_state_q == CoreDrainFault) || (s_state_q == CoreDrainAbort) ||
|
||||
(s_state_q == CoreDrainReset)) && s_drained);
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Mailbox-facing outputs
|
||||
// -------------------------------------------------------------
|
||||
assign launch_ready_o = (s_state_q == CoreIdle) && !s_epoch_restart && !quiesce_req_i &&
|
||||
!flush_i;
|
||||
assign busy_o = s_job_busy;
|
||||
assign draining_o = (s_state_q == CoreDrainFault) || (s_state_q == CoreDrainAbort) ||
|
||||
(s_state_q == CoreDrainReset);
|
||||
assign flush_busy_o = 1'b0;
|
||||
assign idle_o = !block_new_i || clock_pause_ack_o;
|
||||
assign clock_pause_ack_o = block_new_i && dma_pause_ack_i && s_dec_pause_ok &&
|
||||
s_sch_pause_ok;
|
||||
assign pause_active_o = block_new_i && clock_pause_ack_o;
|
||||
assign epoch_ack_o = s_epoch_ack_q;
|
||||
assign quiesce_ack_o = s_quiesce_ack_q;
|
||||
|
||||
assign dma_clear_o = flush_i || s_launch_accept;
|
||||
|
||||
// Read-channel ownership alternates fetch (decoder) and execute
|
||||
// (scheduler) phases, which never overlap.
|
||||
assign dma_read_req_valid_o = s_sch_busy ? s_sch_read_req_valid : s_dec_read_req_valid;
|
||||
assign dma_read_addr_o = s_sch_busy ? s_sch_read_addr : s_dec_read_addr;
|
||||
assign dma_read_bytes_o = s_sch_busy ? s_sch_read_bytes : s_dec_read_bytes;
|
||||
assign s_dec_read_req_ready = !s_sch_busy && dma_read_req_ready_i;
|
||||
assign s_sch_read_req_ready = s_sch_busy && dma_read_req_ready_i;
|
||||
assign s_dec_read_data_valid = !s_sch_busy && dma_read_data_valid_i;
|
||||
assign s_sch_read_data_valid = s_sch_busy && dma_read_data_valid_i;
|
||||
assign dma_read_data_ready_o = s_sch_busy ? s_sch_read_data_ready : s_dec_read_data_ready;
|
||||
|
||||
assign dma_write_req_valid_o = s_sch_write_req_valid;
|
||||
assign dma_write_addr_o = s_sch_write_addr;
|
||||
assign dma_write_bytes_o = s_sch_write_bytes;
|
||||
assign dma_write_data_valid_o = s_sch_write_data_valid;
|
||||
assign dma_write_data_o = s_sch_write_data;
|
||||
assign dma_write_keep_o = s_sch_write_keep;
|
||||
assign dma_write_last_o = s_sch_write_last;
|
||||
|
||||
assign s_dec_job_start = s_launch_accept;
|
||||
assign s_sch_launch = s_launch_accept;
|
||||
assign s_dec_retired = s_sch_retired;
|
||||
assign s_sch_terminal = s_compose_terminal;
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Core FSM
|
||||
// -------------------------------------------------------------
|
||||
always_comb begin
|
||||
s_state_d = s_state_q;
|
||||
s_epoch_ack_d = s_epoch_ack_q;
|
||||
s_quiesce_ack_d = s_quiesce_ack_q;
|
||||
s_snap_state_d = s_snap_state_q;
|
||||
s_snap_payload_d = s_snap_payload_q;
|
||||
s_fault_seen_d = s_fault_seen_q;
|
||||
s_fault_code_d = s_fault_code_q;
|
||||
s_fault_addr_d = s_fault_addr_q;
|
||||
s_fault_info_d = s_fault_info_q;
|
||||
s_fault_desc_d = s_fault_desc_q;
|
||||
s_res_code_d = s_res_code_q;
|
||||
s_res_irq_d = s_res_irq_q;
|
||||
s_res_completed_d = s_res_completed_q;
|
||||
snapshot_req_ready_o = 1'b0;
|
||||
snapshot_resp_valid_o = 1'b0;
|
||||
|
||||
// snapshot endpoint: atomic capture of the live or retained bank
|
||||
unique case (s_snap_state_q)
|
||||
SnapIdle: begin
|
||||
snapshot_req_ready_o = 1'b1;
|
||||
if (snapshot_req_valid_i) begin
|
||||
s_snap_payload_d = s_job_busy ? {s_sch_counters, s_job_id_q} :
|
||||
{s_sch_term_counters, s_term_job_id_q};
|
||||
s_snap_state_d = SnapRespond;
|
||||
end
|
||||
end
|
||||
SnapRespond: begin
|
||||
snapshot_resp_valid_o = 1'b1;
|
||||
if (snapshot_resp_ready_i) begin
|
||||
s_snap_state_d = SnapIdle;
|
||||
end
|
||||
end
|
||||
default: s_snap_state_d = SnapIdle;
|
||||
endcase
|
||||
|
||||
// first-fault capture (also during abort/reset drains: a fault during
|
||||
// cancellation converts the terminal record to ERROR)
|
||||
if (s_evt_any && !s_fault_seen_q &&
|
||||
((s_state_q == CoreRun) || (s_state_q == CoreDrainAbort) ||
|
||||
(s_state_q == CoreDrainReset))) begin
|
||||
s_fault_seen_d = 1'b1;
|
||||
s_fault_code_d = s_evt_code;
|
||||
s_fault_addr_d = s_evt_addr;
|
||||
s_fault_info_d = s_evt_info;
|
||||
s_fault_desc_d = s_evt_desc;
|
||||
end
|
||||
|
||||
unique case (s_state_q)
|
||||
CoreIdle: begin
|
||||
if (s_launch_accept) begin
|
||||
s_state_d = CoreRun;
|
||||
end
|
||||
end
|
||||
CoreRun: begin
|
||||
if (s_evt_any && !s_fault_seen_q) begin
|
||||
s_state_d = CoreDrainFault;
|
||||
end else if (s_all_done_q) begin
|
||||
s_res_code_d = 3'(`APB4_NPU__RESULT_CODE_DONE);
|
||||
s_res_irq_d = 3'b001;
|
||||
s_res_completed_d = {16'd0, s_job_count_q};
|
||||
s_fault_code_d = 4'(`APB4_NPU__FAULT_CODE_NONE);
|
||||
s_fault_desc_d = 32'hffff_ffff;
|
||||
s_fault_addr_d = 32'd0;
|
||||
s_fault_info_d = 32'd0;
|
||||
s_state_d = CorePublish;
|
||||
end else if (abort_i) begin
|
||||
s_state_d = CoreDrainAbort;
|
||||
end else if (quiesce_req_i) begin
|
||||
s_state_d = CoreDrainAbort;
|
||||
end
|
||||
end
|
||||
CoreDrainFault, CoreDrainAbort: begin
|
||||
if (s_drained) begin
|
||||
if (s_fault_seen_q) begin
|
||||
s_res_code_d = 3'(`APB4_NPU__RESULT_CODE_ERROR);
|
||||
s_res_irq_d = 3'b010;
|
||||
end else begin
|
||||
s_res_code_d = 3'(`APB4_NPU__RESULT_CODE_ABORTED);
|
||||
s_res_irq_d = 3'b100;
|
||||
s_fault_code_d = 4'(`APB4_NPU__FAULT_CODE_NONE);
|
||||
s_fault_desc_d = 32'hffff_ffff;
|
||||
s_fault_addr_d = 32'd0;
|
||||
s_fault_info_d = 32'd0;
|
||||
end
|
||||
s_res_completed_d = s_sch_completed;
|
||||
s_state_d = CorePublish;
|
||||
end
|
||||
end
|
||||
CoreDrainReset: begin
|
||||
if (s_drained) begin
|
||||
// stop/drain completed: re-acknowledge the fresh epoch, then
|
||||
// deliver the retained cancellation record
|
||||
s_epoch_ack_d = epoch_req_i;
|
||||
if (s_fault_seen_q) begin
|
||||
s_res_code_d = 3'(`APB4_NPU__RESULT_CODE_ERROR);
|
||||
s_res_irq_d = 3'b010;
|
||||
end else begin
|
||||
s_res_code_d = 3'(`APB4_NPU__RESULT_CODE_RESET_CANCELLED);
|
||||
s_res_irq_d = 3'b010;
|
||||
s_fault_code_d = 4'(`APB4_NPU__FAULT_CODE_RESET_CANCELLED);
|
||||
s_fault_desc_d = 32'hffff_ffff;
|
||||
s_fault_addr_d = 32'd0;
|
||||
s_fault_info_d = 32'd0;
|
||||
end
|
||||
s_res_completed_d = s_sch_completed;
|
||||
s_state_d = CorePublish;
|
||||
end
|
||||
end
|
||||
CorePublish: begin
|
||||
if (result_ready_i) begin
|
||||
s_state_d = CoreIdle;
|
||||
end
|
||||
end
|
||||
default: s_state_d = CoreIdle;
|
||||
endcase
|
||||
|
||||
// Quiesce acknowledge: idle endpoint answers immediately; an active one
|
||||
// answers once the terminal result is latched in the result mailbox.
|
||||
if (quiesce_req_i) begin
|
||||
s_quiesce_ack_d = (s_state_q == CoreIdle) || (s_state_q == CorePublish);
|
||||
end else begin
|
||||
s_quiesce_ack_d = 1'b0;
|
||||
end
|
||||
|
||||
// Epoch restart (link loss, resource reset, coordinated flush): force a
|
||||
// safe state; idle is re-acknowledged immediately, an active or
|
||||
// terminating job is cancelled and reconciled through the drain.
|
||||
if (s_epoch_restart) begin
|
||||
s_snap_state_d = SnapIdle;
|
||||
if (s_state_q == CoreIdle) begin
|
||||
s_epoch_ack_d = epoch_req_i;
|
||||
end else begin
|
||||
s_state_d = CoreDrainReset;
|
||||
end
|
||||
end
|
||||
// Coordinated fabric flush cancels transport immediately; the epoch
|
||||
// restart that follows reconciles the endpoint.
|
||||
if (flush_i && (s_state_q != CoreIdle)) begin
|
||||
s_state_d = CoreDrainReset;
|
||||
end
|
||||
end
|
||||
|
||||
assign result_valid_o = (s_state_q == CorePublish);
|
||||
assign result_data_o = {s_res_irq_q, s_fault_code_q, s_res_code_q, s_fault_info_q,
|
||||
s_fault_addr_q, s_fault_desc_q, s_res_completed_q, s_job_id_q};
|
||||
assign snapshot_resp_data_o = s_snap_payload_q;
|
||||
|
||||
always_ff @(posedge clk_hp_i or negedge rst_hp_n_i) begin
|
||||
if (!rst_hp_n_i) begin
|
||||
s_state_q <= CoreIdle;
|
||||
s_snap_state_q <= SnapIdle;
|
||||
s_job_base_q <= '0;
|
||||
s_job_id_q <= '0;
|
||||
s_timeout_q <= '0;
|
||||
s_job_count_q <= '0;
|
||||
s_term_job_id_q <= '0;
|
||||
s_fault_seen_q <= 1'b0;
|
||||
s_fault_code_q <= '0;
|
||||
s_fault_addr_q <= '0;
|
||||
s_fault_info_q <= '0;
|
||||
s_fault_desc_q <= `APB4_NPU__FAULT_DESCRIPTOR_RESET;
|
||||
s_res_code_q <= '0;
|
||||
s_res_irq_q <= '0;
|
||||
s_res_completed_q <= '0;
|
||||
s_snap_payload_q <= '0;
|
||||
s_all_done_q <= 1'b0;
|
||||
end else begin
|
||||
s_state_q <= s_state_d;
|
||||
s_snap_state_q <= s_snap_state_d;
|
||||
s_snap_payload_q <= s_snap_payload_d;
|
||||
s_fault_seen_q <= s_fault_seen_d;
|
||||
s_fault_code_q <= s_fault_code_d;
|
||||
s_fault_addr_q <= s_fault_addr_d;
|
||||
s_fault_info_q <= s_fault_info_d;
|
||||
s_fault_desc_q <= s_fault_desc_d;
|
||||
s_res_code_q <= s_res_code_d;
|
||||
s_res_irq_q <= s_res_irq_d;
|
||||
s_res_completed_q <= s_res_completed_d;
|
||||
if (s_launch_accept) begin
|
||||
s_job_base_q <= launch_data_i[`APB4_NPU__LAUNCH_JOB_BASE+:32];
|
||||
s_job_id_q <= launch_data_i[`APB4_NPU__LAUNCH_JOB_ID+:32];
|
||||
s_timeout_q <= launch_data_i[`APB4_NPU__LAUNCH_TIMEOUT_CYCLES+:32];
|
||||
s_job_count_q <= launch_data_i[`APB4_NPU__LAUNCH_JOB_COUNT+:16];
|
||||
s_fault_seen_q <= 1'b0;
|
||||
s_fault_desc_q <= `APB4_NPU__FAULT_DESCRIPTOR_RESET;
|
||||
end
|
||||
if (s_compose_terminal) begin
|
||||
s_term_job_id_q <= s_job_id_q;
|
||||
end
|
||||
if ((s_state_q == CoreIdle) || (s_state_q == CorePublish)) begin
|
||||
s_all_done_q <= 1'b0;
|
||||
end else if (s_dec_all_done) begin
|
||||
s_all_done_q <= 1'b1;
|
||||
end
|
||||
if (s_state_q == CoreIdle) begin
|
||||
s_fault_seen_q <= 1'b0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
dffr #(
|
||||
.DATA_WIDTH(EpochWidth)
|
||||
) u_epoch_ack (
|
||||
.clk_i (clk_hp_i),
|
||||
.rst_n_i(rst_hp_n_i),
|
||||
.dat_i (s_epoch_ack_d),
|
||||
.dat_o (s_epoch_ack_q)
|
||||
);
|
||||
dffr #(
|
||||
.DATA_WIDTH(1)
|
||||
) u_quiesce_ack (
|
||||
.clk_i (clk_hp_i),
|
||||
.rst_n_i(rst_hp_n_i),
|
||||
.dat_i (s_quiesce_ack_d),
|
||||
.dat_o (s_quiesce_ack_q)
|
||||
);
|
||||
|
||||
npu_job_decoder u_job_decoder (
|
||||
.clk_hp_i (clk_hp_i),
|
||||
.rst_hp_n_i (rst_hp_n_i),
|
||||
.clear_i (s_stop),
|
||||
.block_new_i (block_new_i),
|
||||
.pause_ok_o (s_dec_pause_ok),
|
||||
.job_start_i (s_dec_job_start),
|
||||
.job_base_i (launch_data_i[`APB4_NPU__LAUNCH_JOB_BASE+:32]),
|
||||
.job_count_i (launch_data_i[`APB4_NPU__LAUNCH_JOB_COUNT+:16]),
|
||||
.read_req_valid_o (s_dec_read_req_valid),
|
||||
.read_req_ready_i (s_dec_read_req_ready),
|
||||
.read_addr_o (s_dec_read_addr),
|
||||
.read_bytes_o (s_dec_read_bytes),
|
||||
.read_data_valid_i (s_dec_read_data_valid),
|
||||
.read_data_ready_o (s_dec_read_data_ready),
|
||||
.read_data_i (dma_read_data_i),
|
||||
.read_last_i (dma_read_last_i),
|
||||
.dma_read_busy_i (dma_read_busy_i),
|
||||
.dma_fault_i (dma_fault_i),
|
||||
.dma_fault_code_i (dma_fault_code_i),
|
||||
.dma_fault_addr_i (dma_fault_addr_i),
|
||||
.dma_fault_resp_i (dma_fault_resp_i),
|
||||
.rec_valid_o (s_rec_valid),
|
||||
.rec_ready_i (s_rec_ready),
|
||||
.rec_opcode_o (s_rec_opcode),
|
||||
.rec_h_o (s_rec_h),
|
||||
.rec_w_o (s_rec_w),
|
||||
.rec_cin_o (s_rec_cin),
|
||||
.rec_cout_o (s_rec_cout),
|
||||
.rec_oh_o (s_rec_oh),
|
||||
.rec_ow_o (s_rec_ow),
|
||||
.rec_input0_base_o (s_rec_input0_base),
|
||||
.rec_output_base_o (s_rec_output_base),
|
||||
.rec_weight_base_o (s_rec_weight_base),
|
||||
.rec_param_base_o (s_rec_param_base),
|
||||
.rec_input0_row_bytes_o(s_rec_irow),
|
||||
.rec_output_row_bytes_o(s_rec_orow),
|
||||
.rec_kh_o (s_rec_kh),
|
||||
.rec_kw_o (s_rec_kw),
|
||||
.rec_sh_o (s_rec_sh),
|
||||
.rec_sw_o (s_rec_sw),
|
||||
.rec_pad_top_o (s_rec_pad_top),
|
||||
.rec_pad_left_o (s_rec_pad_left),
|
||||
.rec_input0_zero_o (s_rec_in0_zero),
|
||||
.rec_weight_bytes_o(s_rec_weight_bytes),
|
||||
.rec_param_bytes_o (s_rec_param_bytes),
|
||||
.rec_desc_index_o (s_rec_desc_index),
|
||||
.retired_i (s_dec_retired),
|
||||
.all_done_o (s_dec_all_done),
|
||||
.busy_o (s_dec_busy),
|
||||
.cur_desc_index_o (s_dec_cur_index),
|
||||
.progress_o (s_dec_progress),
|
||||
.fault_req_o (s_dec_fault_req),
|
||||
.fault_code_o (s_dec_fault_code),
|
||||
.fault_addr_o (s_dec_fault_addr),
|
||||
.fault_info_o (s_dec_fault_info),
|
||||
.fault_desc_o (s_dec_fault_desc)
|
||||
);
|
||||
|
||||
npu_scheduler u_scheduler (
|
||||
.clk_hp_i (clk_hp_i),
|
||||
.rst_hp_n_i (rst_hp_n_i),
|
||||
.clear_i (s_clear),
|
||||
.stop_i (s_stop),
|
||||
.block_new_i (block_new_i),
|
||||
.pause_ok_o (s_sch_pause_ok),
|
||||
.launch_i (s_sch_launch),
|
||||
.timeout_cycles_i (launch_data_i[`APB4_NPU__LAUNCH_TIMEOUT_CYCLES+:32]),
|
||||
.job_active_i (s_state_q == CoreRun),
|
||||
.pause_active_i (pause_active_o),
|
||||
.rec_valid_i (s_rec_valid),
|
||||
.rec_ready_o (s_rec_ready),
|
||||
.rec_opcode_i (s_rec_opcode),
|
||||
.rec_h_i (s_rec_h),
|
||||
.rec_w_i (s_rec_w),
|
||||
.rec_cin_i (s_rec_cin),
|
||||
.rec_cout_i (s_rec_cout),
|
||||
.rec_oh_i (s_rec_oh),
|
||||
.rec_ow_i (s_rec_ow),
|
||||
.rec_input0_base_i (s_rec_input0_base),
|
||||
.rec_output_base_i (s_rec_output_base),
|
||||
.rec_weight_base_i (s_rec_weight_base),
|
||||
.rec_param_base_i (s_rec_param_base),
|
||||
.rec_input0_row_bytes_i(s_rec_irow),
|
||||
.rec_output_row_bytes_i(s_rec_orow),
|
||||
.rec_kh_i (s_rec_kh),
|
||||
.rec_kw_i (s_rec_kw),
|
||||
.rec_sh_i (s_rec_sh),
|
||||
.rec_sw_i (s_rec_sw),
|
||||
.rec_pad_top_i (s_rec_pad_top),
|
||||
.rec_pad_left_i (s_rec_pad_left),
|
||||
.rec_input0_zero_i (s_rec_in0_zero),
|
||||
.rec_weight_bytes_i (s_rec_weight_bytes),
|
||||
.rec_param_bytes_i (s_rec_param_bytes),
|
||||
.rec_desc_index_i (s_rec_desc_index),
|
||||
.retired_o (s_sch_retired),
|
||||
.completed_o (s_sch_completed),
|
||||
.read_req_valid_o (s_sch_read_req_valid),
|
||||
.read_req_ready_i (s_sch_read_req_ready),
|
||||
.read_addr_o (s_sch_read_addr),
|
||||
.read_bytes_o (s_sch_read_bytes),
|
||||
.read_data_valid_i (s_sch_read_data_valid),
|
||||
.read_data_ready_o (s_sch_read_data_ready),
|
||||
.read_data_i (dma_read_data_i),
|
||||
.read_keep_i (dma_read_keep_i),
|
||||
.read_last_i (dma_read_last_i),
|
||||
.write_req_valid_o (s_sch_write_req_valid),
|
||||
.write_req_ready_i (dma_write_req_ready_i),
|
||||
.write_addr_o (s_sch_write_addr),
|
||||
.write_bytes_o (s_sch_write_bytes),
|
||||
.write_data_valid_o (s_sch_write_data_valid),
|
||||
.write_data_ready_i (dma_write_data_ready_i),
|
||||
.write_data_o (s_sch_write_data),
|
||||
.write_keep_o (s_sch_write_keep),
|
||||
.write_last_o (s_sch_write_last),
|
||||
.write_done_i (dma_write_done_i),
|
||||
.dma_read_busy_i (dma_read_busy_i),
|
||||
.dma_write_busy_i (dma_write_busy_i),
|
||||
.dma_pause_ack_i (dma_pause_ack_i),
|
||||
.dma_fault_i (dma_fault_i),
|
||||
.dma_fault_code_i (dma_fault_code_i),
|
||||
.dma_fault_addr_i (dma_fault_addr_i),
|
||||
.dma_fault_resp_i (dma_fault_resp_i),
|
||||
.dma_read_bytes_i (dma_read_bytes_i),
|
||||
.dma_write_bytes_i (dma_write_bytes_i),
|
||||
.dma_stall_cycles_i (dma_stall_cycles_i),
|
||||
.progress_i (s_dec_progress),
|
||||
.terminal_i (s_sch_terminal),
|
||||
.counters_o (s_sch_counters),
|
||||
.term_counters_o (s_sch_term_counters),
|
||||
.fault_req_o (s_sch_fault_req),
|
||||
.fault_code_o (s_sch_fault_code),
|
||||
.fault_addr_o (s_sch_fault_addr),
|
||||
.fault_info_o (s_sch_fault_info),
|
||||
.fault_desc_o (s_sch_fault_desc),
|
||||
.busy_o (s_sch_busy),
|
||||
.drained_o (s_sch_drained)
|
||||
);
|
||||
end
|
||||
endmodule
|
||||
@@ -0,0 +1,729 @@
|
||||
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
|
||||
// SPDX-License-Identifier: MulanPSL-2.0
|
||||
|
||||
// NPU-P3 production AXI4 data-plane engine (docs/ip/npu.md "AXI4 data plane").
|
||||
// 32-bit addresses, 64-bit data, ID width 3 fixed to zero, USER fixed to
|
||||
// zero, INCR bursts of 1..16 beats, at most one outstanding read and one
|
||||
// outstanding write, which may overlap. AxCACHE/AxLOCK/AxPROT/AxQOS/AxREGION
|
||||
// are always zero.
|
||||
//
|
||||
// Burst planning: bulk uses full 64-bit beats; leading/trailing read edges
|
||||
// use the largest naturally aligned narrow size (1/2/4 bytes) so reads never
|
||||
// overfetch; write edges are full-width aligned beats whose scheduler-built
|
||||
// WSTRB covers only addressed lanes; no burst crosses a 4 KiB page.
|
||||
//
|
||||
// Reservation: ARVALID is presented only when the 16-entry receive FIFO is
|
||||
// empty, so an accepted read burst always completes regardless of stream
|
||||
// backpressure. A write burst gathers its complete payload into the 16-entry
|
||||
// write FIFO before AWVALID; W beats are driven only after the AW handshake.
|
||||
//
|
||||
// Command rejection: a zero byte count or an addr+bytes wrap is refused
|
||||
// combinationally (ready low, cmd_err high while presented) and is never
|
||||
// accepted; only bus-visible events raise fault_o with codes 4 (AXI_READ),
|
||||
// 5 (AXI_WRITE) or 6 (AXI_PROTOCOL), with same-cycle priority
|
||||
// AXI_PROTOCOL > AXI_READ > AXI_WRITE. fault_o is sticky until clear_i. After
|
||||
// a fault no new burst is presented and accepted obligations drain; a
|
||||
// malformed R/B response is quarantined (its receiver stops) until clear_i.
|
||||
// A write payload beat whose write_last_i does not match the planned segment
|
||||
// end pulses write_cmd_err_o and aborts the segment locally without any AXI
|
||||
// fault.
|
||||
//
|
||||
// Pause: block_new_i stops command acceptance, new burst presentation and
|
||||
// payload gathering at register-safe boundaries. Presented-but-unaccepted
|
||||
// ARVALID/AWVALID stay asserted with stable payload and accepted transfers
|
||||
// drain. pause_ack_o asserts while block_new_i is held once no AR-accepted
|
||||
// read and no AW-accepted write (W/B) obligation remains; it does not require
|
||||
// empty command slots or busy_o low, and unblocking resumes identical work.
|
||||
//
|
||||
// Counters are 64-bit saturating and clear on reset and clear_i.
|
||||
// read_bytes_o counts the addressed lanes of every accepted R beat, including
|
||||
// beats of error-response bursts. write_bytes_o counts the accepted WSTRB
|
||||
// bytes of every W beat, including bursts whose B response errors (erroneous
|
||||
// traffic stays visible). stall_cycles_o counts cycles with a ready work item
|
||||
// blocked on an AXI handshake (AR/AW/W backpressure or a pending R/B
|
||||
// response). fault_addr_o records the failing beat address (for a write
|
||||
// response, the final W beat of the failed burst) and fault_resp_o the AXI
|
||||
// response bits.
|
||||
module npu_dma (
|
||||
input logic clk_hp_i,
|
||||
input logic rst_hp_n_i,
|
||||
input logic clear_i,
|
||||
input logic block_new_i,
|
||||
output logic pause_ack_o,
|
||||
// read segment command
|
||||
input logic read_req_valid_i,
|
||||
output logic read_req_ready_o,
|
||||
input logic [31:0] read_addr_i,
|
||||
input logic [31:0] read_bytes_i,
|
||||
// read data stream
|
||||
output logic read_data_valid_o,
|
||||
input logic read_data_ready_i,
|
||||
output logic [63:0] read_data_o,
|
||||
output logic [ 7:0] read_keep_o,
|
||||
output logic read_last_o,
|
||||
// write segment command
|
||||
input logic write_req_valid_i,
|
||||
output logic write_req_ready_o,
|
||||
input logic [31:0] write_addr_i,
|
||||
input logic [31:0] write_bytes_i,
|
||||
// write payload stream
|
||||
input logic write_data_valid_i,
|
||||
output logic write_data_ready_o,
|
||||
input logic [63:0] write_data_i,
|
||||
input logic [ 7:0] write_keep_i,
|
||||
input logic write_last_i,
|
||||
output logic write_done_o,
|
||||
// status
|
||||
output logic busy_o,
|
||||
output logic read_busy_o,
|
||||
output logic write_busy_o,
|
||||
output logic [63:0] read_bytes_o,
|
||||
output logic [63:0] write_bytes_o,
|
||||
output logic [63:0] stall_cycles_o,
|
||||
output logic fault_o,
|
||||
output logic [ 3:0] fault_code_o,
|
||||
output logic [31:0] fault_addr_o,
|
||||
output logic [ 1:0] fault_resp_o,
|
||||
output logic read_cmd_err_o,
|
||||
output logic write_cmd_err_o,
|
||||
axi4_if.master axi4
|
||||
);
|
||||
localparam int unsigned FifoDepth = 16;
|
||||
localparam logic [3:0] FaultAxiRead = 4'd4;
|
||||
localparam logic [3:0] FaultAxiWrite = 4'd5;
|
||||
localparam logic [3:0] FaultAxiProtocol = 4'd6;
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
ReadIdle,
|
||||
ReadPlan,
|
||||
ReadAddress,
|
||||
ReadData
|
||||
} read_state_e;
|
||||
typedef enum logic [2:0] {
|
||||
WriteIdle,
|
||||
WritePlan,
|
||||
WriteGather,
|
||||
WriteStaged,
|
||||
WriteAddress,
|
||||
WriteData,
|
||||
WriteResponse
|
||||
} write_state_e;
|
||||
|
||||
read_state_e s_read_state_q;
|
||||
write_state_e s_write_state_q;
|
||||
|
||||
logic s_read_cmd_illegal;
|
||||
logic s_read_cmd_accept;
|
||||
logic [31:0] s_read_addr_q;
|
||||
logic [31:0] s_read_bytes_left_q;
|
||||
logic [31:0] s_read_burst_addr_q;
|
||||
logic [ 4:0] s_read_burst_beats_q;
|
||||
logic [ 2:0] s_read_burst_size_q;
|
||||
logic [ 7:0] s_read_burst_bytes_q;
|
||||
logic s_read_burst_last_q;
|
||||
logic [ 4:0] s_read_beats_left_q;
|
||||
logic [31:0] s_read_beat_addr_q;
|
||||
logic s_read_proto_fault_q;
|
||||
logic [ 4:0] s_read_plan_beats;
|
||||
logic [ 2:0] s_read_plan_size;
|
||||
logic [ 7:0] s_read_plan_bytes;
|
||||
logic [ 3:0] s_read_beat_bytes;
|
||||
logic [ 7:0] s_read_beat_keep;
|
||||
logic s_read_last_expected;
|
||||
logic s_read_proto_error;
|
||||
logic s_read_rsp_accept;
|
||||
logic s_rfifo_push;
|
||||
logic s_rfifo_pop;
|
||||
|
||||
logic s_write_cmd_illegal;
|
||||
logic s_write_cmd_accept;
|
||||
logic [31:0] s_write_addr_q;
|
||||
logic [31:0] s_write_bytes_left_q;
|
||||
logic [31:0] s_write_burst_addr_q;
|
||||
logic [ 4:0] s_write_burst_beats_q;
|
||||
logic [ 7:0] s_write_burst_bytes_q;
|
||||
logic s_write_burst_last_q;
|
||||
logic [ 4:0] s_write_beats_left_q;
|
||||
logic [31:0] s_write_beat_addr_q;
|
||||
logic [ 4:0] s_write_gather_left_q;
|
||||
logic s_write_proto_fault_q;
|
||||
logic [31:0] s_write_plan_addr;
|
||||
logic [ 4:0] s_write_plan_beats;
|
||||
logic [ 7:0] s_write_plan_bytes;
|
||||
logic s_write_plan_last;
|
||||
logic s_write_gather_accept;
|
||||
logic s_write_gather_last;
|
||||
logic s_write_stream_err;
|
||||
logic s_write_w_accept;
|
||||
logic s_write_rsp_accept;
|
||||
logic s_wfifo_push;
|
||||
logic s_wfifo_pop;
|
||||
|
||||
logic [63:0] s_rfifo_data_q [FifoDepth];
|
||||
logic [ 7:0] s_rfifo_keep_q [FifoDepth];
|
||||
logic s_rfifo_last_q [FifoDepth];
|
||||
logic [ 3:0] s_rfifo_rptr_q;
|
||||
logic [ 3:0] s_rfifo_wptr_q;
|
||||
logic [ 4:0] s_rfifo_count_q;
|
||||
logic [63:0] s_wfifo_data_q [FifoDepth];
|
||||
logic [ 7:0] s_wfifo_keep_q [FifoDepth];
|
||||
logic [ 3:0] s_wfifo_rptr_q;
|
||||
logic [ 3:0] s_wfifo_wptr_q;
|
||||
logic [ 4:0] s_wfifo_count_q;
|
||||
|
||||
logic s_fault_q;
|
||||
logic [ 3:0] s_fault_code_q;
|
||||
logic [31:0] s_fault_addr_q;
|
||||
logic [ 1:0] s_fault_resp_q;
|
||||
logic s_evt_proto_read;
|
||||
logic s_evt_axi_read;
|
||||
logic s_evt_proto_write;
|
||||
logic s_evt_axi_write;
|
||||
|
||||
logic [63:0] s_read_bytes_q;
|
||||
logic [63:0] s_write_bytes_q;
|
||||
logic [63:0] s_stall_cycles_q;
|
||||
logic s_stall_event;
|
||||
|
||||
function automatic logic [3:0] count_ones(input logic [7:0] value_i);
|
||||
logic [3:0] count;
|
||||
begin
|
||||
count = 4'd0;
|
||||
for (int unsigned lane = 0; lane < 8; lane++) begin
|
||||
count = count + {3'd0, value_i[lane]};
|
||||
end
|
||||
return count;
|
||||
end
|
||||
endfunction
|
||||
|
||||
function automatic logic [63:0] saturating_add(input logic [63:0] value_i,
|
||||
input logic [3:0] increment_i);
|
||||
logic [64:0] widened;
|
||||
begin
|
||||
widened = {1'b0, value_i} + {61'd0, increment_i};
|
||||
if (widened[64]) begin
|
||||
return 64'hffff_ffff_ffff_ffff;
|
||||
end
|
||||
return widened[63:0];
|
||||
end
|
||||
endfunction
|
||||
|
||||
assign s_read_cmd_illegal = (read_bytes_i == 32'd0) ||
|
||||
({1'b0, read_addr_i} + {1'b0, read_bytes_i} > 33'h1_0000_0000);
|
||||
assign read_req_ready_o = (s_read_state_q == ReadIdle) && !s_read_cmd_illegal && !s_fault_q &&
|
||||
!block_new_i && !clear_i;
|
||||
assign read_cmd_err_o = read_req_valid_i && s_read_cmd_illegal && !clear_i;
|
||||
assign s_read_cmd_accept = read_req_valid_i && read_req_ready_o;
|
||||
|
||||
// Read burst plan: full 64-bit beats in the bulk, the largest naturally
|
||||
// aligned narrow transfer at the leading/trailing edges, never crossing a
|
||||
// 4 KiB page, never fetching bytes outside the segment.
|
||||
always_comb begin
|
||||
logic [12:0] s_bytes_to_4k;
|
||||
logic [12:0] s_beats_to_4k;
|
||||
logic [31:0] s_full_beats;
|
||||
logic [ 3:0] s_to_alignment;
|
||||
s_bytes_to_4k = 13'd4096 - {1'b0, s_read_addr_q[11:0]};
|
||||
s_beats_to_4k = s_bytes_to_4k >> 3;
|
||||
s_full_beats = s_read_bytes_left_q >> 3;
|
||||
s_to_alignment = 4'd8 - {1'b0, s_read_addr_q[2:0]};
|
||||
s_read_plan_beats = 5'd1;
|
||||
s_read_plan_size = 3'd0;
|
||||
s_read_plan_bytes = 8'd1;
|
||||
if ((s_read_addr_q[2:0] == 3'd0) && (s_read_bytes_left_q >= 32'd8)) begin
|
||||
s_read_plan_beats = (s_full_beats > 32'd16) ? 5'd16 : s_full_beats[4:0];
|
||||
if (s_beats_to_4k < {8'd0, s_read_plan_beats}) begin
|
||||
s_read_plan_beats = s_beats_to_4k[4:0];
|
||||
end
|
||||
s_read_plan_size = 3'd3;
|
||||
s_read_plan_bytes = {s_read_plan_beats, 3'b000};
|
||||
end else if ((s_read_addr_q[1:0] == 2'd0) && (s_to_alignment >= 4'd4) &&
|
||||
(s_read_bytes_left_q >= 32'd4)) begin
|
||||
s_read_plan_size = 3'd2;
|
||||
s_read_plan_bytes = 8'd4;
|
||||
end else if ((s_read_addr_q[0] == 1'b0) && (s_to_alignment >= 4'd2) &&
|
||||
(s_read_bytes_left_q >= 32'd2)) begin
|
||||
s_read_plan_size = 3'd1;
|
||||
s_read_plan_bytes = 8'd2;
|
||||
end
|
||||
end
|
||||
|
||||
assign s_read_beat_bytes = 4'd1 << s_read_burst_size_q;
|
||||
assign s_read_beat_keep = (8'hff >> (4'd8 - s_read_beat_bytes)) << s_read_beat_addr_q[2:0];
|
||||
assign s_read_last_expected = s_read_beats_left_q == 5'd1;
|
||||
assign s_read_proto_error = (axi4.rid != '0) || (axi4.rlast != s_read_last_expected);
|
||||
assign s_read_rsp_accept = axi4.rvalid && axi4.rready;
|
||||
|
||||
assign axi4.arid = '0;
|
||||
assign axi4.araddr = s_read_burst_addr_q;
|
||||
assign axi4.arlen = {3'd0, s_read_burst_beats_q} - 8'd1;
|
||||
assign axi4.arsize = s_read_burst_size_q;
|
||||
assign axi4.arburst = 2'b01;
|
||||
assign axi4.arlock = 1'b0;
|
||||
assign axi4.arcache = 4'd0;
|
||||
assign axi4.arprot = 3'd0;
|
||||
assign axi4.arqos = 4'd0;
|
||||
assign axi4.arregion = 4'd0;
|
||||
assign axi4.aruser = '0;
|
||||
assign axi4.arvalid = (s_read_state_q == ReadAddress) && !clear_i;
|
||||
assign axi4.rready = (s_read_state_q == ReadData) && !s_read_proto_fault_q && !clear_i;
|
||||
|
||||
assign s_rfifo_push = s_read_rsp_accept && !s_read_proto_error;
|
||||
assign s_rfifo_pop = read_data_valid_o && read_data_ready_i;
|
||||
assign read_data_valid_o = (s_rfifo_count_q != 5'd0) && !clear_i;
|
||||
assign read_data_o = s_rfifo_data_q[s_rfifo_rptr_q];
|
||||
assign read_keep_o = s_rfifo_keep_q[s_rfifo_rptr_q];
|
||||
assign read_last_o = s_rfifo_last_q[s_rfifo_rptr_q];
|
||||
|
||||
assign s_write_cmd_illegal = (write_bytes_i == 32'd0) ||
|
||||
({1'b0, write_addr_i} + {1'b0, write_bytes_i} > 33'h1_0000_0000);
|
||||
assign write_req_ready_o = (s_write_state_q == WriteIdle) && !s_write_cmd_illegal &&
|
||||
!s_fault_q && !block_new_i && !clear_i;
|
||||
assign write_cmd_err_o = (write_req_valid_i && s_write_cmd_illegal && !clear_i) ||
|
||||
s_write_stream_err;
|
||||
assign s_write_cmd_accept = write_req_valid_i && write_req_ready_o;
|
||||
|
||||
// Write burst plan: the address-aligned beat span of the remaining segment,
|
||||
// at most 16 beats, never crossing a 4 KiB page. Byte lanes outside the
|
||||
// segment are left to the scheduler-built WSTRB.
|
||||
always_comb begin
|
||||
logic [12:0] s_bytes_to_4k;
|
||||
logic [12:0] s_beats_to_4k;
|
||||
logic [31:0] s_span_beats;
|
||||
logic [31:0] s_burst_room;
|
||||
s_write_plan_addr = {s_write_addr_q[31:3], 3'b000};
|
||||
s_bytes_to_4k = 13'd4096 - {1'b0, s_write_plan_addr[11:0]};
|
||||
s_beats_to_4k = s_bytes_to_4k >> 3;
|
||||
s_span_beats = ({29'd0, s_write_addr_q[2:0]} + s_write_bytes_left_q + 32'd7) >> 3;
|
||||
s_write_plan_beats = (s_span_beats > 32'd16) ? 5'd16 : s_span_beats[4:0];
|
||||
if (s_beats_to_4k < {8'd0, s_write_plan_beats}) begin
|
||||
s_write_plan_beats = s_beats_to_4k[4:0];
|
||||
end
|
||||
s_burst_room = ({24'd0, s_write_plan_beats, 3'b000} - {29'd0, s_write_addr_q[2:0]});
|
||||
s_write_plan_last = s_write_bytes_left_q <= s_burst_room;
|
||||
s_write_plan_bytes = s_write_plan_last ? s_write_bytes_left_q[7:0] : s_burst_room[7:0];
|
||||
end
|
||||
|
||||
assign axi4.awid = '0;
|
||||
assign axi4.awaddr = s_write_burst_addr_q;
|
||||
assign axi4.awlen = {3'd0, s_write_burst_beats_q} - 8'd1;
|
||||
assign axi4.awsize = 3'd3;
|
||||
assign axi4.awburst = 2'b01;
|
||||
assign axi4.awlock = 1'b0;
|
||||
assign axi4.awcache = 4'd0;
|
||||
assign axi4.awprot = 3'd0;
|
||||
assign axi4.awqos = 4'd0;
|
||||
assign axi4.awregion = 4'd0;
|
||||
assign axi4.awuser = '0;
|
||||
assign axi4.awvalid = (s_write_state_q == WriteAddress) && !clear_i;
|
||||
assign axi4.wdata = s_wfifo_data_q[s_wfifo_rptr_q];
|
||||
assign axi4.wstrb = s_wfifo_keep_q[s_wfifo_rptr_q];
|
||||
assign axi4.wlast = s_write_beats_left_q == 5'd1;
|
||||
assign axi4.wuser = '0;
|
||||
assign axi4.wvalid = (s_write_state_q == WriteData) && (s_wfifo_count_q != 5'd0) && !clear_i;
|
||||
assign axi4.bready = (s_write_state_q == WriteResponse) && !s_write_proto_fault_q && !clear_i;
|
||||
assign s_write_w_accept = axi4.wvalid && axi4.wready;
|
||||
assign s_write_rsp_accept = axi4.bvalid && axi4.bready;
|
||||
|
||||
assign write_data_ready_o = (s_write_state_q == WriteGather) && !block_new_i && !s_fault_q &&
|
||||
!clear_i;
|
||||
assign s_write_gather_accept = write_data_valid_i && write_data_ready_o;
|
||||
assign s_write_gather_last = (s_write_gather_left_q == 5'd1) && s_write_burst_last_q;
|
||||
assign s_write_stream_err = s_write_gather_accept && (write_last_i != s_write_gather_last);
|
||||
assign s_wfifo_push = s_write_gather_accept && !s_write_stream_err;
|
||||
assign s_wfifo_pop = s_write_w_accept;
|
||||
assign write_done_o = s_write_rsp_accept && (axi4.bid == '0) && (axi4.bresp == 2'b00) &&
|
||||
s_write_burst_last_q;
|
||||
|
||||
assign s_evt_proto_read = axi4.rvalid && !clear_i && !s_read_proto_fault_q &&
|
||||
((s_read_state_q != ReadData) || s_read_proto_error);
|
||||
assign s_evt_axi_read = s_read_rsp_accept && (axi4.rresp != 2'b00);
|
||||
assign s_evt_proto_write = axi4.bvalid && !clear_i && !s_write_proto_fault_q &&
|
||||
((s_write_state_q != WriteResponse) || (axi4.bid != '0));
|
||||
assign s_evt_axi_write = s_write_rsp_accept && (axi4.bresp != 2'b00);
|
||||
|
||||
assign s_stall_event = (axi4.arvalid && !axi4.arready) || (axi4.awvalid && !axi4.awready) ||
|
||||
(axi4.wvalid && !axi4.wready) ||
|
||||
((s_read_state_q == ReadData) && !axi4.rvalid && !s_read_proto_fault_q) ||
|
||||
((s_write_state_q == WriteResponse) && !axi4.bvalid &&
|
||||
!s_write_proto_fault_q);
|
||||
|
||||
assign read_busy_o = s_read_state_q != ReadIdle;
|
||||
assign write_busy_o = s_write_state_q != WriteIdle;
|
||||
assign busy_o = read_busy_o || write_busy_o;
|
||||
// Zero accepted obligations: a presented-but-unaccepted AR/AW does not
|
||||
// block the acknowledge, an AR-accepted read or AW-accepted write does.
|
||||
assign pause_ack_o = block_new_i && (s_read_state_q != ReadData) &&
|
||||
(s_write_state_q != WriteData) && (s_write_state_q != WriteResponse);
|
||||
|
||||
assign read_bytes_o = s_read_bytes_q;
|
||||
assign write_bytes_o = s_write_bytes_q;
|
||||
assign stall_cycles_o = s_stall_cycles_q;
|
||||
assign fault_o = s_fault_q;
|
||||
assign fault_code_o = s_fault_code_q;
|
||||
assign fault_addr_o = s_fault_addr_q;
|
||||
assign fault_resp_o = s_fault_resp_q;
|
||||
|
||||
always_ff @(posedge clk_hp_i or negedge rst_hp_n_i) begin
|
||||
if (!rst_hp_n_i) begin
|
||||
s_read_state_q <= ReadIdle;
|
||||
s_read_addr_q <= '0;
|
||||
s_read_bytes_left_q <= '0;
|
||||
s_read_burst_addr_q <= '0;
|
||||
s_read_burst_beats_q <= '0;
|
||||
s_read_burst_size_q <= '0;
|
||||
s_read_burst_bytes_q <= '0;
|
||||
s_read_burst_last_q <= 1'b0;
|
||||
s_read_beats_left_q <= '0;
|
||||
s_read_beat_addr_q <= '0;
|
||||
s_read_proto_fault_q <= 1'b0;
|
||||
s_write_state_q <= WriteIdle;
|
||||
s_write_addr_q <= '0;
|
||||
s_write_bytes_left_q <= '0;
|
||||
s_write_burst_addr_q <= '0;
|
||||
s_write_burst_beats_q <= '0;
|
||||
s_write_burst_bytes_q <= '0;
|
||||
s_write_burst_last_q <= 1'b0;
|
||||
s_write_beats_left_q <= '0;
|
||||
s_write_beat_addr_q <= '0;
|
||||
s_write_gather_left_q <= '0;
|
||||
s_write_proto_fault_q <= 1'b0;
|
||||
s_rfifo_rptr_q <= '0;
|
||||
s_rfifo_wptr_q <= '0;
|
||||
s_rfifo_count_q <= '0;
|
||||
s_wfifo_rptr_q <= '0;
|
||||
s_wfifo_wptr_q <= '0;
|
||||
s_wfifo_count_q <= '0;
|
||||
s_fault_q <= 1'b0;
|
||||
s_fault_code_q <= '0;
|
||||
s_fault_addr_q <= '0;
|
||||
s_fault_resp_q <= '0;
|
||||
s_read_bytes_q <= '0;
|
||||
s_write_bytes_q <= '0;
|
||||
s_stall_cycles_q <= '0;
|
||||
end else if (clear_i) begin
|
||||
// Coordinated fabric flush: in-flight state cancels together with the
|
||||
// fabric; afterwards the engine is clean, idle and fault-free.
|
||||
s_read_state_q <= ReadIdle;
|
||||
s_read_proto_fault_q <= 1'b0;
|
||||
s_write_state_q <= WriteIdle;
|
||||
s_write_proto_fault_q <= 1'b0;
|
||||
s_rfifo_rptr_q <= '0;
|
||||
s_rfifo_wptr_q <= '0;
|
||||
s_rfifo_count_q <= '0;
|
||||
s_wfifo_rptr_q <= '0;
|
||||
s_wfifo_wptr_q <= '0;
|
||||
s_wfifo_count_q <= '0;
|
||||
s_fault_q <= 1'b0;
|
||||
s_fault_code_q <= '0;
|
||||
s_fault_addr_q <= '0;
|
||||
s_fault_resp_q <= '0;
|
||||
s_read_bytes_q <= '0;
|
||||
s_write_bytes_q <= '0;
|
||||
s_stall_cycles_q <= '0;
|
||||
end else begin
|
||||
unique case (s_read_state_q)
|
||||
ReadIdle: begin
|
||||
if (s_read_cmd_accept) begin
|
||||
s_read_addr_q <= read_addr_i;
|
||||
s_read_bytes_left_q <= read_bytes_i;
|
||||
s_read_state_q <= ReadPlan;
|
||||
end
|
||||
end
|
||||
ReadPlan: begin
|
||||
// Reserve receive storage for the whole burst before presenting AR.
|
||||
if (!block_new_i && !s_fault_q && (s_rfifo_count_q == 5'd0)) begin
|
||||
s_read_burst_addr_q <= s_read_addr_q;
|
||||
s_read_burst_beats_q <= s_read_plan_beats;
|
||||
s_read_burst_size_q <= s_read_plan_size;
|
||||
s_read_burst_bytes_q <= s_read_plan_bytes;
|
||||
s_read_burst_last_q <= {24'd0, s_read_plan_bytes} == s_read_bytes_left_q;
|
||||
s_read_beats_left_q <= s_read_plan_beats;
|
||||
s_read_beat_addr_q <= s_read_addr_q;
|
||||
s_read_proto_fault_q <= 1'b0;
|
||||
s_read_state_q <= ReadAddress;
|
||||
end
|
||||
end
|
||||
ReadAddress: begin
|
||||
if (axi4.arvalid && axi4.arready) begin
|
||||
s_read_state_q <= ReadData;
|
||||
end
|
||||
end
|
||||
ReadData: begin
|
||||
if (s_read_rsp_accept) begin
|
||||
if (s_read_proto_error) begin
|
||||
// Malformed responses are quarantined until the flush.
|
||||
s_read_proto_fault_q <= 1'b1;
|
||||
end else if (s_read_beats_left_q == 5'd1) begin
|
||||
s_read_bytes_left_q <= s_read_bytes_left_q - {24'd0, s_read_burst_bytes_q};
|
||||
s_read_addr_q <= s_read_burst_addr_q + {24'd0, s_read_burst_bytes_q};
|
||||
if (s_fault_q || s_evt_axi_read || s_read_burst_last_q) begin
|
||||
s_read_state_q <= ReadIdle;
|
||||
end else begin
|
||||
s_read_state_q <= ReadPlan;
|
||||
end
|
||||
end else begin
|
||||
s_read_beats_left_q <= s_read_beats_left_q - 1'b1;
|
||||
s_read_beat_addr_q <= s_read_beat_addr_q + {28'd0, s_read_beat_bytes};
|
||||
end
|
||||
end
|
||||
end
|
||||
default: s_read_state_q <= ReadIdle;
|
||||
endcase
|
||||
|
||||
unique case (s_write_state_q)
|
||||
WriteIdle: begin
|
||||
if (s_write_cmd_accept) begin
|
||||
s_write_addr_q <= write_addr_i;
|
||||
s_write_bytes_left_q <= write_bytes_i;
|
||||
s_write_state_q <= WritePlan;
|
||||
end
|
||||
end
|
||||
WritePlan: begin
|
||||
s_write_burst_addr_q <= s_write_plan_addr;
|
||||
s_write_burst_beats_q <= s_write_plan_beats;
|
||||
s_write_burst_bytes_q <= s_write_plan_bytes;
|
||||
s_write_burst_last_q <= s_write_plan_last;
|
||||
s_write_beats_left_q <= s_write_plan_beats;
|
||||
s_write_gather_left_q <= s_write_plan_beats;
|
||||
s_write_beat_addr_q <= s_write_plan_addr;
|
||||
s_write_proto_fault_q <= 1'b0;
|
||||
s_write_state_q <= WriteGather;
|
||||
end
|
||||
WriteGather: begin
|
||||
// Reserve the complete write payload before presenting AW.
|
||||
if (s_write_stream_err) begin
|
||||
// Locally rejected segment: drop the partial payload.
|
||||
s_write_state_q <= WriteIdle;
|
||||
s_wfifo_rptr_q <= '0;
|
||||
s_wfifo_wptr_q <= '0;
|
||||
s_wfifo_count_q <= '0;
|
||||
end else if (s_write_gather_accept) begin
|
||||
if (s_write_gather_left_q == 5'd1) begin
|
||||
s_write_state_q <= WriteStaged;
|
||||
end else begin
|
||||
s_write_gather_left_q <= s_write_gather_left_q - 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
WriteStaged: begin
|
||||
if (!block_new_i && !s_fault_q) begin
|
||||
s_write_state_q <= WriteAddress;
|
||||
end
|
||||
end
|
||||
WriteAddress: begin
|
||||
if (axi4.awvalid && axi4.awready) begin
|
||||
s_write_state_q <= WriteData;
|
||||
end
|
||||
end
|
||||
WriteData: begin
|
||||
if (s_write_w_accept) begin
|
||||
if (s_write_beats_left_q == 5'd1) begin
|
||||
s_write_state_q <= WriteResponse;
|
||||
end else begin
|
||||
s_write_beats_left_q <= s_write_beats_left_q - 1'b1;
|
||||
s_write_beat_addr_q <= s_write_beat_addr_q + 32'd8;
|
||||
end
|
||||
end
|
||||
end
|
||||
WriteResponse: begin
|
||||
if (s_write_rsp_accept) begin
|
||||
if (axi4.bid != '0) begin
|
||||
// Malformed responses are quarantined until the flush.
|
||||
s_write_proto_fault_q <= 1'b1;
|
||||
end else begin
|
||||
s_write_bytes_left_q <= s_write_bytes_left_q - {24'd0, s_write_burst_bytes_q};
|
||||
s_write_addr_q <= s_write_burst_addr_q + {24'd0, s_write_burst_beats_q, 3'b000};
|
||||
if (s_fault_q || s_evt_axi_write || s_write_burst_last_q) begin
|
||||
s_write_state_q <= WriteIdle;
|
||||
end else begin
|
||||
s_write_state_q <= WritePlan;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
default: s_write_state_q <= WriteIdle;
|
||||
endcase
|
||||
|
||||
if (s_rfifo_push) begin
|
||||
s_rfifo_data_q[s_rfifo_wptr_q] <= axi4.rdata;
|
||||
s_rfifo_keep_q[s_rfifo_wptr_q] <= s_read_beat_keep;
|
||||
s_rfifo_last_q[s_rfifo_wptr_q] <= (s_read_beats_left_q == 5'd1) && s_read_burst_last_q;
|
||||
s_rfifo_wptr_q <= s_rfifo_wptr_q + 1'b1;
|
||||
end
|
||||
if (s_rfifo_pop) begin
|
||||
s_rfifo_rptr_q <= s_rfifo_rptr_q + 1'b1;
|
||||
end
|
||||
unique case ({
|
||||
s_rfifo_push, s_rfifo_pop
|
||||
})
|
||||
2'b10: s_rfifo_count_q <= s_rfifo_count_q + 1'b1;
|
||||
2'b01: s_rfifo_count_q <= s_rfifo_count_q - 1'b1;
|
||||
default: begin
|
||||
end
|
||||
endcase
|
||||
|
||||
if (s_wfifo_push) begin
|
||||
s_wfifo_data_q[s_wfifo_wptr_q] <= write_data_i;
|
||||
s_wfifo_keep_q[s_wfifo_wptr_q] <= write_keep_i;
|
||||
s_wfifo_wptr_q <= s_wfifo_wptr_q + 1'b1;
|
||||
end
|
||||
if (s_wfifo_pop) begin
|
||||
s_wfifo_rptr_q <= s_wfifo_rptr_q + 1'b1;
|
||||
end
|
||||
unique case ({
|
||||
s_wfifo_push, s_wfifo_pop
|
||||
})
|
||||
2'b10: s_wfifo_count_q <= s_wfifo_count_q + 1'b1;
|
||||
2'b01: s_wfifo_count_q <= s_wfifo_count_q - 1'b1;
|
||||
default: begin
|
||||
end
|
||||
endcase
|
||||
|
||||
if (!s_fault_q) begin
|
||||
// Deterministic first-fault priority: AXI_PROTOCOL, AXI_READ, AXI_WRITE.
|
||||
if (s_evt_proto_read || s_evt_proto_write) begin
|
||||
s_fault_q <= 1'b1;
|
||||
s_fault_code_q <= FaultAxiProtocol;
|
||||
s_fault_addr_q <= s_evt_proto_read ? s_read_beat_addr_q : s_write_beat_addr_q;
|
||||
s_fault_resp_q <= s_evt_proto_read ? axi4.rresp : axi4.bresp;
|
||||
end else if (s_evt_axi_read) begin
|
||||
s_fault_q <= 1'b1;
|
||||
s_fault_code_q <= FaultAxiRead;
|
||||
s_fault_addr_q <= s_read_beat_addr_q;
|
||||
s_fault_resp_q <= axi4.rresp;
|
||||
end else if (s_evt_axi_write) begin
|
||||
s_fault_q <= 1'b1;
|
||||
s_fault_code_q <= FaultAxiWrite;
|
||||
s_fault_addr_q <= s_write_beat_addr_q;
|
||||
s_fault_resp_q <= axi4.bresp;
|
||||
end
|
||||
end
|
||||
|
||||
if (s_read_rsp_accept) begin
|
||||
s_read_bytes_q <= saturating_add(s_read_bytes_q, count_ones(s_read_beat_keep));
|
||||
end
|
||||
if (s_write_w_accept) begin
|
||||
s_write_bytes_q <= saturating_add(s_write_bytes_q, count_ones(axi4.wstrb));
|
||||
end
|
||||
if (s_stall_event && (s_stall_cycles_q != 64'hffff_ffff_ffff_ffff)) begin
|
||||
s_stall_cycles_q <= s_stall_cycles_q + 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
`ifndef SV_ASSRT_DISABLE
|
||||
logic s_aw_wait_q;
|
||||
logic s_ar_wait_q;
|
||||
logic s_w_wait_q;
|
||||
logic s_r_wait_q;
|
||||
logic s_b_wait_q;
|
||||
logic s_read_outstanding_q;
|
||||
logic s_write_outstanding_q;
|
||||
logic s_write_aw_accepted_q;
|
||||
logic s_pause_ack_q;
|
||||
logic s_arvalid_q;
|
||||
logic s_awvalid_q;
|
||||
logic s_reset_seen_q;
|
||||
|
||||
always_ff @(posedge clk_hp_i or negedge rst_hp_n_i) begin
|
||||
if (!rst_hp_n_i) begin
|
||||
s_aw_wait_q <= 1'b0;
|
||||
s_ar_wait_q <= 1'b0;
|
||||
s_w_wait_q <= 1'b0;
|
||||
s_r_wait_q <= 1'b0;
|
||||
s_b_wait_q <= 1'b0;
|
||||
s_read_outstanding_q <= 1'b0;
|
||||
s_write_outstanding_q <= 1'b0;
|
||||
s_write_aw_accepted_q <= 1'b0;
|
||||
s_pause_ack_q <= 1'b0;
|
||||
s_arvalid_q <= 1'b0;
|
||||
s_awvalid_q <= 1'b0;
|
||||
s_reset_seen_q <= 1'b1;
|
||||
end else begin
|
||||
s_reset_seen_q <= 1'b0;
|
||||
s_pause_ack_q <= pause_ack_o;
|
||||
s_arvalid_q <= axi4.arvalid;
|
||||
s_awvalid_q <= axi4.awvalid;
|
||||
if (clear_i) begin
|
||||
s_aw_wait_q <= 1'b0;
|
||||
s_ar_wait_q <= 1'b0;
|
||||
s_w_wait_q <= 1'b0;
|
||||
s_r_wait_q <= 1'b0;
|
||||
s_b_wait_q <= 1'b0;
|
||||
s_read_outstanding_q <= 1'b0;
|
||||
s_write_outstanding_q <= 1'b0;
|
||||
s_write_aw_accepted_q <= 1'b0;
|
||||
end else begin
|
||||
s_aw_wait_q <= axi4.awvalid && !axi4.awready;
|
||||
s_ar_wait_q <= axi4.arvalid && !axi4.arready;
|
||||
s_w_wait_q <= axi4.wvalid && !axi4.wready;
|
||||
s_r_wait_q <= axi4.rvalid && !axi4.rready;
|
||||
s_b_wait_q <= axi4.bvalid && !axi4.bready;
|
||||
if (axi4.arvalid && axi4.arready) begin
|
||||
s_read_outstanding_q <= 1'b1;
|
||||
end else if (s_read_rsp_accept && (s_read_beats_left_q == 5'd1)) begin
|
||||
s_read_outstanding_q <= 1'b0;
|
||||
end
|
||||
if (axi4.awvalid && axi4.awready) begin
|
||||
s_write_outstanding_q <= 1'b1;
|
||||
s_write_aw_accepted_q <= 1'b1;
|
||||
end
|
||||
if (s_write_rsp_accept) begin
|
||||
s_write_outstanding_q <= 1'b0;
|
||||
end
|
||||
if (s_write_w_accept && (s_write_beats_left_q == 5'd1)) begin
|
||||
s_write_aw_accepted_q <= 1'b0;
|
||||
end
|
||||
end
|
||||
// VALID payloads stay stable through any stall; only the coordinated
|
||||
// fabric flush may invalidate a presented transaction.
|
||||
if (s_aw_wait_q) begin
|
||||
assert ((axi4.awvalid && $stable({axi4.awaddr, axi4.awlen, axi4.awsize})) || clear_i);
|
||||
end
|
||||
if (s_ar_wait_q) begin
|
||||
assert ((axi4.arvalid && $stable({axi4.araddr, axi4.arlen, axi4.arsize})) || clear_i);
|
||||
end
|
||||
if (s_w_wait_q) begin
|
||||
assert ((axi4.wvalid && $stable({axi4.wdata, axi4.wstrb, axi4.wlast})) || clear_i);
|
||||
end
|
||||
if (s_r_wait_q) begin
|
||||
assert ((axi4.rvalid && $stable({axi4.rid, axi4.rdata, axi4.rresp, axi4.rlast})) ||
|
||||
clear_i);
|
||||
end
|
||||
if (s_b_wait_q) begin
|
||||
assert ((axi4.bvalid && $stable({axi4.bid, axi4.bresp})) || clear_i);
|
||||
end
|
||||
// W is only ever driven after its AW handshake.
|
||||
if (axi4.wvalid) begin
|
||||
assert (s_write_aw_accepted_q);
|
||||
end
|
||||
// At most one outstanding transaction per direction.
|
||||
if (axi4.arvalid && axi4.arready) begin
|
||||
assert (!s_read_outstanding_q);
|
||||
end
|
||||
if (axi4.awvalid && axi4.awready) begin
|
||||
assert (!s_write_outstanding_q);
|
||||
end
|
||||
// No new AR/AW presentation begins while the pause is acknowledged.
|
||||
if (s_pause_ack_q && !s_arvalid_q) begin
|
||||
assert (!axi4.arvalid);
|
||||
end
|
||||
if (s_pause_ack_q && !s_awvalid_q) begin
|
||||
assert (!axi4.awvalid);
|
||||
end
|
||||
// Address valids stay low out of reset.
|
||||
if (s_reset_seen_q) begin
|
||||
assert (!axi4.arvalid && !axi4.awvalid && !axi4.wvalid);
|
||||
end
|
||||
// Reserved storage can never overflow.
|
||||
if (s_rfifo_push) begin
|
||||
assert (s_rfifo_count_q < 5'd16);
|
||||
end
|
||||
if (s_wfifo_push) begin
|
||||
assert (s_wfifo_count_q < 5'd16);
|
||||
end
|
||||
end
|
||||
end
|
||||
`endif
|
||||
endmodule
|
||||
@@ -0,0 +1,981 @@
|
||||
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
|
||||
// SPDX-License-Identifier: MulanPSL-2.0
|
||||
|
||||
// NPU-P3 job decoder: bounded linear fetch and full ABI-1.0 validation of the
|
||||
// 128-byte descriptor records of one job (docs/ip/npu.md "Descriptor ABI 1.0"
|
||||
// and "Job submission and ordering"). Each record is fetched through the
|
||||
// npu_dma read channel into 32 private staging words, validated in the exact
|
||||
// check order of the golden host validator scripts/npu_descriptors.py
|
||||
// (structural record, opcode support, unused fields, geometry, operator,
|
||||
// strides/spans, alignment, overlap) with widened no-wrap arithmetic, and is
|
||||
// then emitted to the scheduler as a typed execute record. Records are
|
||||
// fetched, validated, executed and retired strictly in order; a validation
|
||||
// failure or a fetch bus fault is terminal for the job and earlier records
|
||||
// are not rolled back.
|
||||
//
|
||||
// Fault metadata conventions: a validation fault reports the record fetch
|
||||
// address plus 4*word (the ABI word index) on fault_addr_o; overlap faults
|
||||
// and the descriptor-region wrap carry no single word and report the record
|
||||
// base. A fetch fault forwards the DMA code (AXI_READ or AXI_PROTOCOL), the
|
||||
// failing beat address and the AXI response, with FAULT_INFO direction=read
|
||||
// and the not-applicable lane 255. A descriptor address or region that wraps
|
||||
// the 32-bit space is refused locally as RANGE before any bus request.
|
||||
`include "npu_define.svh"
|
||||
|
||||
module npu_job_decoder (
|
||||
input logic clk_hp_i,
|
||||
input logic rst_hp_n_i,
|
||||
// coordinated cancel from npu_core (abort/quiesce/reset/flush): stop at
|
||||
// the current segment boundary, drain the in-flight fetch, then idle
|
||||
input logic clear_i,
|
||||
input logic block_new_i,
|
||||
output logic pause_ok_o,
|
||||
// job launch from npu_core
|
||||
input logic job_start_i,
|
||||
input logic [31:0] job_base_i,
|
||||
input logic [15:0] job_count_i,
|
||||
// npu_dma read command channel (owned while fetching)
|
||||
output logic read_req_valid_o,
|
||||
input logic read_req_ready_i,
|
||||
output logic [31:0] read_addr_o,
|
||||
output logic [31:0] read_bytes_o,
|
||||
// npu_dma read stream
|
||||
input logic read_data_valid_i,
|
||||
output logic read_data_ready_o,
|
||||
input logic [63:0] read_data_i,
|
||||
input logic read_last_i,
|
||||
// npu_dma status
|
||||
input logic dma_read_busy_i,
|
||||
input logic dma_fault_i,
|
||||
input logic [ 3:0] dma_fault_code_i,
|
||||
input logic [31:0] dma_fault_addr_i,
|
||||
input logic [ 1:0] dma_fault_resp_i,
|
||||
// validated execute record to the scheduler
|
||||
output logic rec_valid_o,
|
||||
input logic rec_ready_i,
|
||||
output logic [ 3:0] rec_opcode_o,
|
||||
output logic [12:0] rec_h_o,
|
||||
output logic [12:0] rec_w_o,
|
||||
output logic [12:0] rec_cin_o,
|
||||
output logic [12:0] rec_cout_o,
|
||||
output logic [12:0] rec_oh_o,
|
||||
output logic [12:0] rec_ow_o,
|
||||
output logic [31:0] rec_input0_base_o,
|
||||
output logic [31:0] rec_output_base_o,
|
||||
output logic [31:0] rec_weight_base_o,
|
||||
output logic [31:0] rec_param_base_o,
|
||||
output logic [31:0] rec_input0_row_bytes_o,
|
||||
output logic [31:0] rec_output_row_bytes_o,
|
||||
output logic [ 7:0] rec_kh_o,
|
||||
output logic [ 7:0] rec_kw_o,
|
||||
output logic [ 7:0] rec_sh_o,
|
||||
output logic [ 7:0] rec_sw_o,
|
||||
output logic [ 7:0] rec_pad_top_o,
|
||||
output logic [ 7:0] rec_pad_left_o,
|
||||
output logic [ 7:0] rec_input0_zero_o,
|
||||
output logic [31:0] rec_weight_bytes_o,
|
||||
output logic [31:0] rec_param_bytes_o,
|
||||
output logic [15:0] rec_desc_index_o,
|
||||
// scheduler retirement handshake
|
||||
input logic retired_i,
|
||||
output logic all_done_o,
|
||||
output logic busy_o,
|
||||
output logic [15:0] cur_desc_index_o,
|
||||
// one-cycle progress pulse for the job no-progress watchdog
|
||||
output logic progress_o,
|
||||
// terminal fault report (held until clear_i)
|
||||
output logic fault_req_o,
|
||||
output logic [ 3:0] fault_code_o,
|
||||
output logic [31:0] fault_addr_o,
|
||||
output logic [31:0] fault_info_o,
|
||||
output logic [31:0] fault_desc_o
|
||||
);
|
||||
localparam logic [31:0] FaultInfoInternal = {16'd0, 8'd255, 4'd0, 2'd0, 2'd0};
|
||||
|
||||
typedef enum logic [4:0] {
|
||||
DecIdle,
|
||||
DecFetchCmd,
|
||||
DecFetchData,
|
||||
DecDrain,
|
||||
DecCompute,
|
||||
DecCompute2,
|
||||
DecCompute3,
|
||||
DecStruct1,
|
||||
DecStruct2,
|
||||
DecStruct3,
|
||||
DecStruct4,
|
||||
DecOpcode,
|
||||
DecUnused,
|
||||
DecGeom1,
|
||||
DecGeom2,
|
||||
DecOper,
|
||||
DecOperCommon,
|
||||
DecStride,
|
||||
DecAlign,
|
||||
DecOverlap,
|
||||
DecEmit,
|
||||
DecWaitRetire,
|
||||
DecFault
|
||||
} state_e;
|
||||
|
||||
state_e s_state_d, s_state_q;
|
||||
logic [31:0] s_words_q[32];
|
||||
logic [31:0] s_base_d, s_base_q;
|
||||
logic [15:0] s_count_d, s_count_q;
|
||||
logic [15:0] s_index_d, s_index_q;
|
||||
logic [ 4:0] s_beat_q;
|
||||
logic [ 3:0] s_fault_code_q;
|
||||
logic [31:0] s_fault_addr_q;
|
||||
logic [31:0] s_fault_info_q;
|
||||
logic [15:0] s_fault_index_q;
|
||||
logic [31:0] s_desc_addr;
|
||||
logic s_fetch_wrap;
|
||||
logic s_cmd_accept;
|
||||
logic s_stream_accept;
|
||||
logic s_last_accept;
|
||||
|
||||
// decoded record views of the staged words
|
||||
logic [ 7:0] s_opcode;
|
||||
logic [15:0] s_dim_h, s_dim_w, s_dim_cin, s_dim_cout, s_dim_oh, s_dim_ow;
|
||||
logic [7:0] s_kh, s_kw, s_sh, s_sw;
|
||||
logic [7:0] s_pad_t, s_pad_b, s_pad_l, s_pad_r;
|
||||
logic [7:0] s_tile_h, s_tile_w;
|
||||
logic s_is_kernel_op;
|
||||
logic s_is_weighted_op;
|
||||
logic s_is_param_op;
|
||||
logic s_is_add;
|
||||
logic s_is_gap;
|
||||
logic signed [31:0] s_extent_h;
|
||||
logic signed [31:0] s_extent_w;
|
||||
logic [15:0] s_khkw;
|
||||
logic [63:0] s_full_k;
|
||||
logic [63:0] s_weight_expect;
|
||||
logic [63:0] s_span_in0;
|
||||
logic [63:0] s_span_in1;
|
||||
logic [63:0] s_span_out;
|
||||
logic [63:0] s_min_row_in;
|
||||
logic [63:0] s_min_row_out;
|
||||
logic [63:0] s_region_end;
|
||||
|
||||
// registered derived quantities (staged across DecCompute..DecCompute3 with
|
||||
// at most one multiply level per stage, consumed by the check sequencer:
|
||||
// keeps the wide multiplies/compares off the FSM path)
|
||||
logic signed [31:0] s_extent_h_q;
|
||||
logic signed [31:0] s_extent_w_q;
|
||||
logic [15:0] s_khkw_q;
|
||||
logic [63:0] s_full_k_q;
|
||||
logic [63:0] s_weight_expect_q;
|
||||
logic [63:0] s_span_in0_q;
|
||||
logic [63:0] s_span_in1_q;
|
||||
logic [63:0] s_span_out_q;
|
||||
logic [63:0] s_min_row_in_q;
|
||||
logic [63:0] s_min_row_out_q;
|
||||
logic [63:0] s_region_end_q;
|
||||
logic [63:0] s_rng_base_q [6];
|
||||
logic [63:0] s_rng_end_q [6];
|
||||
logic s_overlap_pair_q;
|
||||
|
||||
// per-check-state combinational verdict
|
||||
logic s_chk_fail;
|
||||
logic [ 3:0] s_chk_code;
|
||||
logic [ 5:0] s_chk_word;
|
||||
logic s_chk_wordless;
|
||||
|
||||
// overlap ranges
|
||||
logic [63:0] s_rng_base [6];
|
||||
logic [63:0] s_rng_end [6];
|
||||
logic [ 5:0] s_rng_valid;
|
||||
logic s_overlap_pair;
|
||||
|
||||
assign s_desc_addr = s_base_q + {9'd0, s_index_q, 7'd0};
|
||||
assign s_fetch_wrap = ({1'b0, s_desc_addr} + 33'd128) > 33'h1_0000_0000;
|
||||
assign s_cmd_accept = read_req_valid_o && read_req_ready_i;
|
||||
assign s_stream_accept = read_data_valid_i && read_data_ready_o;
|
||||
assign s_last_accept = s_stream_accept && read_last_i;
|
||||
|
||||
assign s_opcode = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_VERSION_OPCODE][7:0];
|
||||
assign s_dim_h = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_INPUT_HW][15:0];
|
||||
assign s_dim_w = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_INPUT_HW][31:16];
|
||||
assign s_dim_cin = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_CHANNELS][15:0];
|
||||
assign s_dim_cout = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_CHANNELS][31:16];
|
||||
assign s_dim_oh = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_OUTPUT_HW][15:0];
|
||||
assign s_dim_ow = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_OUTPUT_HW][31:16];
|
||||
assign s_kh = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_KERNEL_STRIDE][7:0];
|
||||
assign s_kw = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_KERNEL_STRIDE][15:8];
|
||||
assign s_sh = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_KERNEL_STRIDE][23:16];
|
||||
assign s_sw = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_KERNEL_STRIDE][31:24];
|
||||
assign s_pad_t = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_PADDING][7:0];
|
||||
assign s_pad_b = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_PADDING][15:8];
|
||||
assign s_pad_l = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_PADDING][23:16];
|
||||
assign s_pad_r = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_PADDING][31:24];
|
||||
assign s_tile_h = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_TILE_HW][7:0];
|
||||
assign s_tile_w = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_TILE_HW][15:8];
|
||||
|
||||
assign s_is_kernel_op = (s_opcode == `APB4_NPU__OP_CONV2D) ||
|
||||
(s_opcode == `APB4_NPU__OP_DEPTHWISE3X3) || (s_opcode == `APB4_NPU__OP_MAX_POOL) ||
|
||||
(s_opcode == `APB4_NPU__OP_AVERAGE_POOL);
|
||||
assign s_is_weighted_op = (s_opcode == `APB4_NPU__OP_CONV2D) ||
|
||||
(s_opcode == `APB4_NPU__OP_DEPTHWISE3X3) || (s_opcode == `APB4_NPU__OP_FULLY_CONNECTED);
|
||||
assign s_is_param_op = s_is_weighted_op || (s_opcode == `APB4_NPU__OP_ADD);
|
||||
assign s_is_add = (s_opcode == `APB4_NPU__OP_ADD);
|
||||
assign s_is_gap = (s_opcode == `APB4_NPU__OP_GLOBAL_AVERAGE_POOL);
|
||||
|
||||
// Computed output extent: positive floor semantics reproduced with the
|
||||
// multiply-frame equivalence floor(t/s)+1 == e <=> t >= 0 &&
|
||||
// (e-1)*s <= t < e*s for s > 0, which avoids a hardware divider.
|
||||
assign s_extent_h = $signed(
|
||||
{16'd0, s_dim_h}
|
||||
) + $signed(
|
||||
{24'd0, s_pad_t}
|
||||
) + $signed(
|
||||
{24'd0, s_pad_b}
|
||||
) - $signed(
|
||||
{24'd0, s_kh}
|
||||
);
|
||||
assign s_extent_w = $signed(
|
||||
{16'd0, s_dim_w}
|
||||
) + $signed(
|
||||
{24'd0, s_pad_l}
|
||||
) + $signed(
|
||||
{24'd0, s_pad_r}
|
||||
) - $signed(
|
||||
{24'd0, s_kw}
|
||||
);
|
||||
|
||||
// Kernel-position count: kh*kw is the first multiply level, registered in
|
||||
// DecCompute; the per-opcode reduction below completes in DecCompute2.
|
||||
assign s_khkw = {8'd0, s_kh} * {8'd0, s_kw};
|
||||
always_comb begin
|
||||
unique case (s_opcode)
|
||||
`APB4_NPU__OP_CONV2D: begin
|
||||
s_full_k = {48'd0, s_khkw_q} * {48'd0, s_dim_cin};
|
||||
end
|
||||
`APB4_NPU__OP_DEPTHWISE3X3: s_full_k = 64'd9;
|
||||
`APB4_NPU__OP_FULLY_CONNECTED: s_full_k = {48'd0, s_dim_cin};
|
||||
`APB4_NPU__OP_MAX_POOL, `APB4_NPU__OP_AVERAGE_POOL: begin
|
||||
s_full_k = {48'd0, s_khkw_q};
|
||||
end
|
||||
`APB4_NPU__OP_GLOBAL_AVERAGE_POOL: s_full_k = {48'd0, s_dim_h} * {48'd0, s_dim_w};
|
||||
default: s_full_k = 64'd1;
|
||||
endcase
|
||||
end
|
||||
|
||||
// Expected weight bytes: the last multiply level, registered in DecCompute3.
|
||||
always_comb begin
|
||||
if (s_opcode == `APB4_NPU__OP_DEPTHWISE3X3) begin
|
||||
s_weight_expect = (({48'd0, s_dim_cin} + 64'd7) >> 3) * 64'd72;
|
||||
end else begin
|
||||
s_weight_expect = (({48'd0, s_dim_cout} + 64'd7) >> 3) * s_full_k_q * 64'd8;
|
||||
end
|
||||
end
|
||||
|
||||
assign s_min_row_in = {48'd0, s_dim_w} * {48'd0, s_dim_cin};
|
||||
assign s_min_row_out = {48'd0, s_dim_ow} * {48'd0, s_dim_cout};
|
||||
assign s_span_in0 = ({48'd0, s_dim_h} - 64'd1) *
|
||||
{32'd0, s_words_q[`APB4_NPU__DESCRIPTOR_WORD_INPUT0_ROW_BYTES]} + s_min_row_in_q;
|
||||
assign s_span_in1 = ({48'd0, s_dim_h} - 64'd1) *
|
||||
{32'd0, s_words_q[`APB4_NPU__DESCRIPTOR_WORD_INPUT1_ROW_BYTES]} + s_min_row_in_q;
|
||||
assign s_span_out = ({48'd0, s_dim_oh} - 64'd1) *
|
||||
{32'd0, s_words_q[`APB4_NPU__DESCRIPTOR_WORD_OUTPUT_ROW_BYTES]} + s_min_row_out_q;
|
||||
assign s_region_end = {32'd0, s_base_q} + {41'd0, s_count_q, 7'd0};
|
||||
|
||||
// Overlap ranges in the golden validator's membership order: input0,
|
||||
// output, input1 (ADD only), weights, parameters, descriptor region.
|
||||
always_comb begin
|
||||
s_rng_base[0] = {32'd0, s_words_q[`APB4_NPU__DESCRIPTOR_WORD_INPUT0_BASE]};
|
||||
s_rng_end[0] = s_rng_base[0] + {32'd0, s_words_q[`APB4_NPU__DESCRIPTOR_WORD_INPUT0_BYTES]};
|
||||
s_rng_base[1] = {32'd0, s_words_q[`APB4_NPU__DESCRIPTOR_WORD_OUTPUT_BASE]};
|
||||
s_rng_end[1] = s_rng_base[1] + {32'd0, s_words_q[`APB4_NPU__DESCRIPTOR_WORD_OUTPUT_BYTES]};
|
||||
s_rng_base[2] = {32'd0, s_words_q[`APB4_NPU__DESCRIPTOR_WORD_INPUT1_BASE]};
|
||||
s_rng_end[2] = s_rng_base[2] + {32'd0, s_words_q[`APB4_NPU__DESCRIPTOR_WORD_INPUT1_BYTES]};
|
||||
s_rng_base[3] = {32'd0, s_words_q[`APB4_NPU__DESCRIPTOR_WORD_WEIGHT_BASE]};
|
||||
s_rng_end[3] = s_rng_base[3] + {32'd0, s_words_q[`APB4_NPU__DESCRIPTOR_WORD_WEIGHT_BYTES]};
|
||||
s_rng_base[4] = {32'd0, s_words_q[`APB4_NPU__DESCRIPTOR_WORD_PARAM_BASE]};
|
||||
s_rng_end[4] = s_rng_base[4] + {32'd0, s_words_q[`APB4_NPU__DESCRIPTOR_WORD_PARAM_BYTES]};
|
||||
s_rng_base[5] = {32'd0, s_base_q};
|
||||
s_rng_end[5] = s_region_end;
|
||||
s_rng_valid[0] = 1'b1;
|
||||
s_rng_valid[1] = 1'b1;
|
||||
s_rng_valid[2] = s_is_add;
|
||||
s_rng_valid[3] = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_WEIGHT_BYTES] != 32'd0;
|
||||
s_rng_valid[4] = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_PARAM_BYTES] != 32'd0;
|
||||
s_rng_valid[5] = 1'b1;
|
||||
s_overlap_pair = 1'b0;
|
||||
for (int unsigned range_a = 0; range_a < 5; range_a++) begin
|
||||
for (int unsigned range_b = range_a + 1; range_b < 6; range_b++) begin
|
||||
if (s_rng_valid[range_a] && s_rng_valid[range_b] &&
|
||||
!(s_is_add && (range_a == 0) && (range_b == 2))) begin
|
||||
if ((s_rng_base[range_a] < s_rng_end[range_b]) &&
|
||||
(s_rng_base[range_b] < s_rng_end[range_a])) begin
|
||||
s_overlap_pair = 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Validation check sequencer: one state per golden-validator stage, with the
|
||||
// intra-stage priority matching the script's first-fault order.
|
||||
always_comb begin
|
||||
logic [31:0] s_w0;
|
||||
logic [31:0] s_w1;
|
||||
logic [31:0] s_w3;
|
||||
logic [31:0] s_w5;
|
||||
logic [31:0] s_w11;
|
||||
logic [31:0] s_w12;
|
||||
logic [31:0] s_w13;
|
||||
logic [31:0] s_w14;
|
||||
logic [31:0] s_w15;
|
||||
logic [31:0] s_word16;
|
||||
logic [31:0] s_w17;
|
||||
logic [31:0] s_w18;
|
||||
logic [31:0] s_w19;
|
||||
logic [31:0] s_w20;
|
||||
logic [31:0] s_w21;
|
||||
logic [31:0] s_w22;
|
||||
logic [31:0] s_w23;
|
||||
logic [31:0] s_w24;
|
||||
logic [31:0] s_w25;
|
||||
logic s_extent_h_pos;
|
||||
logic s_extent_w_pos;
|
||||
logic s_extent_h_match;
|
||||
logic s_extent_w_match;
|
||||
logic [31:0] s_extent_h_u;
|
||||
logic [31:0] s_extent_w_u;
|
||||
s_w0 = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_VERSION_OPCODE];
|
||||
s_w1 = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_RESERVED];
|
||||
s_w3 = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_INPUT1_BASE];
|
||||
s_w5 = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_PARAM_BASE];
|
||||
s_w11 = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_INPUT1_ROW_BYTES];
|
||||
s_w12 = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_KERNEL_STRIDE];
|
||||
s_w13 = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_PADDING];
|
||||
s_w14 = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_TILE_HW];
|
||||
s_w15 = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_K_SLICE];
|
||||
s_word16 = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_INPUT0_BYTES];
|
||||
s_w17 = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_INPUT1_BYTES];
|
||||
s_w18 = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_OUTPUT_BYTES];
|
||||
s_w19 = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_PARAM_BYTES];
|
||||
s_w20 = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_INPUT0_ZERO];
|
||||
s_w21 = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_INPUT1_ZERO];
|
||||
s_w22 = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_OUTPUT_ZERO];
|
||||
s_w23 = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_ACTIVATION_BOUNDS];
|
||||
s_w24 = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_WEIGHT_BASE];
|
||||
s_w25 = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_WEIGHT_BYTES];
|
||||
s_extent_h_pos = !s_extent_h_q[31];
|
||||
s_extent_w_pos = !s_extent_w_q[31];
|
||||
s_extent_h_u = {1'b0, s_extent_h_q[30:0]};
|
||||
s_extent_w_u = {1'b0, s_extent_w_q[30:0]};
|
||||
s_extent_h_match = (({16'd0, s_dim_oh} - 32'd1) * {24'd0, s_sh} <= s_extent_h_u) &&
|
||||
(s_extent_h_u < {16'd0, s_dim_oh} * {24'd0, s_sh});
|
||||
s_extent_w_match = (({16'd0, s_dim_ow} - 32'd1) * {24'd0, s_sw} <= s_extent_w_u) &&
|
||||
(s_extent_w_u < {16'd0, s_dim_ow} * {24'd0, s_sw});
|
||||
|
||||
s_chk_fail = 1'b0;
|
||||
s_chk_code = `APB4_NPU__FAULT_CODE_DESCRIPTOR;
|
||||
s_chk_word = 6'd0;
|
||||
s_chk_wordless = 1'b0;
|
||||
unique case (s_state_q)
|
||||
DecStruct1: begin
|
||||
if (s_w0[31:16] != `APB4_NPU__DESCRIPTOR_VERSION) begin
|
||||
s_chk_fail = 1'b1;
|
||||
end else if (s_w0[15:8] != 8'd0) begin
|
||||
s_chk_fail = 1'b1;
|
||||
end else if (s_w1 != 32'd0) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd1;
|
||||
end
|
||||
end
|
||||
DecStruct2: begin
|
||||
for (int unsigned word = 26; word < 32; word++) begin
|
||||
if (!s_chk_fail && (s_words_q[word] != 32'd0)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'(word);
|
||||
end
|
||||
end
|
||||
end
|
||||
DecStruct3: begin
|
||||
if (s_w14[31:16] != 16'd0) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd14;
|
||||
end else if (s_w20[31:8] != 24'd0) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd20;
|
||||
end else if (s_w22[31:8] != 24'd0) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd22;
|
||||
end else if (s_w23[31:16] != 16'd0) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd23;
|
||||
end else if ($signed(s_w23[7:0]) > $signed(s_w23[15:8])) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd23;
|
||||
end
|
||||
end
|
||||
DecStruct4: begin
|
||||
if ((s_tile_h == 8'd0) || (s_tile_h > 8'd8) || (s_tile_w == 8'd0) ||
|
||||
(s_tile_w > 8'd8)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd14;
|
||||
end else if ({24'd0, s_tile_h} * {24'd0, s_tile_w} > 32'd8) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd14;
|
||||
end else if ((s_w15 == 32'd0) || (s_w15 > 32'd1024)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd15;
|
||||
end
|
||||
end
|
||||
DecOpcode: begin
|
||||
if ((s_opcode == 8'd0) || (s_opcode > 8'd8)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_code = `APB4_NPU__FAULT_CODE_UNSUPPORTED;
|
||||
end
|
||||
end
|
||||
DecUnused: begin
|
||||
if (!s_is_add && (s_w3 != 32'd0)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd3;
|
||||
end else if (!s_is_add && (s_w11 != 32'd0)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd11;
|
||||
end else if (!s_is_add && (s_w17 != 32'd0)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd17;
|
||||
end else if (!s_is_add && (s_w21 != 32'd0)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd21;
|
||||
end else if (!s_is_weighted_op && (s_w24 != 32'd0)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd24;
|
||||
end else if (!s_is_weighted_op && (s_w25 != 32'd0)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd25;
|
||||
end else if (!s_is_param_op && (s_w5 != 32'd0)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd5;
|
||||
end else if (!s_is_param_op && (s_w19 != 32'd0)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd19;
|
||||
end else if (s_is_gap && (s_w12 != 32'd0)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd12;
|
||||
end else if (s_is_gap && (s_w13 != 32'd0)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd13;
|
||||
end
|
||||
end
|
||||
DecGeom1: begin
|
||||
s_chk_code = `APB4_NPU__FAULT_CODE_RANGE;
|
||||
if ((s_dim_h == 16'd0) || (s_dim_h > 16'd4096)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd6;
|
||||
end else if ((s_dim_w == 16'd0) || (s_dim_w > 16'd4096)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd6;
|
||||
end else if ((s_dim_cin == 16'd0) || (s_dim_cin > 16'd4096)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd7;
|
||||
end else if ((s_dim_cout == 16'd0) || (s_dim_cout > 16'd4096)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd7;
|
||||
end else if ((s_dim_oh == 16'd0) || (s_dim_oh > 16'd4096)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd8;
|
||||
end else if ((s_dim_ow == 16'd0) || (s_dim_ow > 16'd4096)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd8;
|
||||
end
|
||||
end
|
||||
DecGeom2: begin
|
||||
if (s_is_kernel_op) begin
|
||||
s_chk_code = `APB4_NPU__FAULT_CODE_RANGE;
|
||||
if ((s_kh == 8'd0) || (s_kw == 8'd0) || (s_sh == 8'd0) || (s_sw == 8'd0)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd12;
|
||||
end else if (({24'd0, s_pad_t} >= {24'd0, s_kh}) ||
|
||||
({24'd0, s_pad_b} >= {24'd0, s_kh}) ||
|
||||
({24'd0, s_pad_l} >= {24'd0, s_kw}) ||
|
||||
({24'd0, s_pad_r} >= {24'd0, s_kw})) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd13;
|
||||
end else if (!s_extent_h_pos || !s_extent_w_pos) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd8;
|
||||
end else if (!s_extent_h_match || !s_extent_w_match) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd8;
|
||||
end
|
||||
end
|
||||
end
|
||||
DecOper: begin
|
||||
unique case (s_opcode)
|
||||
`APB4_NPU__OP_CONV2D: begin
|
||||
s_chk_code = `APB4_NPU__FAULT_CODE_UNSUPPORTED;
|
||||
if ((s_kh > 8'd16) || (s_kw > 8'd16)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd12;
|
||||
end else if (((s_sh != 8'd1) && (s_sh != 8'd2)) ||
|
||||
((s_sw != 8'd1) && (s_sw != 8'd2))) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd12;
|
||||
end
|
||||
end
|
||||
`APB4_NPU__OP_DEPTHWISE3X3: begin
|
||||
s_chk_code = `APB4_NPU__FAULT_CODE_UNSUPPORTED;
|
||||
if ((s_kh != 8'd3) || (s_kw != 8'd3)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd12;
|
||||
end else if (((s_sh != 8'd1) && (s_sh != 8'd2)) ||
|
||||
((s_sw != 8'd1) && (s_sw != 8'd2))) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd12;
|
||||
end else if (s_dim_cout != s_dim_cin) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd7;
|
||||
end
|
||||
end
|
||||
`APB4_NPU__OP_FULLY_CONNECTED: begin
|
||||
s_chk_code = `APB4_NPU__FAULT_CODE_UNSUPPORTED;
|
||||
if ((s_dim_h != 16'd1) || (s_dim_w != 16'd1)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd6;
|
||||
end else if ((s_dim_oh != 16'd1) || (s_dim_ow != 16'd1)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd8;
|
||||
end else if (s_w12 != 32'h0101_0101) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd12;
|
||||
end else if (s_w13 != 32'd0) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd13;
|
||||
end
|
||||
end
|
||||
`APB4_NPU__OP_ADD: begin
|
||||
if (s_w21[31:8] != 24'd0) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd21;
|
||||
end else if (s_w3 == 32'd0) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd3;
|
||||
end else begin
|
||||
s_chk_code = `APB4_NPU__FAULT_CODE_UNSUPPORTED;
|
||||
if ((s_dim_oh != s_dim_h) || (s_dim_ow != s_dim_w)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd8;
|
||||
end else if (s_dim_cout != s_dim_cin) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd7;
|
||||
end else if (s_w12 != 32'h0101_0101) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd12;
|
||||
end else if (s_w13 != 32'd0) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd13;
|
||||
end
|
||||
end
|
||||
end
|
||||
`APB4_NPU__OP_MAX_POOL, `APB4_NPU__OP_AVERAGE_POOL: begin
|
||||
s_chk_code = `APB4_NPU__FAULT_CODE_UNSUPPORTED;
|
||||
if ((s_kh > 8'd16) || (s_kw > 8'd16)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd12;
|
||||
end else if (((s_sh != 8'd1) && (s_sh != 8'd2)) ||
|
||||
((s_sw != 8'd1) && (s_sw != 8'd2))) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd12;
|
||||
end else if (s_dim_cout != s_dim_cin) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd7;
|
||||
end else if (s_w20[7:0] != s_w22[7:0]) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd22;
|
||||
end
|
||||
end
|
||||
`APB4_NPU__OP_GLOBAL_AVERAGE_POOL: begin
|
||||
s_chk_code = `APB4_NPU__FAULT_CODE_UNSUPPORTED;
|
||||
if ((s_dim_oh != 16'd1) || (s_dim_ow != 16'd1)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd8;
|
||||
end else if (s_dim_cout != s_dim_cin) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd7;
|
||||
end else if (s_w20[7:0] != s_w22[7:0]) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd22;
|
||||
end
|
||||
end
|
||||
default: begin
|
||||
// CLAMP
|
||||
s_chk_code = `APB4_NPU__FAULT_CODE_UNSUPPORTED;
|
||||
if ((s_dim_oh != s_dim_h) || (s_dim_ow != s_dim_w)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd8;
|
||||
end else if (s_dim_cout != s_dim_cin) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd7;
|
||||
end else if (s_w20[7:0] != s_w22[7:0]) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd22;
|
||||
end else if (s_w12 != 32'h0101_0101) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd12;
|
||||
end else if (s_w13 != 32'd0) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd13;
|
||||
end
|
||||
end
|
||||
endcase
|
||||
end
|
||||
DecOperCommon: begin
|
||||
if ({32'd0, s_w15} > s_full_k_q) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_code = `APB4_NPU__FAULT_CODE_UNSUPPORTED;
|
||||
s_chk_word = 6'd15;
|
||||
end else if (({24'd0, s_tile_h} > {16'd0, s_dim_oh}) ||
|
||||
({24'd0, s_tile_w} > {16'd0, s_dim_ow})) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_code = `APB4_NPU__FAULT_CODE_RANGE;
|
||||
s_chk_word = 6'd14;
|
||||
end else if (s_is_weighted_op) begin
|
||||
if (s_w24 == 32'd0) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd24;
|
||||
end else if ({32'd0, s_w25} != s_weight_expect_q) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd25;
|
||||
end else if (s_w5 == 32'd0) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd5;
|
||||
end else if ({32'd0, s_w19} != ({48'd0, s_dim_cout} * 64'd16)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd19;
|
||||
end
|
||||
end else if (s_is_add) begin
|
||||
if (s_w5 == 32'd0) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd5;
|
||||
end else if (s_w19 != 32'd32) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd19;
|
||||
end
|
||||
end
|
||||
end
|
||||
DecStride: begin
|
||||
s_chk_code = `APB4_NPU__FAULT_CODE_RANGE;
|
||||
if ({32'd0, s_words_q[`APB4_NPU__DESCRIPTOR_WORD_INPUT0_ROW_BYTES]} < s_min_row_in_q) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd9;
|
||||
end else if ({32'd0, s_words_q[`APB4_NPU__DESCRIPTOR_WORD_OUTPUT_ROW_BYTES]} <
|
||||
s_min_row_out_q) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd10;
|
||||
end else if (s_is_add && ({32'd0, s_w11} < s_min_row_in_q)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd11;
|
||||
end else if (s_span_in0_q > {32'd0, s_word16}) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd16;
|
||||
end else if ((s_rng_base_q[0] + s_span_in0_q) > 64'h1_0000_0000) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd2;
|
||||
end else if (s_rng_end_q[0] > 64'h1_0000_0000) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd2;
|
||||
end else if (s_is_add && (s_span_in1_q > {32'd0, s_w17})) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd17;
|
||||
end else if (s_is_add && ((s_rng_base_q[2] + s_span_in1_q) > 64'h1_0000_0000)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd3;
|
||||
end else if (s_is_add && (s_rng_end_q[2] > 64'h1_0000_0000)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd3;
|
||||
end else if (s_span_out_q > {32'd0, s_w18}) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd18;
|
||||
end else if ((s_rng_base_q[1] + s_span_out_q) > 64'h1_0000_0000) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd4;
|
||||
end else if (s_rng_end_q[1] > 64'h1_0000_0000) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd4;
|
||||
end else if ((s_w25 != 32'd0) && (s_rng_end_q[3] > 64'h1_0000_0000)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd24;
|
||||
end else if ((s_w19 != 32'd0) && (s_rng_end_q[4] > 64'h1_0000_0000)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd5;
|
||||
end
|
||||
end
|
||||
DecAlign: begin
|
||||
if ((s_w5 != 32'd0) && (s_w5[5:0] != 6'd0)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd5;
|
||||
end else if ((s_w24 != 32'd0) && (s_w24[5:0] != 6'd0)) begin
|
||||
s_chk_fail = 1'b1;
|
||||
s_chk_word = 6'd24;
|
||||
end
|
||||
end
|
||||
DecOverlap: begin
|
||||
s_chk_code = `APB4_NPU__FAULT_CODE_RANGE;
|
||||
s_chk_wordless = 1'b1;
|
||||
if (s_region_end_q > 64'h1_0000_0000) begin
|
||||
s_chk_fail = 1'b1;
|
||||
end else if (s_overlap_pair_q) begin
|
||||
s_chk_fail = 1'b1;
|
||||
end
|
||||
end
|
||||
default: begin
|
||||
end
|
||||
endcase
|
||||
end
|
||||
|
||||
assign read_req_valid_o = (s_state_q == DecFetchCmd) && !s_fetch_wrap && !clear_i;
|
||||
assign read_addr_o = s_desc_addr;
|
||||
assign read_bytes_o = 32'd128;
|
||||
assign read_data_ready_o = ((s_state_q == DecFetchData) || (s_state_q == DecDrain)) && !clear_i;
|
||||
|
||||
assign busy_o = (s_state_q != DecIdle);
|
||||
assign pause_ok_o = (s_state_q != DecFetchData) && (s_state_q != DecDrain);
|
||||
assign cur_desc_index_o = s_index_q;
|
||||
assign progress_o = s_stream_accept || (rec_valid_o && rec_ready_i) || retired_i;
|
||||
assign fault_req_o = (s_state_q == DecFault);
|
||||
assign rec_valid_o = (s_state_q == DecEmit) && !clear_i;
|
||||
|
||||
// all_done pulses when the final record retires; the core owns publication.
|
||||
assign all_done_o = (s_state_q == DecWaitRetire) && retired_i &&
|
||||
((s_index_q + 16'd1) == s_count_q);
|
||||
|
||||
assign fault_code_o = s_fault_code_q;
|
||||
assign fault_addr_o = s_fault_addr_q;
|
||||
assign fault_info_o = s_fault_info_q;
|
||||
assign fault_desc_o = {16'd0, s_fault_index_q};
|
||||
|
||||
assign rec_opcode_o = 4'(s_opcode);
|
||||
assign rec_h_o = 13'(s_dim_h);
|
||||
assign rec_w_o = 13'(s_dim_w);
|
||||
assign rec_cin_o = 13'(s_dim_cin);
|
||||
assign rec_cout_o = 13'(s_dim_cout);
|
||||
assign rec_oh_o = 13'(s_dim_oh);
|
||||
assign rec_ow_o = 13'(s_dim_ow);
|
||||
assign rec_input0_base_o = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_INPUT0_BASE];
|
||||
assign rec_output_base_o = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_OUTPUT_BASE];
|
||||
assign rec_weight_base_o = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_WEIGHT_BASE];
|
||||
assign rec_param_base_o = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_PARAM_BASE];
|
||||
assign rec_input0_row_bytes_o = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_INPUT0_ROW_BYTES];
|
||||
assign rec_output_row_bytes_o = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_OUTPUT_ROW_BYTES];
|
||||
assign rec_kh_o = s_kh;
|
||||
assign rec_kw_o = s_kw;
|
||||
assign rec_sh_o = s_sh;
|
||||
assign rec_sw_o = s_sw;
|
||||
assign rec_pad_top_o = s_pad_t;
|
||||
assign rec_pad_left_o = s_pad_l;
|
||||
assign rec_input0_zero_o = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_INPUT0_ZERO][7:0];
|
||||
assign rec_weight_bytes_o = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_WEIGHT_BYTES];
|
||||
assign rec_param_bytes_o = s_words_q[`APB4_NPU__DESCRIPTOR_WORD_PARAM_BYTES];
|
||||
assign rec_desc_index_o = s_index_q;
|
||||
|
||||
always_comb begin
|
||||
s_state_d = s_state_q;
|
||||
s_base_d = s_base_q;
|
||||
s_count_d = s_count_q;
|
||||
s_index_d = s_index_q;
|
||||
unique case (s_state_q)
|
||||
DecIdle: begin
|
||||
if (job_start_i) begin
|
||||
s_base_d = job_base_i;
|
||||
s_count_d = job_count_i;
|
||||
s_index_d = 16'd0;
|
||||
s_state_d = DecFetchCmd;
|
||||
end
|
||||
end
|
||||
DecFetchCmd: begin
|
||||
// A wrapped record address is a local RANGE rejection, never a bus
|
||||
// request; the DMA is never asked to fetch outside the 32-bit space.
|
||||
if (s_fetch_wrap || dma_fault_i) begin
|
||||
s_state_d = DecFault;
|
||||
end else if (s_cmd_accept) begin
|
||||
s_state_d = DecFetchData;
|
||||
end
|
||||
end
|
||||
DecFetchData: begin
|
||||
if (dma_fault_i) begin
|
||||
s_state_d = DecFault;
|
||||
end else if (s_last_accept) begin
|
||||
s_state_d = DecCompute;
|
||||
end
|
||||
end
|
||||
// Three pipeline cycles that register every wide derived quantity (one
|
||||
// multiply level each); the check sequencer below consumes registers.
|
||||
DecCompute: s_state_d = DecCompute2;
|
||||
DecCompute2: s_state_d = DecCompute3;
|
||||
DecCompute3: s_state_d = DecStruct1;
|
||||
DecStruct1: s_state_d = s_chk_fail ? DecFault : DecStruct2;
|
||||
DecStruct2: s_state_d = s_chk_fail ? DecFault : DecStruct3;
|
||||
DecStruct3: s_state_d = s_chk_fail ? DecFault : DecStruct4;
|
||||
DecStruct4: s_state_d = s_chk_fail ? DecFault : DecOpcode;
|
||||
DecOpcode: s_state_d = s_chk_fail ? DecFault : DecUnused;
|
||||
DecUnused: s_state_d = s_chk_fail ? DecFault : DecGeom1;
|
||||
DecGeom1: s_state_d = s_chk_fail ? DecFault : DecGeom2;
|
||||
DecGeom2: s_state_d = s_chk_fail ? DecFault : DecOper;
|
||||
DecOper: s_state_d = s_chk_fail ? DecFault : DecOperCommon;
|
||||
DecOperCommon: s_state_d = s_chk_fail ? DecFault : DecStride;
|
||||
DecStride: s_state_d = s_chk_fail ? DecFault : DecAlign;
|
||||
DecAlign: s_state_d = s_chk_fail ? DecFault : DecOverlap;
|
||||
DecOverlap: s_state_d = s_chk_fail ? DecFault : DecEmit;
|
||||
DecEmit: begin
|
||||
if (rec_ready_i) begin
|
||||
s_state_d = DecWaitRetire;
|
||||
end
|
||||
end
|
||||
DecWaitRetire: begin
|
||||
if (retired_i) begin
|
||||
if ((s_index_q + 16'd1) == s_count_q) begin
|
||||
s_state_d = DecIdle;
|
||||
end else begin
|
||||
s_index_d = s_index_q + 16'd1;
|
||||
s_state_d = DecFetchCmd;
|
||||
end
|
||||
end
|
||||
end
|
||||
DecDrain: begin
|
||||
// Coordinated-cancel fetch drain: the accepted read segment always
|
||||
// runs out; after a DMA fault the stream is consumed while it lasts.
|
||||
if (s_last_accept || (!dma_read_busy_i && !read_data_valid_i)) begin
|
||||
s_state_d = DecIdle;
|
||||
end
|
||||
end
|
||||
DecFault: begin
|
||||
// Terminal until the core's coordinated cancel.
|
||||
end
|
||||
default: s_state_d = DecIdle;
|
||||
endcase
|
||||
if (clear_i) begin
|
||||
unique case (s_state_q)
|
||||
// A presented-but-unaccepted fetch is withdrawn; an accepted one is
|
||||
// drained like any other in-flight segment.
|
||||
DecFetchCmd: s_state_d = s_cmd_accept ? DecDrain : DecIdle;
|
||||
DecFetchData: s_state_d = DecDrain;
|
||||
default: s_state_d = DecIdle;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk_hp_i or negedge rst_hp_n_i) begin
|
||||
if (!rst_hp_n_i) begin
|
||||
s_state_q <= DecIdle;
|
||||
s_base_q <= '0;
|
||||
s_count_q <= '0;
|
||||
s_index_q <= '0;
|
||||
s_beat_q <= '0;
|
||||
s_fault_code_q <= '0;
|
||||
s_fault_addr_q <= '0;
|
||||
s_fault_info_q <= '0;
|
||||
s_fault_index_q <= '0;
|
||||
s_extent_h_q <= '0;
|
||||
s_extent_w_q <= '0;
|
||||
s_khkw_q <= '0;
|
||||
s_full_k_q <= '0;
|
||||
s_weight_expect_q <= '0;
|
||||
s_span_in0_q <= '0;
|
||||
s_span_in1_q <= '0;
|
||||
s_span_out_q <= '0;
|
||||
s_min_row_in_q <= '0;
|
||||
s_min_row_out_q <= '0;
|
||||
s_region_end_q <= '0;
|
||||
s_overlap_pair_q <= 1'b0;
|
||||
for (int unsigned range = 0; range < 6; range++) begin
|
||||
s_rng_base_q[range] <= '0;
|
||||
s_rng_end_q[range] <= '0;
|
||||
end
|
||||
for (int unsigned word = 0; word < 32; word++) begin
|
||||
s_words_q[word] <= '0;
|
||||
end
|
||||
end else begin
|
||||
s_state_q <= s_state_d;
|
||||
s_base_q <= s_base_d;
|
||||
s_count_q <= s_count_d;
|
||||
s_index_q <= s_index_d;
|
||||
if (s_cmd_accept) begin
|
||||
s_beat_q <= 5'd0;
|
||||
end else if ((s_state_q == DecFetchData) && s_stream_accept) begin
|
||||
s_beat_q <= s_beat_q + 5'd1;
|
||||
end
|
||||
if ((s_state_q == DecFetchData) && s_stream_accept) begin
|
||||
s_words_q[{s_beat_q[3:0], 1'b0}] <= read_data_i[31:0];
|
||||
s_words_q[{s_beat_q[3:0], 1'b1}] <= read_data_i[63:32];
|
||||
end
|
||||
if (s_state_q == DecCompute) begin
|
||||
s_extent_h_q <= s_extent_h;
|
||||
s_extent_w_q <= s_extent_w;
|
||||
s_khkw_q <= s_khkw;
|
||||
s_min_row_in_q <= s_min_row_in;
|
||||
s_min_row_out_q <= s_min_row_out;
|
||||
s_region_end_q <= s_region_end;
|
||||
s_overlap_pair_q <= s_overlap_pair;
|
||||
for (int unsigned range = 0; range < 6; range++) begin
|
||||
s_rng_base_q[range] <= s_rng_base[range];
|
||||
s_rng_end_q[range] <= s_rng_end[range];
|
||||
end
|
||||
end
|
||||
if (s_state_q == DecCompute2) begin
|
||||
s_full_k_q <= s_full_k;
|
||||
s_span_in0_q <= s_span_in0;
|
||||
s_span_in1_q <= s_span_in1;
|
||||
s_span_out_q <= s_span_out;
|
||||
end
|
||||
if (s_state_q == DecCompute3) begin
|
||||
s_weight_expect_q <= s_weight_expect;
|
||||
end
|
||||
if ((s_state_q != DecFault) && (s_state_d == DecFault)) begin
|
||||
if (s_fetch_wrap) begin
|
||||
// Local wrap rejection: RANGE with no single offending word.
|
||||
s_fault_code_q <= `APB4_NPU__FAULT_CODE_RANGE;
|
||||
s_fault_addr_q <= s_desc_addr;
|
||||
s_fault_info_q <= FaultInfoInternal;
|
||||
end else if (dma_fault_i) begin
|
||||
// Bus fault while fetching: forward code/address/response.
|
||||
s_fault_code_q <= dma_fault_code_i;
|
||||
s_fault_addr_q <= dma_fault_addr_i;
|
||||
s_fault_info_q <= {16'd0, 8'd255, 4'd0, 2'd1, dma_fault_resp_i};
|
||||
end else begin
|
||||
s_fault_code_q <= s_chk_code;
|
||||
s_fault_addr_q <= s_chk_wordless ? s_desc_addr :
|
||||
(s_desc_addr + {24'd0, s_chk_word, 2'b00});
|
||||
s_fault_info_q <= FaultInfoInternal;
|
||||
end
|
||||
s_fault_index_q <= s_index_q;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
`ifndef SV_ASSRT_DISABLE
|
||||
logic s_rec_wait_q;
|
||||
logic [4:0] s_beat_seen_q;
|
||||
|
||||
always_ff @(posedge clk_hp_i or negedge rst_hp_n_i) begin
|
||||
if (!rst_hp_n_i) begin
|
||||
s_rec_wait_q <= 1'b0;
|
||||
s_beat_seen_q <= 5'd0;
|
||||
end else begin
|
||||
s_rec_wait_q <= rec_valid_o && !rec_ready_i;
|
||||
if (s_state_q == DecFetchCmd) begin
|
||||
s_beat_seen_q <= 5'd0;
|
||||
end else if ((s_state_q == DecFetchData) && s_stream_accept) begin
|
||||
s_beat_seen_q <= s_beat_seen_q + 5'd1;
|
||||
end
|
||||
// A presented record holds stable until the scheduler accepts it.
|
||||
if (s_rec_wait_q) begin
|
||||
assert (rec_valid_o);
|
||||
end
|
||||
// A descriptor fetch is exactly sixteen 64-bit beats.
|
||||
if ((s_state_q == DecFetchData) && (s_state_d == DecCompute)) begin
|
||||
assert (s_beat_seen_q == 5'd15);
|
||||
end
|
||||
// The staging writes stay inside the 32-word record.
|
||||
if ((s_state_q == DecFetchData) && s_stream_accept) begin
|
||||
assert (s_beat_q < 5'd16);
|
||||
end
|
||||
end
|
||||
end
|
||||
`endif
|
||||
endmodule
|
||||
File diff suppressed because it is too large.
Load diff
@@ -179,6 +179,12 @@
|
||||
/ip/multimedia/jpeg_dma.sv
|
||||
/ip/multimedia/jpeg_reg.sv
|
||||
/ip/multimedia/apb4_jpeg.sv
|
||||
/ip/multimedia/npu_pkg.sv
|
||||
/ip/multimedia/npu_local_sram.sv
|
||||
/ip/multimedia/npu_patch_packer.sv
|
||||
/ip/multimedia/npu_dma.sv
|
||||
/ip/multimedia/npu_job_decoder.sv
|
||||
/ip/multimedia/npu_scheduler.sv
|
||||
/ip/multimedia/npu_reg.sv
|
||||
/ip/multimedia/npu_control_cdc.sv
|
||||
/ip/multimedia/npu_core.sv
|
||||
|
||||
@@ -359,6 +359,12 @@ def source_files(target: str) -> list[Path]:
|
||||
TOP / "hp_axi4_mux3.sv",
|
||||
SCRIPT_DIR / "gateway_a_formal.sv",
|
||||
]
|
||||
if target == "npu_dma":
|
||||
return [
|
||||
COMMON_RTL / "interface/axi4_if.sv",
|
||||
MULTIMEDIA / "npu_dma.sv",
|
||||
SCRIPT_DIR / "npu_dma_formal.sv",
|
||||
]
|
||||
if target == "sdio":
|
||||
return [
|
||||
COMMON_RTL / "interface/apb4_if.sv",
|
||||
@@ -436,6 +442,7 @@ def parse_args() -> argparse.Namespace:
|
||||
"apu_sequencer",
|
||||
"gateway_a",
|
||||
"sdio",
|
||||
"npu_dma",
|
||||
),
|
||||
required=True,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,488 @@
|
||||
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
|
||||
// retroSoC is licensed under Mulan PSL v2.
|
||||
// You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
// You may obtain a copy of Mulan PSL v2 at:
|
||||
// http://license.coscl.org.cn/MulanPSL2
|
||||
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// See the Mulan PSL v2 for more details.
|
||||
|
||||
// Bounded formal harness for the NPU-P3 AXI4 data-plane engine (npu_dma).
|
||||
// One legal-but-adversarial command is offered per direction from the first
|
||||
// active cycle until it is accepted (anyconst address/byte-count fields), so
|
||||
// edge alignments, short segments and post-flush re-issue are all explored.
|
||||
// The constrained AXI4 responder below keeps at most one outstanding burst
|
||||
// per direction, runs independent per-channel stall state machines (0..3
|
||||
// stall cycles per handshake beat, chosen once per run by anyconst
|
||||
// personalities), and optionally injects SLVERR/DECERR or malformed
|
||||
// RID/RLAST/BID responses selected by anyconst knobs. block_new_i and
|
||||
// clear_i toggle inside anyconst-timed windows; clear_i models the
|
||||
// coordinated fabric flush, so the responder cancels in-flight transfers
|
||||
// together with the engine and never presents residual R/B beats to a
|
||||
// flushed receiver (BMC-sound suppression). A burst quarantined by a
|
||||
// malformed response is likewise retired silently because the engine stops
|
||||
// its receiver until the flush. All legality assumptions on the command and
|
||||
// stream sources live in npu_dma_formal_props.sv.
|
||||
|
||||
module npu_dma_formal_design (
|
||||
// verilog_format: off -- protocol observations are grouped by channel.
|
||||
input logic clk_i,
|
||||
output logic rst_n_i,
|
||||
output logic f_past_valid,
|
||||
output logic clear,
|
||||
output logic block_new,
|
||||
output logic pause_ack,
|
||||
output logic read_req_valid,
|
||||
output logic read_req_ready,
|
||||
output logic [31:0] read_addr,
|
||||
output logic [31:0] read_bytes,
|
||||
output logic read_data_valid,
|
||||
output logic read_data_ready,
|
||||
output logic [63:0] read_data,
|
||||
output logic [ 7:0] read_keep,
|
||||
output logic read_last,
|
||||
output logic write_req_valid,
|
||||
output logic write_req_ready,
|
||||
output logic [31:0] write_addr,
|
||||
output logic [31:0] write_bytes,
|
||||
output logic write_data_valid,
|
||||
output logic write_data_ready,
|
||||
output logic [63:0] write_data,
|
||||
output logic [ 7:0] write_keep,
|
||||
output logic write_last,
|
||||
output logic write_done,
|
||||
output logic busy,
|
||||
output logic read_busy,
|
||||
output logic write_busy,
|
||||
output logic [63:0] read_bytes_done,
|
||||
output logic [63:0] write_bytes_done,
|
||||
output logic [63:0] stall_cycles,
|
||||
output logic fault,
|
||||
output logic [ 3:0] fault_code,
|
||||
output logic [31:0] fault_addr,
|
||||
output logic [ 1:0] fault_resp,
|
||||
output logic read_cmd_err,
|
||||
output logic write_cmd_err,
|
||||
output logic [ 4:0] rfifo_count,
|
||||
output logic [ 4:0] wfifo_count,
|
||||
output logic [ 1:0] read_fault,
|
||||
output logic [63:0] read_data_const,
|
||||
output logic [63:0] write_data_const,
|
||||
output logic [ 7:0] write_keep_const,
|
||||
output logic awvalid,
|
||||
output logic awready,
|
||||
output logic [ 2:0] awid,
|
||||
output logic [31:0] awaddr,
|
||||
output logic [ 7:0] awlen,
|
||||
output logic [ 2:0] awsize,
|
||||
output logic [ 1:0] awburst,
|
||||
output logic awlock,
|
||||
output logic [ 3:0] awcache,
|
||||
output logic [ 2:0] awprot,
|
||||
output logic [ 3:0] awqos,
|
||||
output logic [ 3:0] awregion,
|
||||
output logic awuser,
|
||||
output logic wvalid,
|
||||
output logic wready,
|
||||
output logic [63:0] wdata,
|
||||
output logic [ 7:0] wstrb,
|
||||
output logic wlast,
|
||||
output logic wuser,
|
||||
output logic bvalid,
|
||||
output logic bready,
|
||||
output logic [ 2:0] bid,
|
||||
output logic [ 1:0] bresp,
|
||||
output logic buser,
|
||||
output logic arvalid,
|
||||
output logic arready,
|
||||
output logic [ 2:0] arid,
|
||||
output logic [31:0] araddr,
|
||||
output logic [ 7:0] arlen,
|
||||
output logic [ 2:0] arsize,
|
||||
output logic [ 1:0] arburst,
|
||||
output logic arlock,
|
||||
output logic [ 3:0] arcache,
|
||||
output logic [ 2:0] arprot,
|
||||
output logic [ 3:0] arqos,
|
||||
output logic [ 3:0] arregion,
|
||||
output logic aruser,
|
||||
output logic rvalid,
|
||||
output logic rready,
|
||||
output logic [ 2:0] rid,
|
||||
output logic [63:0] rdata,
|
||||
output logic [ 1:0] rresp,
|
||||
output logic rlast,
|
||||
output logic ruser
|
||||
// verilog_format: on
|
||||
);
|
||||
localparam logic [1:0] AxiRespOkay = 2'b00;
|
||||
localparam logic [1:0] AxiRespSlverr = 2'b10;
|
||||
localparam logic [1:0] AxiRespDecerr = 2'b11;
|
||||
|
||||
// Command fields, payload constants and fault-injection selectors are
|
||||
// constant per run. Payload data/keep values never influence the engine's
|
||||
// control path, so they are anyconst (this also keeps the solver cheap);
|
||||
// the properties file cross-checks the data path against these constants.
|
||||
(* anyconst *)logic [31:0] f_read_addr;
|
||||
(* anyconst *)logic [31:0] f_read_bytes;
|
||||
(* anyconst *)logic [31:0] f_write_addr;
|
||||
(* anyconst *)logic [31:0] f_write_bytes;
|
||||
(* anyconst *)logic [ 1:0] f_read_fault;
|
||||
(* anyconst *)logic f_read_proto_rlast;
|
||||
(* anyconst *)logic [ 1:0] f_write_fault;
|
||||
(* anyconst *)logic [63:0] f_read_data;
|
||||
(* anyconst *)logic [63:0] f_write_data;
|
||||
(* anyconst *)logic [ 7:0] f_write_keep;
|
||||
// Per-channel stall personalities, constant per run: each address
|
||||
// handshake is stalled f_ar_stall/f_aw_stall cycles before the grant, each
|
||||
// R beat waits f_r_gap cycles before presentation, each W beat is stalled
|
||||
// f_w_stall cycles, and B waits f_b_gap cycles after the final W beat.
|
||||
// Bounding stalls to 0..3 keeps the bounded proof tractable; longer stalls
|
||||
// add no new engine states because the engine holds its registers while a
|
||||
// handshake is stalled (payload stability is checked on the first stalled
|
||||
// cycle already), and the control-event windows below still place
|
||||
// pause/clear/stream events at every possible stall cycle.
|
||||
(* anyconst *)logic [ 1:0] f_ar_stall;
|
||||
(* anyconst *)logic [ 1:0] f_r_gap;
|
||||
(* anyconst *)logic [ 1:0] f_aw_stall;
|
||||
(* anyconst *)logic [ 1:0] f_w_stall;
|
||||
(* anyconst *)logic [ 1:0] f_b_gap;
|
||||
// Control-event windows, constant per run. Both commands are presented
|
||||
// from the first active cycle until they are accepted (and are re-presented
|
||||
// after a flush): the engine has no timers, so an absolute issue phase
|
||||
// carries no contract meaning. block_new_i is held over the [start, stop)
|
||||
// window, clear_i pulses for one cycle, and the read stream is stalled over
|
||||
// its own window. Windows are bounded to the first 16 active cycles, where
|
||||
// every engine phase of a reachable burst occurs; this keeps every
|
||||
// adversarial event placement reachable while avoiding per-cycle free
|
||||
// inputs, which are what make deep bounded queries expensive.
|
||||
(* anyconst *)logic [ 3:0] f_block_start;
|
||||
(* anyconst *)logic [ 3:0] f_block_stop;
|
||||
(* anyconst *)logic f_clear_en;
|
||||
(* anyconst *)logic [ 3:0] f_clear_at;
|
||||
(* anyconst *)logic [ 3:0] f_rdstall_start;
|
||||
(* anyconst *)logic [ 3:0] f_rdstall_stop;
|
||||
// Only the write stream last flag is free per cycle: the solver chooses a
|
||||
// correct segment end for success traces or a wrong one to exercise the
|
||||
// local write_cmd_err abort. Every check in the properties file is a
|
||||
// safety property and needs no fairness assumption.
|
||||
(* anyseq *)logic f_write_last;
|
||||
|
||||
logic [ 5:0] s_cycle_q;
|
||||
logic s_read_done_q;
|
||||
logic s_write_done_q;
|
||||
|
||||
logic s_r_active_q;
|
||||
logic [ 8:0] s_r_beats_q;
|
||||
logic s_r_beat_valid_q;
|
||||
logic s_r_first_q;
|
||||
logic [ 1:0] s_r_resp_q;
|
||||
logic s_r_proto_rid_q;
|
||||
logic s_r_proto_rlast_q;
|
||||
logic s_w_active_q;
|
||||
logic [ 8:0] s_w_beats_q;
|
||||
logic s_w_bvalid_q;
|
||||
logic s_w_bpending_q;
|
||||
logic [ 1:0] s_w_bresp_q;
|
||||
logic s_w_bid_bad_q;
|
||||
logic [ 1:0] s_ar_stall_q;
|
||||
logic [ 1:0] s_r_gap_q;
|
||||
logic [ 1:0] s_aw_stall_q;
|
||||
logic [ 1:0] s_w_stall_q;
|
||||
logic [ 1:0] s_b_gap_q;
|
||||
|
||||
axi4_if #(
|
||||
.ADDR_WIDTH(32),
|
||||
.DATA_WIDTH(64),
|
||||
.ID_WIDTH (3),
|
||||
.USER_WIDTH(1)
|
||||
) axi4 (
|
||||
.aclk (clk_i),
|
||||
.aresetn(rst_n_i)
|
||||
);
|
||||
|
||||
// Constrained responder: independent per-channel stall state machines
|
||||
// driven by the anyconst personalities; a burst carries one latched fault
|
||||
// personality.
|
||||
assign axi4.arready = !s_r_active_q && !s_r_beat_valid_q && (s_ar_stall_q >= f_ar_stall);
|
||||
assign axi4.rid = s_r_proto_rid_q ? 3'd1 : 3'd0;
|
||||
assign axi4.rdata = f_read_data;
|
||||
assign axi4.rresp = s_r_resp_q;
|
||||
assign axi4.rlast = (s_r_beats_q == 9'd1) || (s_r_proto_rlast_q && s_r_first_q);
|
||||
assign axi4.ruser = 1'b0;
|
||||
assign axi4.rvalid = s_r_beat_valid_q;
|
||||
|
||||
assign axi4.awready = !s_w_active_q && !s_w_bvalid_q && !s_w_bpending_q &&
|
||||
(s_aw_stall_q >= f_aw_stall);
|
||||
assign axi4.wready = s_w_active_q && (s_w_stall_q >= f_w_stall);
|
||||
assign axi4.bid = s_w_bid_bad_q ? 3'd1 : 3'd0;
|
||||
assign axi4.bresp = s_w_bresp_q;
|
||||
assign axi4.buser = 1'b0;
|
||||
assign axi4.bvalid = s_w_bvalid_q;
|
||||
|
||||
assign read_req_valid = rst_n_i && !s_read_done_q;
|
||||
assign read_addr = f_read_addr;
|
||||
assign read_bytes = f_read_bytes;
|
||||
assign read_data_ready = rst_n_i &&
|
||||
!((s_cycle_q >= {2'd0, f_rdstall_start}) &&
|
||||
(s_cycle_q < {2'd0, f_rdstall_stop}));
|
||||
assign write_req_valid = rst_n_i && !s_write_done_q;
|
||||
assign write_addr = f_write_addr;
|
||||
assign write_bytes = f_write_bytes;
|
||||
assign write_data_valid = rst_n_i;
|
||||
assign write_data = f_write_data;
|
||||
assign write_keep = f_write_keep;
|
||||
assign write_last = f_write_last;
|
||||
assign block_new = rst_n_i && (s_cycle_q >= {2'd0, f_block_start}) &&
|
||||
(s_cycle_q < {2'd0, f_block_stop});
|
||||
assign clear = rst_n_i && f_clear_en && (s_cycle_q == {2'd0, f_clear_at});
|
||||
assign read_fault = f_read_fault;
|
||||
assign read_data_const = f_read_data;
|
||||
assign write_data_const = f_write_data;
|
||||
assign write_keep_const = f_write_keep;
|
||||
|
||||
npu_dma u_dut (
|
||||
.clk_hp_i (clk_i),
|
||||
.rst_hp_n_i (rst_n_i),
|
||||
.clear_i (clear),
|
||||
.block_new_i (block_new),
|
||||
.pause_ack_o (pause_ack),
|
||||
.read_req_valid_i (read_req_valid),
|
||||
.read_req_ready_o (read_req_ready),
|
||||
.read_addr_i (read_addr),
|
||||
.read_bytes_i (read_bytes),
|
||||
.read_data_valid_o (read_data_valid),
|
||||
.read_data_ready_i (read_data_ready),
|
||||
.read_data_o (read_data),
|
||||
.read_keep_o (read_keep),
|
||||
.read_last_o (read_last),
|
||||
.write_req_valid_i (write_req_valid),
|
||||
.write_req_ready_o (write_req_ready),
|
||||
.write_addr_i (write_addr),
|
||||
.write_bytes_i (write_bytes),
|
||||
.write_data_valid_i(write_data_valid),
|
||||
.write_data_ready_o(write_data_ready),
|
||||
.write_data_i (write_data),
|
||||
.write_keep_i (write_keep),
|
||||
.write_last_i (write_last),
|
||||
.write_done_o (write_done),
|
||||
.busy_o (busy),
|
||||
.read_busy_o (read_busy),
|
||||
.write_busy_o (write_busy),
|
||||
.read_bytes_o (read_bytes_done),
|
||||
.write_bytes_o (write_bytes_done),
|
||||
.stall_cycles_o (stall_cycles),
|
||||
.fault_o (fault),
|
||||
.fault_code_o (fault_code),
|
||||
.fault_addr_o (fault_addr),
|
||||
.fault_resp_o (fault_resp),
|
||||
.read_cmd_err_o (read_cmd_err),
|
||||
.write_cmd_err_o (write_cmd_err),
|
||||
.axi4 (axi4)
|
||||
);
|
||||
|
||||
assign awvalid = axi4.awvalid;
|
||||
assign awready = axi4.awready;
|
||||
assign awid = axi4.awid;
|
||||
assign awaddr = axi4.awaddr;
|
||||
assign awlen = axi4.awlen;
|
||||
assign awsize = axi4.awsize;
|
||||
assign awburst = axi4.awburst;
|
||||
assign awlock = axi4.awlock;
|
||||
assign awcache = axi4.awcache;
|
||||
assign awprot = axi4.awprot;
|
||||
assign awqos = axi4.awqos;
|
||||
assign awregion = axi4.awregion;
|
||||
assign awuser = axi4.awuser;
|
||||
assign wvalid = axi4.wvalid;
|
||||
assign wready = axi4.wready;
|
||||
assign wdata = axi4.wdata;
|
||||
assign wstrb = axi4.wstrb;
|
||||
assign wlast = axi4.wlast;
|
||||
assign wuser = axi4.wuser;
|
||||
assign bvalid = axi4.bvalid;
|
||||
assign bready = axi4.bready;
|
||||
assign bid = axi4.bid;
|
||||
assign bresp = axi4.bresp;
|
||||
assign buser = axi4.buser;
|
||||
assign arvalid = axi4.arvalid;
|
||||
assign arready = axi4.arready;
|
||||
assign arid = axi4.arid;
|
||||
assign araddr = axi4.araddr;
|
||||
assign arlen = axi4.arlen;
|
||||
assign arsize = axi4.arsize;
|
||||
assign arburst = axi4.arburst;
|
||||
assign arlock = axi4.arlock;
|
||||
assign arcache = axi4.arcache;
|
||||
assign arprot = axi4.arprot;
|
||||
assign arqos = axi4.arqos;
|
||||
assign arregion = axi4.arregion;
|
||||
assign aruser = axi4.aruser;
|
||||
assign rvalid = axi4.rvalid;
|
||||
assign rready = axi4.rready;
|
||||
assign rid = axi4.rid;
|
||||
assign rdata = axi4.rdata;
|
||||
assign rresp = axi4.rresp;
|
||||
assign rlast = axi4.rlast;
|
||||
assign ruser = axi4.ruser;
|
||||
assign rfifo_count = u_dut.s_rfifo_count_q;
|
||||
assign wfifo_count = u_dut.s_wfifo_count_q;
|
||||
|
||||
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 <= 6'd0;
|
||||
end else if (s_cycle_q != 6'h3f) begin
|
||||
s_cycle_q <= s_cycle_q + 6'd1;
|
||||
end
|
||||
end
|
||||
|
||||
// Responder state. The clear_i branch is the coordinated fabric flush:
|
||||
// in-flight bursts cancel with the engine, so no residual R/B beat is ever
|
||||
// presented to a flushed receiver. A burst whose malformed beat has been
|
||||
// accepted (bad RID or early RLAST) is retired silently because the engine
|
||||
// quarantines its receiver until the flush.
|
||||
always_ff @(posedge clk_i or negedge rst_n_i) begin
|
||||
if (!rst_n_i) begin
|
||||
s_r_active_q <= 1'b0;
|
||||
s_r_beats_q <= 9'd0;
|
||||
s_r_beat_valid_q <= 1'b0;
|
||||
s_r_first_q <= 1'b0;
|
||||
s_r_resp_q <= AxiRespOkay;
|
||||
s_r_proto_rid_q <= 1'b0;
|
||||
s_r_proto_rlast_q <= 1'b0;
|
||||
s_w_active_q <= 1'b0;
|
||||
s_w_beats_q <= 9'd0;
|
||||
s_w_bvalid_q <= 1'b0;
|
||||
s_w_bpending_q <= 1'b0;
|
||||
s_w_bresp_q <= AxiRespOkay;
|
||||
s_w_bid_bad_q <= 1'b0;
|
||||
s_ar_stall_q <= 2'd0;
|
||||
s_r_gap_q <= 2'd0;
|
||||
s_aw_stall_q <= 2'd0;
|
||||
s_w_stall_q <= 2'd0;
|
||||
s_b_gap_q <= 2'd0;
|
||||
s_read_done_q <= 1'b0;
|
||||
s_write_done_q <= 1'b0;
|
||||
end else if (clear) begin
|
||||
s_r_active_q <= 1'b0;
|
||||
s_r_beats_q <= 9'd0;
|
||||
s_r_beat_valid_q <= 1'b0;
|
||||
s_r_first_q <= 1'b0;
|
||||
s_r_resp_q <= AxiRespOkay;
|
||||
s_r_proto_rid_q <= 1'b0;
|
||||
s_r_proto_rlast_q <= 1'b0;
|
||||
s_w_active_q <= 1'b0;
|
||||
s_w_beats_q <= 9'd0;
|
||||
s_w_bvalid_q <= 1'b0;
|
||||
s_w_bpending_q <= 1'b0;
|
||||
s_w_bresp_q <= AxiRespOkay;
|
||||
s_w_bid_bad_q <= 1'b0;
|
||||
s_ar_stall_q <= 2'd0;
|
||||
s_r_gap_q <= 2'd0;
|
||||
s_aw_stall_q <= 2'd0;
|
||||
s_w_stall_q <= 2'd0;
|
||||
s_b_gap_q <= 2'd0;
|
||||
// A flushed command is offered again, so post-flush retry is covered.
|
||||
s_read_done_q <= 1'b0;
|
||||
s_write_done_q <= 1'b0;
|
||||
end else begin
|
||||
if (read_req_valid && read_req_ready) begin
|
||||
s_read_done_q <= 1'b1;
|
||||
end
|
||||
if (write_req_valid && write_req_ready) begin
|
||||
s_write_done_q <= 1'b1;
|
||||
end
|
||||
// Per-channel stall counters saturate above the anyconst personality.
|
||||
if (axi4.arvalid && axi4.arready) begin
|
||||
s_ar_stall_q <= 2'd0;
|
||||
end else if (axi4.arvalid && !axi4.arready && (s_ar_stall_q != 2'd3)) begin
|
||||
s_ar_stall_q <= s_ar_stall_q + 2'd1;
|
||||
end
|
||||
if (axi4.awvalid && axi4.awready) begin
|
||||
s_aw_stall_q <= 2'd0;
|
||||
end else if (axi4.awvalid && !axi4.awready && (s_aw_stall_q != 2'd3)) begin
|
||||
s_aw_stall_q <= s_aw_stall_q + 2'd1;
|
||||
end
|
||||
if (axi4.arvalid && axi4.arready) begin
|
||||
s_r_active_q <= 1'b1;
|
||||
s_r_beats_q <= {1'b0, axi4.arlen} + 9'd1;
|
||||
s_r_first_q <= 1'b1;
|
||||
s_r_gap_q <= 2'd0;
|
||||
if (f_read_fault == 2'd1) begin
|
||||
s_r_resp_q <= AxiRespSlverr;
|
||||
end else if (f_read_fault == 2'd2) begin
|
||||
s_r_resp_q <= AxiRespDecerr;
|
||||
end else begin
|
||||
s_r_resp_q <= AxiRespOkay;
|
||||
end
|
||||
s_r_proto_rid_q <= (f_read_fault == 2'd3) && !f_read_proto_rlast;
|
||||
s_r_proto_rlast_q <= (f_read_fault == 2'd3) && f_read_proto_rlast;
|
||||
end
|
||||
if (s_r_active_q && !s_r_beat_valid_q) begin
|
||||
if (s_r_gap_q >= f_r_gap) begin
|
||||
s_r_beat_valid_q <= 1'b1;
|
||||
end else begin
|
||||
s_r_gap_q <= s_r_gap_q + 2'd1;
|
||||
end
|
||||
end
|
||||
if (axi4.rvalid && axi4.rready) begin
|
||||
s_r_beat_valid_q <= 1'b0;
|
||||
s_r_first_q <= 1'b0;
|
||||
s_r_gap_q <= 2'd0;
|
||||
if ((s_r_beats_q == 9'd1) || axi4.rlast || s_r_proto_rid_q) begin
|
||||
s_r_active_q <= 1'b0;
|
||||
s_r_beats_q <= 9'd0;
|
||||
end else begin
|
||||
s_r_beats_q <= s_r_beats_q - 9'd1;
|
||||
end
|
||||
end
|
||||
if (axi4.awvalid && axi4.awready) begin
|
||||
s_w_active_q <= 1'b1;
|
||||
s_w_beats_q <= {1'b0, axi4.awlen} + 9'd1;
|
||||
s_w_stall_q <= 2'd0;
|
||||
if (f_write_fault == 2'd1) begin
|
||||
s_w_bresp_q <= AxiRespSlverr;
|
||||
end else if (f_write_fault == 2'd2) begin
|
||||
s_w_bresp_q <= AxiRespDecerr;
|
||||
end else begin
|
||||
s_w_bresp_q <= AxiRespOkay;
|
||||
end
|
||||
s_w_bid_bad_q <= (f_write_fault == 2'd3);
|
||||
end
|
||||
if (axi4.wvalid && !axi4.wready && (s_w_stall_q != 2'd3)) begin
|
||||
s_w_stall_q <= s_w_stall_q + 2'd1;
|
||||
end
|
||||
if (axi4.wvalid && axi4.wready) begin
|
||||
s_w_stall_q <= 2'd0;
|
||||
if (s_w_beats_q == 9'd1) begin
|
||||
s_w_active_q <= 1'b0;
|
||||
s_w_beats_q <= 9'd0;
|
||||
s_w_bpending_q <= 1'b1;
|
||||
s_b_gap_q <= 2'd0;
|
||||
end else begin
|
||||
s_w_beats_q <= s_w_beats_q - 9'd1;
|
||||
end
|
||||
end
|
||||
if (s_w_bpending_q) begin
|
||||
if (s_b_gap_q >= f_b_gap) begin
|
||||
s_w_bpending_q <= 1'b0;
|
||||
s_w_bvalid_q <= 1'b1;
|
||||
end else begin
|
||||
s_b_gap_q <= s_b_gap_q + 2'd1;
|
||||
end
|
||||
end
|
||||
if (axi4.bvalid && axi4.bready) begin
|
||||
s_w_bvalid_q <= 1'b0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,640 @@
|
||||
// Copyright (c) 2026 Yuchi Miao <miaoyuchi@ict.ac.cn>
|
||||
// retroSoC is licensed under Mulan PSL v2.
|
||||
// You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
// You may obtain a copy of Mulan PSL v2 at:
|
||||
// http://license.coscl.org.cn/MulanPSL2
|
||||
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||
// See the Mulan PSL v2 for more details.
|
||||
|
||||
// Property set for the NPU-P3 AXI4 data-plane engine (npu_dma). A shadow
|
||||
// scoreboard tracks every accepted command, burst and beat from the bus so
|
||||
// the contract checks below are purely bus-derived.
|
||||
//
|
||||
// Assumption list (all explicit; nothing else is assumed):
|
||||
// (A1) command legality: non-zero byte counts bounded to 128 bytes and no
|
||||
// 32-bit address wrap, so bounded runs reach the deepest burst plans
|
||||
// and the combinational command rejection never fires;
|
||||
// (A2) the write payload stream obeys valid/ready: the last flag is stable
|
||||
// while a beat is stalled (valid itself and data/keep are constant).
|
||||
// Commands are held by the harness until acceptance, so no command-channel
|
||||
// assumption is needed.
|
||||
// The AXI4 responder is RTL inside npu_dma_formal.sv (not assumed), so R/B
|
||||
// stability and response placement are checked there as model self-checks.
|
||||
// No fairness assumptions are needed: every assertion is a safety property.
|
||||
// Responder stalls are bounded (0..3 cycles per handshake beat, anyconst per
|
||||
// run — the engine holds its registers during a stall, so longer stalls add
|
||||
// no new engine states), and the covers below stand in as bounded-liveness
|
||||
// spot checks.
|
||||
|
||||
module npu_dma_formal;
|
||||
// verilog_format: off -- formal observations mirror npu_dma_formal_design ports.
|
||||
(* anyseq *) (* gclk *) reg clk_i;
|
||||
wire rst_n_i;
|
||||
wire f_past_valid;
|
||||
wire clear;
|
||||
wire block_new;
|
||||
wire pause_ack;
|
||||
wire read_req_valid;
|
||||
wire read_req_ready;
|
||||
wire [31:0] read_addr;
|
||||
wire [31:0] read_bytes;
|
||||
wire read_data_valid;
|
||||
wire read_data_ready;
|
||||
wire [63:0] read_data;
|
||||
wire [ 7:0] read_keep;
|
||||
wire read_last;
|
||||
wire write_req_valid;
|
||||
wire write_req_ready;
|
||||
wire [31:0] write_addr;
|
||||
wire [31:0] write_bytes;
|
||||
wire write_data_valid;
|
||||
wire write_data_ready;
|
||||
wire [63:0] write_data;
|
||||
wire [ 7:0] write_keep;
|
||||
wire write_last;
|
||||
wire write_done;
|
||||
wire busy;
|
||||
wire read_busy;
|
||||
wire write_busy;
|
||||
wire [63:0] read_bytes_done;
|
||||
wire [63:0] write_bytes_done;
|
||||
wire [63:0] stall_cycles;
|
||||
wire fault;
|
||||
wire [ 3:0] fault_code;
|
||||
wire [31:0] fault_addr;
|
||||
wire [ 1:0] fault_resp;
|
||||
wire read_cmd_err;
|
||||
wire write_cmd_err;
|
||||
wire [ 4:0] rfifo_count;
|
||||
wire [ 4:0] wfifo_count;
|
||||
wire [ 1:0] read_fault;
|
||||
wire [63:0] read_data_const;
|
||||
wire [63:0] write_data_const;
|
||||
wire [ 7:0] write_keep_const;
|
||||
wire awvalid;
|
||||
wire awready;
|
||||
wire [ 2:0] awid;
|
||||
wire [31:0] awaddr;
|
||||
wire [ 7:0] awlen;
|
||||
wire [ 2:0] awsize;
|
||||
wire [ 1:0] awburst;
|
||||
wire awlock;
|
||||
wire [ 3:0] awcache;
|
||||
wire [ 2:0] awprot;
|
||||
wire [ 3:0] awqos;
|
||||
wire [ 3:0] awregion;
|
||||
wire awuser;
|
||||
wire wvalid;
|
||||
wire wready;
|
||||
wire [63:0] wdata;
|
||||
wire [ 7:0] wstrb;
|
||||
wire wlast;
|
||||
wire wuser;
|
||||
wire bvalid;
|
||||
wire bready;
|
||||
wire [ 2:0] bid;
|
||||
wire [ 1:0] bresp;
|
||||
wire buser;
|
||||
wire arvalid;
|
||||
wire arready;
|
||||
wire [ 2:0] arid;
|
||||
wire [31:0] araddr;
|
||||
wire [ 7:0] arlen;
|
||||
wire [ 2:0] arsize;
|
||||
wire [ 1:0] arburst;
|
||||
wire arlock;
|
||||
wire [ 3:0] arcache;
|
||||
wire [ 2:0] arprot;
|
||||
wire [ 3:0] arqos;
|
||||
wire [ 3:0] arregion;
|
||||
wire aruser;
|
||||
wire rvalid;
|
||||
wire rready;
|
||||
wire [ 2:0] rid;
|
||||
wire [63:0] rdata;
|
||||
wire [ 1:0] rresp;
|
||||
wire rlast;
|
||||
wire ruser;
|
||||
// verilog_format: on
|
||||
|
||||
logic f_rd_outstanding_q;
|
||||
logic [ 8:0] f_rd_beats_left_q;
|
||||
logic [ 2:0] f_rd_size_q;
|
||||
logic [31:0] f_rd_beat_addr_q;
|
||||
logic [31:0] f_rd_seg_addr_q;
|
||||
logic [32:0] f_rd_seg_end_q;
|
||||
logic [31:0] f_rd_next_burst_q;
|
||||
logic f_rd_done_q;
|
||||
logic f_wr_aw_q;
|
||||
logic [ 8:0] f_wr_beats_left_q;
|
||||
logic f_wr_b_q;
|
||||
logic [31:0] f_wr_beat_addr_q;
|
||||
logic [31:0] f_wr_next_burst_q;
|
||||
|
||||
wire f_rd_cmd_accept;
|
||||
wire f_wr_cmd_accept;
|
||||
wire f_ar_accept;
|
||||
wire f_r_accept;
|
||||
wire f_r_last_due;
|
||||
wire f_r_proto_now;
|
||||
wire f_aw_accept;
|
||||
wire f_w_accept;
|
||||
wire f_b_accept;
|
||||
wire [ 3:0] f_wstrb_ones;
|
||||
wire f_evt_proto_rd;
|
||||
wire f_evt_axi_rd;
|
||||
wire f_evt_proto_wr;
|
||||
wire f_evt_axi_wr;
|
||||
|
||||
assign f_rd_cmd_accept = read_req_valid && read_req_ready;
|
||||
assign f_wr_cmd_accept = write_req_valid && write_req_ready;
|
||||
assign f_ar_accept = arvalid && arready;
|
||||
assign f_r_accept = rvalid && rready;
|
||||
assign f_r_last_due = f_rd_beats_left_q == 9'd1;
|
||||
assign f_r_proto_now = rvalid && f_rd_outstanding_q && ((rid != 3'd0) || (rlast != f_r_last_due));
|
||||
assign f_aw_accept = awvalid && awready;
|
||||
assign f_w_accept = wvalid && wready;
|
||||
assign f_b_accept = bvalid && bready;
|
||||
assign f_wstrb_ones = {3'd0, wstrb[0]} + {3'd0, wstrb[1]} + {3'd0, wstrb[2]} +
|
||||
{3'd0, wstrb[3]} + {3'd0, wstrb[4]} + {3'd0, wstrb[5]} +
|
||||
{3'd0, wstrb[6]} + {3'd0, wstrb[7]};
|
||||
// Bus-visible fault events, mirroring the engine's fault sources: a
|
||||
// malformed R beat faults on presentation, a malformed BID faults on
|
||||
// presentation, and error responses fault on acceptance.
|
||||
assign f_evt_proto_rd = f_r_proto_now && !clear;
|
||||
assign f_evt_axi_rd = f_r_accept && (rresp != 2'b00);
|
||||
assign f_evt_proto_wr = bvalid && f_wr_b_q && (bid != 3'd0) && !clear;
|
||||
assign f_evt_axi_wr = f_b_accept && (bresp != 2'b00);
|
||||
|
||||
npu_dma_formal_design u_design (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i (rst_n_i),
|
||||
.f_past_valid (f_past_valid),
|
||||
.clear (clear),
|
||||
.block_new (block_new),
|
||||
.pause_ack (pause_ack),
|
||||
.read_req_valid (read_req_valid),
|
||||
.read_req_ready (read_req_ready),
|
||||
.read_addr (read_addr),
|
||||
.read_bytes (read_bytes),
|
||||
.read_data_valid (read_data_valid),
|
||||
.read_data_ready (read_data_ready),
|
||||
.read_data (read_data),
|
||||
.read_keep (read_keep),
|
||||
.read_last (read_last),
|
||||
.write_req_valid (write_req_valid),
|
||||
.write_req_ready (write_req_ready),
|
||||
.write_addr (write_addr),
|
||||
.write_bytes (write_bytes),
|
||||
.write_data_valid(write_data_valid),
|
||||
.write_data_ready(write_data_ready),
|
||||
.write_data (write_data),
|
||||
.write_keep (write_keep),
|
||||
.write_last (write_last),
|
||||
.write_done (write_done),
|
||||
.busy (busy),
|
||||
.read_busy (read_busy),
|
||||
.write_busy (write_busy),
|
||||
.read_bytes_done (read_bytes_done),
|
||||
.write_bytes_done(write_bytes_done),
|
||||
.stall_cycles (stall_cycles),
|
||||
.fault (fault),
|
||||
.fault_code (fault_code),
|
||||
.fault_addr (fault_addr),
|
||||
.fault_resp (fault_resp),
|
||||
.read_cmd_err (read_cmd_err),
|
||||
.write_cmd_err (write_cmd_err),
|
||||
.rfifo_count (rfifo_count),
|
||||
.wfifo_count (wfifo_count),
|
||||
.read_fault (read_fault),
|
||||
.read_data_const (read_data_const),
|
||||
.write_data_const(write_data_const),
|
||||
.write_keep_const(write_keep_const),
|
||||
.awvalid (awvalid),
|
||||
.awready (awready),
|
||||
.awid (awid),
|
||||
.awaddr (awaddr),
|
||||
.awlen (awlen),
|
||||
.awsize (awsize),
|
||||
.awburst (awburst),
|
||||
.awlock (awlock),
|
||||
.awcache (awcache),
|
||||
.awprot (awprot),
|
||||
.awqos (awqos),
|
||||
.awregion (awregion),
|
||||
.awuser (awuser),
|
||||
.wvalid (wvalid),
|
||||
.wready (wready),
|
||||
.wdata (wdata),
|
||||
.wstrb (wstrb),
|
||||
.wlast (wlast),
|
||||
.wuser (wuser),
|
||||
.bvalid (bvalid),
|
||||
.bready (bready),
|
||||
.bid (bid),
|
||||
.bresp (bresp),
|
||||
.buser (buser),
|
||||
.arvalid (arvalid),
|
||||
.arready (arready),
|
||||
.arid (arid),
|
||||
.araddr (araddr),
|
||||
.arlen (arlen),
|
||||
.arsize (arsize),
|
||||
.arburst (arburst),
|
||||
.arlock (arlock),
|
||||
.arcache (arcache),
|
||||
.arprot (arprot),
|
||||
.arqos (arqos),
|
||||
.arregion (arregion),
|
||||
.aruser (aruser),
|
||||
.rvalid (rvalid),
|
||||
.rready (rready),
|
||||
.rid (rid),
|
||||
.rdata (rdata),
|
||||
.rresp (rresp),
|
||||
.rlast (rlast),
|
||||
.ruser (ruser)
|
||||
);
|
||||
|
||||
// Shadow scoreboard. A malformed accepted R/B beat quarantines the
|
||||
// receiver in the engine, so the shadow retains the burst obligation until
|
||||
// the flush; address/beat tracking otherwise follows the accepted traffic.
|
||||
always @(posedge clk_i) begin
|
||||
if (!rst_n_i) begin
|
||||
f_rd_outstanding_q <= 1'b0;
|
||||
f_rd_beats_left_q <= 9'd0;
|
||||
f_rd_size_q <= 3'd0;
|
||||
f_rd_beat_addr_q <= 32'd0;
|
||||
f_rd_seg_addr_q <= 32'd0;
|
||||
f_rd_seg_end_q <= 33'd0;
|
||||
f_rd_next_burst_q <= 32'd0;
|
||||
f_rd_done_q <= 1'b0;
|
||||
f_wr_aw_q <= 1'b0;
|
||||
f_wr_beats_left_q <= 9'd0;
|
||||
f_wr_b_q <= 1'b0;
|
||||
f_wr_beat_addr_q <= 32'd0;
|
||||
f_wr_next_burst_q <= 32'd0;
|
||||
end else if (clear) begin
|
||||
f_rd_outstanding_q <= 1'b0;
|
||||
f_rd_beats_left_q <= 9'd0;
|
||||
f_rd_size_q <= 3'd0;
|
||||
f_rd_beat_addr_q <= 32'd0;
|
||||
f_rd_seg_addr_q <= 32'd0;
|
||||
f_rd_seg_end_q <= 33'd0;
|
||||
f_rd_next_burst_q <= 32'd0;
|
||||
f_rd_done_q <= 1'b0;
|
||||
f_wr_aw_q <= 1'b0;
|
||||
f_wr_beats_left_q <= 9'd0;
|
||||
f_wr_b_q <= 1'b0;
|
||||
f_wr_beat_addr_q <= 32'd0;
|
||||
f_wr_next_burst_q <= 32'd0;
|
||||
end else begin
|
||||
if (f_rd_cmd_accept) begin
|
||||
f_rd_seg_addr_q <= read_addr;
|
||||
f_rd_seg_end_q <= {1'b0, read_addr} + {1'b0, read_bytes};
|
||||
f_rd_next_burst_q <= read_addr;
|
||||
f_rd_done_q <= 1'b0;
|
||||
end
|
||||
if (f_wr_cmd_accept) begin
|
||||
f_wr_next_burst_q <= {write_addr[31:3], 3'b000};
|
||||
end
|
||||
if (f_r_accept) begin
|
||||
if ((rid != 3'd0) || (rlast != f_r_last_due)) begin
|
||||
f_rd_outstanding_q <= 1'b1;
|
||||
end else if (f_r_last_due) begin
|
||||
f_rd_outstanding_q <= 1'b0;
|
||||
if ((f_rd_next_burst_q == f_rd_seg_end_q[31:0]) && (rresp == 2'b00)) begin
|
||||
f_rd_done_q <= 1'b1;
|
||||
end
|
||||
end else begin
|
||||
f_rd_beats_left_q <= f_rd_beats_left_q - 9'd1;
|
||||
f_rd_beat_addr_q <= f_rd_beat_addr_q + (32'd1 << f_rd_size_q);
|
||||
end
|
||||
end
|
||||
if (f_w_accept) begin
|
||||
if (f_wr_beats_left_q == 9'd1) begin
|
||||
f_wr_aw_q <= 1'b0;
|
||||
f_wr_b_q <= 1'b1;
|
||||
end else begin
|
||||
f_wr_beats_left_q <= f_wr_beats_left_q - 9'd1;
|
||||
f_wr_beat_addr_q <= f_wr_beat_addr_q + 32'd8;
|
||||
end
|
||||
end
|
||||
if (f_b_accept && (bid == 3'd0)) begin
|
||||
f_wr_b_q <= 1'b0;
|
||||
end
|
||||
if (f_ar_accept) begin
|
||||
f_rd_outstanding_q <= 1'b1;
|
||||
f_rd_beats_left_q <= {1'b0, arlen} + 9'd1;
|
||||
f_rd_size_q <= arsize;
|
||||
f_rd_beat_addr_q <= araddr;
|
||||
f_rd_next_burst_q <= araddr + (({24'd0, arlen} + 32'd1) << arsize);
|
||||
end
|
||||
if (f_aw_accept) begin
|
||||
f_wr_aw_q <= 1'b1;
|
||||
f_wr_beats_left_q <= {1'b0, awlen} + 9'd1;
|
||||
f_wr_beat_addr_q <= awaddr;
|
||||
f_wr_next_burst_q <= awaddr + (({24'd0, awlen} + 32'd1) << 3);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Assumptions A1 (command legality) and A2 (write stream last-flag
|
||||
// stability). Commands are held by the harness until accepted, so no
|
||||
// command-channel assumption is needed.
|
||||
always @(posedge clk_i) begin
|
||||
if (rst_n_i) begin
|
||||
assume (read_bytes != 32'd0);
|
||||
assume (read_bytes <= 32'd128);
|
||||
assume ({1'b0, read_addr} + {1'b0, read_bytes} <= 33'h1_0000_0000);
|
||||
assume (write_bytes != 32'd0);
|
||||
assume (write_bytes <= 32'd128);
|
||||
assume ({1'b0, write_addr} + {1'b0, write_bytes} <= 33'h1_0000_0000);
|
||||
end
|
||||
if (f_past_valid && $past(rst_n_i)) begin
|
||||
if ($past(write_data_valid && !write_data_ready)) begin
|
||||
assume (write_last == $past(write_last));
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Responder model self-checks: a failure here is a harness bug, not an
|
||||
// engine bug. Responses arrive only inside an outstanding burst, payload
|
||||
// is stable through any stall (the flush invalidates in-flight beats), and
|
||||
// RLAST is well placed unless a protocol fault is being injected.
|
||||
always @(posedge clk_i) begin
|
||||
if (rst_n_i) begin
|
||||
if (rvalid) begin
|
||||
assert (f_rd_outstanding_q);
|
||||
end
|
||||
if (bvalid) begin
|
||||
assert (f_wr_b_q);
|
||||
end
|
||||
if (rvalid && (read_fault != 2'd3)) begin
|
||||
assert (rlast == f_r_last_due);
|
||||
end
|
||||
end
|
||||
if (f_past_valid && $past(rst_n_i) && !$past(clear) && !clear) begin
|
||||
if ($past(rvalid && !rready)) begin
|
||||
assert (rvalid);
|
||||
assert (rid == $past(rid));
|
||||
assert (rdata == $past(rdata));
|
||||
assert (rresp == $past(rresp));
|
||||
assert (rlast == $past(rlast));
|
||||
assert (ruser == $past(ruser));
|
||||
end
|
||||
if ($past(bvalid && !bready)) begin
|
||||
assert (bvalid);
|
||||
assert (bid == $past(bid));
|
||||
assert (bresp == $past(bresp));
|
||||
assert (buser == $past(buser));
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Engine contract, current-cycle checks.
|
||||
always @(posedge clk_i) begin
|
||||
if (rst_n_i) begin
|
||||
// Emitted read-address attributes: ID zero, INCR, at most 16 beats,
|
||||
// sizes 1/2/4/8 with narrow transfers only as single-beat edges,
|
||||
// naturally aligned, never crossing a 4 KiB page, never fetching
|
||||
// outside the accepted segment, and only with an empty receive FIFO.
|
||||
if (arvalid) begin
|
||||
assert (arid == 3'd0);
|
||||
assert (arburst == 2'b01);
|
||||
assert (arlen <= 8'd15);
|
||||
assert (arsize <= 3'd3);
|
||||
assert ((arsize == 3'd3) || (arlen == 8'd0));
|
||||
assert ((araddr & ((32'd1 << arsize) - 32'd1)) == 32'd0);
|
||||
assert ({1'b0, araddr[11:0]} + (({5'd0, arlen} + 13'd1) << arsize) <= 13'd4096);
|
||||
assert (arlock == 1'b0);
|
||||
assert (arcache == 4'd0);
|
||||
assert (arprot == 3'd0);
|
||||
assert (arqos == 4'd0);
|
||||
assert (arregion == 4'd0);
|
||||
assert (aruser == 1'b0);
|
||||
assert (rfifo_count == 5'd0);
|
||||
assert (araddr == f_rd_next_burst_q);
|
||||
assert ({1'b0, araddr} >= {1'b0, f_rd_seg_addr_q});
|
||||
assert ({1'b0, araddr} + (({25'd0, arlen} + 33'd1) << arsize) <= f_rd_seg_end_q);
|
||||
end
|
||||
// Emitted write-address attributes: ID zero, INCR, full-width aligned
|
||||
// beats only, at most 16 beats, never crossing a 4 KiB page, with the
|
||||
// complete payload already staged in the write FIFO.
|
||||
if (awvalid) begin
|
||||
assert (awid == 3'd0);
|
||||
assert (awburst == 2'b01);
|
||||
assert (awlen <= 8'd15);
|
||||
assert (awsize == 3'd3);
|
||||
assert (awaddr[2:0] == 3'd0);
|
||||
assert ({1'b0, awaddr[11:0]} + (({5'd0, awlen} + 13'd1) << 3) <= 13'd4096);
|
||||
assert (awlock == 1'b0);
|
||||
assert (awcache == 4'd0);
|
||||
assert (awprot == 3'd0);
|
||||
assert (awqos == 4'd0);
|
||||
assert (awregion == 4'd0);
|
||||
assert (awuser == 1'b0);
|
||||
assert ({4'd0, wfifo_count} == ({1'b0, awlen} + 9'd1));
|
||||
assert (awaddr == f_wr_next_burst_q);
|
||||
end
|
||||
if (wvalid) begin
|
||||
assert (wuser == 1'b0);
|
||||
// W is only ever driven after its AW handshake.
|
||||
assert (f_wr_aw_q);
|
||||
// The write FIFO passes the staged payload through unmodified.
|
||||
assert (wdata == write_data_const);
|
||||
assert (wstrb == write_keep_const);
|
||||
end
|
||||
if (read_data_valid) begin
|
||||
// The receive FIFO passes the response data through unmodified.
|
||||
assert (read_data == read_data_const);
|
||||
end
|
||||
// At most one outstanding transaction per direction.
|
||||
if (f_ar_accept) begin
|
||||
assert (!f_rd_outstanding_q);
|
||||
end
|
||||
if (f_aw_accept) begin
|
||||
assert (!f_wr_aw_q);
|
||||
assert (!f_wr_b_q);
|
||||
end
|
||||
// Accepted-transfer conservation: the engine never consumes more beats
|
||||
// than the burst carries, WLAST marks exactly the final W beat, and the
|
||||
// receivers close after the final beat of a burst.
|
||||
if (f_r_accept) begin
|
||||
assert (f_rd_beats_left_q != 9'd0);
|
||||
end
|
||||
if (f_w_accept) begin
|
||||
assert (f_wr_beats_left_q != 9'd0);
|
||||
assert (wlast == (f_wr_beats_left_q == 9'd1));
|
||||
end
|
||||
if (write_done) begin
|
||||
assert (f_b_accept);
|
||||
assert (bid == 3'd0);
|
||||
assert (bresp == 2'b00);
|
||||
end
|
||||
// Legal commands never raise the combinational rejection.
|
||||
assert (!read_cmd_err);
|
||||
// Pause acknowledge is exactly "block_new held with no AR-accepted
|
||||
// read and no AW-accepted write obligation".
|
||||
if (block_new && !f_rd_outstanding_q && !f_wr_aw_q && !f_wr_b_q) begin
|
||||
assert (pause_ack);
|
||||
end
|
||||
if (pause_ack) begin
|
||||
assert (block_new);
|
||||
assert (!f_rd_outstanding_q);
|
||||
assert (!f_wr_aw_q);
|
||||
assert (!f_wr_b_q);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Engine contract, registered checks.
|
||||
always @(posedge clk_i) begin
|
||||
if (f_past_valid && $past(rst_n_i)) begin
|
||||
// VALID payloads stay stable through any stall on the engine-driven
|
||||
// channels and the read-data stream; only the coordinated fabric flush
|
||||
// may retract a presented transfer.
|
||||
if ($past(arvalid && !arready) && !clear) begin
|
||||
assert (arvalid);
|
||||
assert (arid == $past(arid));
|
||||
assert (araddr == $past(araddr));
|
||||
assert (arlen == $past(arlen));
|
||||
assert (arsize == $past(arsize));
|
||||
assert (arburst == $past(arburst));
|
||||
assert (arlock == $past(arlock));
|
||||
assert (arcache == $past(arcache));
|
||||
assert (arprot == $past(arprot));
|
||||
assert (arqos == $past(arqos));
|
||||
assert (arregion == $past(arregion));
|
||||
assert (aruser == $past(aruser));
|
||||
end
|
||||
if ($past(awvalid && !awready) && !clear) begin
|
||||
assert (awvalid);
|
||||
assert (awid == $past(awid));
|
||||
assert (awaddr == $past(awaddr));
|
||||
assert (awlen == $past(awlen));
|
||||
assert (awsize == $past(awsize));
|
||||
assert (awburst == $past(awburst));
|
||||
assert (awlock == $past(awlock));
|
||||
assert (awcache == $past(awcache));
|
||||
assert (awprot == $past(awprot));
|
||||
assert (awqos == $past(awqos));
|
||||
assert (awregion == $past(awregion));
|
||||
assert (awuser == $past(awuser));
|
||||
end
|
||||
if ($past(wvalid && !wready) && !clear) begin
|
||||
assert (wvalid);
|
||||
assert (wdata == $past(wdata));
|
||||
assert (wstrb == $past(wstrb));
|
||||
assert (wlast == $past(wlast));
|
||||
assert (wuser == $past(wuser));
|
||||
end
|
||||
if ($past(read_data_valid && !read_data_ready) && !clear) begin
|
||||
assert (read_data_valid);
|
||||
assert (read_data == $past(read_data));
|
||||
assert (read_keep == $past(read_keep));
|
||||
assert (read_last == $past(read_last));
|
||||
end
|
||||
// No new AR/AW presentation begins while the pause is acknowledged.
|
||||
if ($past(pause_ack && !arvalid) && !$past(clear)) begin
|
||||
assert (!arvalid);
|
||||
end
|
||||
if ($past(pause_ack && !awvalid) && !$past(clear)) begin
|
||||
assert (!awvalid);
|
||||
end
|
||||
// Receivers close after the final accepted beat of a burst.
|
||||
if ($past(f_r_accept && f_r_last_due)) begin
|
||||
assert (!rready);
|
||||
end
|
||||
if ($past(f_w_accept && (f_wr_beats_left_q == 9'd1))) begin
|
||||
assert (!wvalid);
|
||||
end
|
||||
// Sticky first fault with deterministic priority
|
||||
// AXI_PROTOCOL (6) > AXI_READ (4) > AXI_WRITE (5).
|
||||
if (!$past(fault)) begin
|
||||
if ($past(f_evt_proto_rd)) begin
|
||||
assert (fault);
|
||||
assert (fault_code == 4'd6);
|
||||
assert (fault_resp == $past(rresp));
|
||||
assert (fault_addr == $past(f_rd_beat_addr_q));
|
||||
end else if ($past(f_evt_proto_wr)) begin
|
||||
assert (fault);
|
||||
assert (fault_code == 4'd6);
|
||||
assert (fault_resp == $past(bresp));
|
||||
assert (fault_addr == $past(f_wr_beat_addr_q));
|
||||
end else if ($past(f_evt_axi_rd)) begin
|
||||
assert (fault);
|
||||
assert (fault_code == 4'd4);
|
||||
assert (fault_resp == $past(rresp));
|
||||
assert (fault_addr == $past(f_rd_beat_addr_q));
|
||||
end else if ($past(f_evt_axi_wr)) begin
|
||||
assert (fault);
|
||||
assert (fault_code == 4'd5);
|
||||
assert (fault_resp == $past(bresp));
|
||||
assert (fault_addr == $past(f_wr_beat_addr_q));
|
||||
end
|
||||
end
|
||||
// The fault is sticky with stable fields until clear_i.
|
||||
if ($past(fault) && !$past(clear) && !clear) begin
|
||||
assert (fault);
|
||||
assert (fault_code == $past(fault_code));
|
||||
assert (fault_addr == $past(fault_addr));
|
||||
assert (fault_resp == $past(fault_resp));
|
||||
end
|
||||
if (fault) begin
|
||||
assert ((fault_code == 4'd4) || (fault_code == 4'd5) || (fault_code == 4'd6));
|
||||
end
|
||||
// Counters are monotone without clear_i and count exactly the accepted
|
||||
// traffic (addressed read lanes, accepted WSTRB bytes).
|
||||
if (!$past(clear) && !clear) begin
|
||||
assert (read_bytes_done >= $past(read_bytes_done));
|
||||
assert (write_bytes_done >= $past(write_bytes_done));
|
||||
assert (stall_cycles >= $past(stall_cycles));
|
||||
end
|
||||
if ($past(f_r_accept)) begin
|
||||
assert (read_bytes_done == ($past(read_bytes_done) + (64'd1 << $past(f_rd_size_q))));
|
||||
end
|
||||
if ($past(f_w_accept)) begin
|
||||
assert (write_bytes_done == ($past(write_bytes_done) + {60'd0, $past(f_wstrb_ones)}));
|
||||
end
|
||||
end
|
||||
// Valids stay low out of reset. pause_ack is exempt: it combinationally
|
||||
// follows block_new on an idle engine, which is legal at any time.
|
||||
if (f_past_valid && rst_n_i && !$past(rst_n_i)) begin
|
||||
assert (!arvalid && !awvalid && !wvalid && !rready && !bready);
|
||||
assert (!read_data_valid && !write_data_ready && !write_done);
|
||||
assert (!busy && !fault);
|
||||
end
|
||||
if (!rst_n_i) begin
|
||||
assert (!arvalid && !awvalid && !wvalid && !rready && !bready);
|
||||
assert (!read_data_valid && !write_data_ready && !write_done);
|
||||
assert (!busy && !fault && !pause_ack);
|
||||
end
|
||||
end
|
||||
|
||||
// Reachability covers: successful read and write bursts, the fault cases,
|
||||
// pause with a retained unaccepted VALID, and a mid-flight flush.
|
||||
always @(posedge clk_i) begin
|
||||
if (rst_n_i) begin
|
||||
cover (f_ar_accept && (arlen == 8'd15));
|
||||
cover (f_ar_accept && (arsize != 3'd3));
|
||||
cover (f_ar_accept && (arlen >= 8'd3));
|
||||
cover (f_rd_done_q && !fault);
|
||||
cover (f_aw_accept && (awlen >= 8'd1));
|
||||
cover (write_done);
|
||||
cover (fault && (fault_code == 4'd4));
|
||||
cover (fault && (fault_code == 4'd5));
|
||||
cover (fault && (fault_code == 4'd6));
|
||||
cover (pause_ack && arvalid && !arready);
|
||||
cover (pause_ack && awvalid && !awready);
|
||||
cover (clear && f_rd_outstanding_q);
|
||||
cover (clear && (f_wr_aw_q || f_wr_b_q));
|
||||
cover (f_rd_outstanding_q && (f_wr_aw_q || f_wr_b_q));
|
||||
cover (write_cmd_err);
|
||||
cover (read_data_valid && read_data_ready && read_last);
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
+12
-5
@@ -16,6 +16,8 @@ FORMAL_DMA_DEPTH ?= 24
|
||||
FORMAL_DMA_COVER_DEPTH ?= 32
|
||||
FORMAL_GA2D_DEPTH ?= 36
|
||||
FORMAL_GA2D_COVER_DEPTH ?= 199
|
||||
FORMAL_NPU_DMA_DEPTH ?= 16
|
||||
FORMAL_NPU_DMA_COVER_DEPTH ?= 32
|
||||
FORMAL_APU_DEPTH ?= 24
|
||||
FORMAL_APU_CODEC_DEPTH ?= 32
|
||||
FORMAL_APU_LOADER_DEPTH ?= 128
|
||||
@@ -35,20 +37,21 @@ FORMAL_OPIPSRAM_TIMEOUT ?= 300
|
||||
FORMAL_OPIPSRAM_BMC_TIMEOUT ?= 600
|
||||
FORMAL_DMA_TIMEOUT ?= 120
|
||||
FORMAL_GA2D_TIMEOUT ?= 7200
|
||||
FORMAL_NPU_DMA_TIMEOUT ?= 7200
|
||||
FORMAL_APU_TIMEOUT ?= 300
|
||||
FORMAL_GATEWAY_A_TIMEOUT ?= 120
|
||||
FORMAL_SDIO_TIMEOUT ?= 120
|
||||
FORMAL_APU_P5_LOADER_TARGETS := apu_loader_p5_success apu_loader_p5_bound apu_loader_p5_abort apu_loader_p5_resource_reset apu_loader_p5_memo_fallback
|
||||
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_APU_P5_LOADER_TARGETS)
|
||||
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 ga2d,$*),$(FORMAL_GA2D_TIMEOUT),$(if $(filter dma,$*),$(FORMAL_DMA_TIMEOUT),$(if $(filter apu apu_codec apu_kws 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_TARGET_TIMEOUT = $(if $(filter ga2d,$*),$(FORMAL_GA2D_TIMEOUT),$(if $(filter npu_dma,$*),$(FORMAL_NPU_DMA_TIMEOUT),$(if $(filter dma,$*),$(FORMAL_DMA_TIMEOUT),$(if $(filter apu apu_codec apu_kws 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_P5_LOADER_TARGETS),$*),$(FORMAL_APU_P5_LOADER_DEPTH),$(if $(filter $(FORMAL_APU_LOADER_TARGETS),$*),$(FORMAL_APU_LOADER_DEPTH),$(if $(filter apu_kws,$*),$(FORMAL_APU_KWS_DEPTH),$(if $(filter apu_sequencer,$*),$(FORMAL_APU_SEQUENCER_DEPTH),$(if $(filter apu_codec,$*),$(FORMAL_APU_CODEC_DEPTH),$(FORMAL_APU_DEPTH))))))
|
||||
FORMAL_APU_TARGET_COVER_DEPTH = $(if $(filter $(FORMAL_APU_P5_LOADER_TARGETS),$*),$(FORMAL_APU_P5_LOADER_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,$(if $(filter $(FORMAL_APU_PRIMITIVE_TARGETS),$*),apu_primitives,$*))
|
||||
FORMAL_PROPERTIES_SOURCE = $(if $(filter ga2d,$*),$(RTL_PATH)/formal/ga2d_formal.sv,$(RTL_PATH)/formal/$(FORMAL_TARGET_TOP)_formal_props.sv)
|
||||
FORMAL_PROPERTIES_DEFINE = $(if $(filter ga2d,$*),--properties-define GA2D_FORMAL_PROPERTIES,)
|
||||
FORMAL_COMPACT_COVER_TARGETS := i2c opipsram dma ga2d $(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 ga2d apu apu_codec apu_kws $(FORMAL_APU_PRIMITIVE_TARGETS) $(FORMAL_APU_LOADER_TARGETS) apu_sequencer gateway_a sdio
|
||||
FORMAL_TARGETS := bus rib_adapter rib2apb sysctrl pll_rcu gpio ws2812 uart i2c timer clint dvp i2s psram onchip_ram opipsram dma ga2d apu apu_codec apu_kws $(FORMAL_APU_PRIMITIVE_TARGETS) $(FORMAL_APU_LOADER_TARGETS) apu_sequencer gateway_a sdio npu_dma
|
||||
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
|
||||
@@ -104,6 +107,8 @@ FORMAL_SOURCE_FILES := $(RTL_PATH)/formal/bus_formal.sv \
|
||||
$(RTL_PATH)/formal/gateway_a_formal_props.sv \
|
||||
$(RTL_PATH)/formal/sdio_formal.sv \
|
||||
$(RTL_PATH)/formal/sdio_formal_props.sv \
|
||||
$(RTL_PATH)/formal/npu_dma_formal.sv \
|
||||
$(RTL_PATH)/formal/npu_dma_formal_props.sv \
|
||||
$(RTL_PATH)/top/rib_bus.sv \
|
||||
$(RTL_PATH)/top/rib_error_slave.sv \
|
||||
$(RTL_PATH)/top/rib_if.sv \
|
||||
@@ -234,14 +239,14 @@ $(FORMAL_DIR)/%/prove.sby: $(FORMAL_DIR)/%/design.v \
|
||||
$(FORMAL_SBY_GENERATOR)
|
||||
python3 $(FORMAL_SBY_GENERATOR) --top $(FORMAL_TARGET_TOP)_formal --input $< \
|
||||
--properties $(FORMAL_PROPERTIES_SOURCE) $(FORMAL_PROPERTIES_DEFINE) --solver $(FORMAL_SOLVER) \
|
||||
--mode $(if $(filter ga2d,$*),bmc,$(if $(filter dma apu apu_codec apu_kws apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS) gateway_a sdio onchip_ram,$*),bmc,prove)) --depth $(if $(filter ga2d,$*),$(FORMAL_GA2D_DEPTH),$(if $(filter pll_rcu,$*),$(FORMAL_PLL_RCU_DEPTH),$(if $(filter dma,$*),$(FORMAL_DMA_DEPTH),$(if $(filter apu apu_codec apu_kws 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 $@
|
||||
--mode $(if $(filter ga2d,$*),bmc,$(if $(filter dma npu_dma apu apu_codec apu_kws apu_primitives apu_sequencer $(FORMAL_APU_LOADER_TARGETS) $(FORMAL_APU_PRIMITIVE_TARGETS) gateway_a sdio onchip_ram,$*),bmc,prove)) --depth $(if $(filter ga2d,$*),$(FORMAL_GA2D_DEPTH),$(if $(filter pll_rcu,$*),$(FORMAL_PLL_RCU_DEPTH),$(if $(filter npu_dma,$*),$(FORMAL_NPU_DMA_DEPTH),$(if $(filter dma,$*),$(FORMAL_DMA_DEPTH),$(if $(filter apu apu_codec apu_kws 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 $(FORMAL_PROPERTIES_SOURCE) $(FORMAL_PROPERTIES_DEFINE) --solver $(FORMAL_SOLVER) \
|
||||
--mode cover --depth $(if $(filter ga2d,$*),$(FORMAL_GA2D_COVER_DEPTH),$(if $(filter pll_rcu,$*),$(FORMAL_PLL_RCU_DEPTH),$(if $(filter dma,$*),$(FORMAL_DMA_COVER_DEPTH),$(if $(filter apu apu_codec apu_kws 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))))))))))) \
|
||||
--mode cover --depth $(if $(filter ga2d,$*),$(FORMAL_GA2D_COVER_DEPTH),$(if $(filter pll_rcu,$*),$(FORMAL_PLL_RCU_DEPTH),$(if $(filter npu_dma,$*),$(FORMAL_NPU_DMA_COVER_DEPTH),$(if $(filter dma,$*),$(FORMAL_DMA_COVER_DEPTH),$(if $(filter apu apu_codec apu_kws 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_APU_P5_LOADER_TARGETS),$*),--skip $(FORMAL_APU_P5_LOADER_COVER_SKIP)) \
|
||||
$(if $(filter $(FORMAL_COMPACT_COVER_TARGETS),$*),--no-vcd) --output $@
|
||||
@if [ "$*" = opipsram ]; then sed -i '/^async2sync/i clk2fflogic' $@; fi
|
||||
@@ -355,10 +360,12 @@ formal-gateway-a: $(FORMAL_DIR)/gateway_a/.stamp | manifest
|
||||
|
||||
formal-sdio: $(FORMAL_DIR)/sdio/.stamp | manifest
|
||||
|
||||
formal-npu_dma: $(FORMAL_DIR)/npu_dma/.stamp | manifest
|
||||
|
||||
formal-doctor:
|
||||
$(MAKE) FORMAL=YES SIMU=IVERILOG SYNTH=YOSYS STA=NONE doctor
|
||||
|
||||
formal-clean:
|
||||
python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH) --path $(FORMAL_DIR)
|
||||
|
||||
.PHONY: formal formal-bus formal-rib-adapter formal-rib2apb formal-sysctrl formal-pll-rcu formal-gpio formal-ws2812 formal-uart formal-i2c formal-timer formal-clint formal-dvp formal-i2s formal-psram formal-onchip-ram formal-opipsram formal-dma formal-ga2d formal-apu formal-apu-kws formal-apu-loader formal-apu-loader-p5 formal-apu-sequencer formal-gateway-a formal-sdio formal-doctor formal-clean
|
||||
.PHONY: formal formal-bus formal-rib-adapter formal-rib2apb formal-sysctrl formal-pll-rcu formal-gpio formal-ws2812 formal-uart formal-i2c formal-timer formal-clint formal-dvp formal-i2s formal-psram formal-onchip-ram formal-opipsram formal-dma formal-ga2d formal-apu formal-apu-kws formal-apu-loader formal-apu-loader-p5 formal-apu-sequencer formal-gateway-a formal-sdio formal-npu_dma formal-doctor formal-clean
|
||||
@@ -201,12 +201,15 @@
|
||||
"npu_control_cdc.sv",
|
||||
"npu_core.sv",
|
||||
"npu_define.svh",
|
||||
"npu_dma.sv",
|
||||
"npu_job_decoder.sv",
|
||||
"npu_local_sram.sv",
|
||||
"npu_mac_array.sv",
|
||||
"npu_patch_packer.sv",
|
||||
"npu_pkg.sv",
|
||||
"npu_reg.sv",
|
||||
"npu_requantizer.sv",
|
||||
"npu_scheduler.sv",
|
||||
"npu_vector.sv"
|
||||
],
|
||||
"rtl/ip/peripheral": [
|
||||
|
||||
+191
-10
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the isolated NPU-P1 IHP130 synthesis and 72 MHz STA flow."""
|
||||
"""Run the isolated NPU IHP130 synthesis and 72 MHz STA flow (P1/P3 modules)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -31,6 +31,11 @@ TOPS = (
|
||||
"npu_accumulator",
|
||||
"npu_vector",
|
||||
"npu_requantizer",
|
||||
"npu_dma",
|
||||
"npu_job_decoder",
|
||||
"npu_scheduler",
|
||||
"npu_core",
|
||||
"apb4_npu",
|
||||
)
|
||||
NPU_RTL = (
|
||||
"npu_pkg.sv",
|
||||
@@ -40,7 +45,159 @@ NPU_RTL = (
|
||||
"npu_accumulator.sv",
|
||||
"npu_vector.sv",
|
||||
"npu_requantizer.sv",
|
||||
"npu_dma.sv",
|
||||
"npu_job_decoder.sv",
|
||||
"npu_scheduler.sv",
|
||||
"npu_reg.sv",
|
||||
"npu_control_cdc.sv",
|
||||
"npu_core.sv",
|
||||
"apb4_npu.sv",
|
||||
)
|
||||
# Tops whose only non-logic port is an AXI4 master interface get a generated
|
||||
# synthesis wrapper in the build tree (never tracked): the interface is
|
||||
# instantiated inside the wrapper so the DUT has no unconnected ports.
|
||||
INTERFACE_WRAPPED = {
|
||||
"npu_dma": """
|
||||
module npu_dma_synth_top (
|
||||
input logic clk_hp_i,
|
||||
input logic rst_hp_n_i,
|
||||
input logic clear_i,
|
||||
input logic block_new_i,
|
||||
output logic pause_ack_o,
|
||||
input logic read_req_valid_i,
|
||||
output logic read_req_ready_o,
|
||||
input logic [31:0] read_addr_i,
|
||||
input logic [31:0] read_bytes_i,
|
||||
output logic read_data_valid_o,
|
||||
input logic read_data_ready_i,
|
||||
output logic [63:0] read_data_o,
|
||||
output logic [ 7:0] read_keep_o,
|
||||
output logic read_last_o,
|
||||
input logic write_req_valid_i,
|
||||
output logic write_req_ready_o,
|
||||
input logic [31:0] write_addr_i,
|
||||
input logic [31:0] write_bytes_i,
|
||||
input logic write_data_valid_i,
|
||||
output logic write_data_ready_o,
|
||||
input logic [63:0] write_data_i,
|
||||
input logic [ 7:0] write_keep_i,
|
||||
input logic write_last_i,
|
||||
output logic write_done_o,
|
||||
output logic busy_o,
|
||||
output logic read_busy_o,
|
||||
output logic write_busy_o,
|
||||
output logic [63:0] read_bytes_o,
|
||||
output logic [63:0] write_bytes_o,
|
||||
output logic [63:0] stall_cycles_o,
|
||||
output logic fault_o,
|
||||
output logic [ 3:0] fault_code_o,
|
||||
output logic [31:0] fault_addr_o,
|
||||
output logic [ 1:0] fault_resp_o,
|
||||
output logic read_cmd_err_o,
|
||||
output logic write_cmd_err_o
|
||||
);
|
||||
axi4_if #(
|
||||
.ADDR_WIDTH(32),
|
||||
.DATA_WIDTH(64),
|
||||
.ID_WIDTH (3),
|
||||
.USER_WIDTH(1)
|
||||
) u_axi4 (
|
||||
.aclk (clk_hp_i),
|
||||
.aresetn(rst_hp_n_i)
|
||||
);
|
||||
npu_dma u_dut (
|
||||
.clk_hp_i (clk_hp_i),
|
||||
.rst_hp_n_i (rst_hp_n_i),
|
||||
.clear_i (clear_i),
|
||||
.block_new_i (block_new_i),
|
||||
.pause_ack_o (pause_ack_o),
|
||||
.read_req_valid_i (read_req_valid_i),
|
||||
.read_req_ready_o (read_req_ready_o),
|
||||
.read_addr_i (read_addr_i),
|
||||
.read_bytes_i (read_bytes_i),
|
||||
.read_data_valid_o (read_data_valid_o),
|
||||
.read_data_ready_i (read_data_ready_i),
|
||||
.read_data_o (read_data_o),
|
||||
.read_keep_o (read_keep_o),
|
||||
.read_last_o (read_last_o),
|
||||
.write_req_valid_i (write_req_valid_i),
|
||||
.write_req_ready_o (write_req_ready_o),
|
||||
.write_addr_i (write_addr_i),
|
||||
.write_bytes_i (write_bytes_i),
|
||||
.write_data_valid_i(write_data_valid_i),
|
||||
.write_data_ready_o(write_data_ready_o),
|
||||
.write_data_i (write_data_i),
|
||||
.write_keep_i (write_keep_i),
|
||||
.write_last_i (write_last_i),
|
||||
.write_done_o (write_done_o),
|
||||
.busy_o (busy_o),
|
||||
.read_busy_o (read_busy_o),
|
||||
.write_busy_o (write_busy_o),
|
||||
.read_bytes_o (read_bytes_o),
|
||||
.write_bytes_o (write_bytes_o),
|
||||
.stall_cycles_o (stall_cycles_o),
|
||||
.fault_o (fault_o),
|
||||
.fault_code_o (fault_code_o),
|
||||
.fault_addr_o (fault_addr_o),
|
||||
.fault_resp_o (fault_resp_o),
|
||||
.read_cmd_err_o (read_cmd_err_o),
|
||||
.write_cmd_err_o (write_cmd_err_o),
|
||||
.axi4 (u_axi4)
|
||||
);
|
||||
endmodule
|
||||
""",
|
||||
"apb4_npu": """
|
||||
module apb4_npu_synth_top (
|
||||
input logic clk_i,
|
||||
input logic rst_n_i,
|
||||
input logic clk_hp_i,
|
||||
input logic rst_hp_n_i,
|
||||
input logic [1:0] resource_owner_i,
|
||||
input logic resource_owner_lock_i,
|
||||
input logic resource_quiesce_i,
|
||||
input logic resource_reset_i,
|
||||
output logic idle_o,
|
||||
output logic block_ack_o,
|
||||
output logic irq_o,
|
||||
input logic hp_block_new_i,
|
||||
output logic hp_pause_ack_o,
|
||||
input logic hp_flush_i,
|
||||
output logic hp_flush_busy_o,
|
||||
output logic hp_idle_o
|
||||
);
|
||||
apb4_if u_apb4 (.pclk(clk_i), .presetn(rst_n_i));
|
||||
axi4_if #(
|
||||
.ADDR_WIDTH(32),
|
||||
.DATA_WIDTH(64),
|
||||
.ID_WIDTH (3),
|
||||
.USER_WIDTH(1)
|
||||
) u_npu_axi4 (
|
||||
.aclk (clk_hp_i),
|
||||
.aresetn(rst_hp_n_i)
|
||||
);
|
||||
apb4_npu u_dut (
|
||||
.clk_i (clk_i),
|
||||
.rst_n_i (rst_n_i),
|
||||
.clk_hp_i (clk_hp_i),
|
||||
.rst_hp_n_i (rst_hp_n_i),
|
||||
.resource_owner_i (resource_owner_i),
|
||||
.resource_owner_lock_i(resource_owner_lock_i),
|
||||
.resource_quiesce_i (resource_quiesce_i),
|
||||
.resource_reset_i (resource_reset_i),
|
||||
.apb4 (u_apb4),
|
||||
.idle_o (idle_o),
|
||||
.block_ack_o (block_ack_o),
|
||||
.irq_o (irq_o),
|
||||
.hp_block_new_i (hp_block_new_i),
|
||||
.hp_pause_ack_o (hp_pause_ack_o),
|
||||
.hp_flush_i (hp_flush_i),
|
||||
.hp_flush_busy_o (hp_flush_busy_o),
|
||||
.hp_idle_o (hp_idle_o),
|
||||
.npu_axi4 (u_npu_axi4)
|
||||
);
|
||||
endmodule
|
||||
""",
|
||||
}
|
||||
SRAM_MACRO = "RM_IHPSG13_1P_1024x32_c2_bm_bist"
|
||||
EXPECTED_SRAM_MACROS = 16
|
||||
TARGET_PERIOD_PS = 13889
|
||||
@@ -60,6 +217,10 @@ def _run_flow(tool: str, log: Path, result: Path, env: dict[str, str], command:
|
||||
raise RuntimeError(f"{tool} flow failed for {env.get('TOP_DESIGN', env.get('OPENSTA_TOP'))}: {log}")
|
||||
|
||||
|
||||
def _synth_top(top: str) -> str:
|
||||
return f"{top}_synth_top" if top in INTERFACE_WRAPPED else top
|
||||
|
||||
|
||||
def _write_filelist(path: Path) -> None:
|
||||
lines = [
|
||||
"+define+PDK_IHP130",
|
||||
@@ -72,16 +233,36 @@ def _write_filelist(path: Path) -> None:
|
||||
for name in NPU_RTL:
|
||||
source = ROOT / "rtl/ip/multimedia" / name
|
||||
if not source.is_file():
|
||||
raise RuntimeError(f"missing NPU-P1 RTL source: {source}")
|
||||
raise RuntimeError(f"missing NPU RTL source: {source}")
|
||||
lines.append(str(source))
|
||||
common_rtl = ROOT / "rtl/managed/clusterip/common/rtl"
|
||||
lines.append(str(ROOT / "rtl/tech/tc_sram.sv"))
|
||||
lines.append(str(ROOT / "rtl/managed/clusterip/common/rtl/utils/register.sv"))
|
||||
for extra in (
|
||||
"interface/apb4_if.sv",
|
||||
"interface/axi4_if.sv",
|
||||
"utils/register.sv",
|
||||
"utils/xchecker.sv",
|
||||
"utils/fifo.sv",
|
||||
"clkrst/rst_sync.sv",
|
||||
"cdc/cdc_sync.sv",
|
||||
"cdc/cdc_rst_ctrlr.sv",
|
||||
"cdc/async_reqack.sv",
|
||||
):
|
||||
source = common_rtl / extra
|
||||
if not source.is_file():
|
||||
raise RuntimeError(f"missing Common source: {source}")
|
||||
lines.append(str(source))
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
for top, wrapper in INTERFACE_WRAPPED.items():
|
||||
wrapper_path = path.parent / f"{_synth_top(top)}.sv"
|
||||
wrapper_path.write_text(wrapper, encoding="utf-8")
|
||||
lines.append(str(wrapper_path))
|
||||
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def _synth(top: str, build_root: Path, filelist: Path) -> dict[str, object]:
|
||||
base = build_root / "syn/yosys-npu" / top
|
||||
synth_top = _synth_top(top)
|
||||
for sub in ("out", "tmp", "rpt"):
|
||||
(base / sub).mkdir(parents=True, exist_ok=True)
|
||||
env = {
|
||||
@@ -92,17 +273,17 @@ def _synth(top: str, build_root: Path, filelist: Path) -> dict[str, object]:
|
||||
"SRAM_SIZE_KIB": "32",
|
||||
"YOSYS_TARGET_PERIOD_PS": str(TARGET_PERIOD_PS),
|
||||
"SV_FLIST": str(filelist),
|
||||
"TOP_DESIGN": top,
|
||||
"PROJ_NAME": top,
|
||||
"TOP_DESIGN": synth_top,
|
||||
"PROJ_NAME": synth_top,
|
||||
"BUILD": str(base / "out"),
|
||||
"WORK": str(base / "tmp"),
|
||||
"REPORTS": str(base / "rpt"),
|
||||
"NETLIST": str(base / "out" / f"{top}_yosys.v"),
|
||||
"CONFIG": str(base / "out" / f"{top}_yosys.config"),
|
||||
"NETLIST": str(base / "out" / f"{synth_top}_yosys.v"),
|
||||
"CONFIG": str(base / "out" / f"{synth_top}_yosys.config"),
|
||||
}
|
||||
log = build_root / "syn/yosys-npu" / f"{top}.log"
|
||||
_run_flow("yosys", log, base / "result-synth.json", env, ["yosys", "-c", str(SYNTH_TCL)])
|
||||
area = json.loads((base / "rpt" / f"{top}_area.json").read_text(encoding="utf-8"))
|
||||
area = json.loads((base / "rpt" / f"{synth_top}_area.json").read_text(encoding="utf-8"))
|
||||
design = area["design"]
|
||||
macro_count = design.get("num_cells_by_type", {}).get(SRAM_MACRO, 0)
|
||||
latch_cells = {
|
||||
@@ -142,7 +323,7 @@ def _sta(top: str, build_root: Path, netlist: Path) -> dict[str, object]:
|
||||
"OPENSTA_SDC": str(STA_SDC),
|
||||
"OPENSTA_REPORT": str(base / f"{top}_checks.rpt"),
|
||||
"OPENSTA_METRICS": str(base / f"{top}_timing_metrics.rpt"),
|
||||
"OPENSTA_TOP": top,
|
||||
"OPENSTA_TOP": _synth_top(top),
|
||||
}
|
||||
log = base / f"{top}.log"
|
||||
_run_flow("opensta", log, base / "result-sta.json", env, ["sta", "-no_init", "-exit", str(STA_TCL)])
|
||||
@@ -216,7 +397,7 @@ def main() -> int:
|
||||
failures.append(
|
||||
f"{top} synthesis shows latches: {synth['latch_log_lines']} {synth['latch_cells']}"
|
||||
)
|
||||
netlist = build_root / "syn/yosys-npu" / top / "out" / f"{top}_yosys.v"
|
||||
netlist = build_root / "syn/yosys-npu" / top / "out" / f"{_synth_top(top)}_yosys.v"
|
||||
if args.sta:
|
||||
if not netlist.is_file():
|
||||
raise RuntimeError(f"missing netlist for {top}: {netlist}; run --synth first")
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,669 @@
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
// NPU-P3 npu_job_decoder unit test: the decoder drives a production npu_dma
|
||||
// read channel against a byte-memory BFM with optional per-beat SLVERR/DECERR
|
||||
// and malformed-RID injection. Descriptor images and expected verdicts come
|
||||
// from plusargs generated by tests/test_npu_rtl_p3.py with
|
||||
// scripts/npu_descriptors.py as the golden validator. Covered: valid records
|
||||
// of every opcode (emitted record fields compared word-for-word against the
|
||||
// image), every validator rejection class with first-fault code/word/index,
|
||||
// multi-descriptor in-order traversal, fetch SLVERR/DECERR and protocol
|
||||
// faults with forwarded address/response, descriptor-region truncation,
|
||||
// pause retention mid-fetch, and no reuse of the launch slot after a terminal
|
||||
// fault without a fresh job_start.
|
||||
`include "npu_define.svh"
|
||||
|
||||
module npu_job_decoder_tb;
|
||||
localparam logic [31:0] SramBase = 32'h3000_0000;
|
||||
localparam int unsigned MemoryBytes = 262144;
|
||||
localparam int unsigned PhaseTimeout = 20000;
|
||||
|
||||
// plusarg-driven case configuration
|
||||
string s_img_path;
|
||||
logic [31:0] s_job_base;
|
||||
logic [31:0] s_job_count;
|
||||
logic [31:0] s_mode; // 0 accept, 1 validation fault, 2 fetch fault, 3 pause fetch
|
||||
logic [31:0] s_exp_fcode;
|
||||
logic [31:0] s_exp_findex;
|
||||
logic s_exp_wordless;
|
||||
logic [31:0] s_exp_fword;
|
||||
logic [31:0] s_rderr_addr;
|
||||
logic [31:0] s_rderr_code;
|
||||
logic [31:0] s_rderr_badid;
|
||||
|
||||
logic clk_hp_i = 1'b0;
|
||||
logic rst_hp_n_i = 1'b0;
|
||||
logic clear_i = 1'b0;
|
||||
logic block_new_i = 1'b0;
|
||||
logic dec_pause_ok;
|
||||
logic job_start_i = 1'b0;
|
||||
logic [31:0] job_base_i = '0;
|
||||
logic [15:0] job_count_i = '0;
|
||||
logic s_dec_read_req_valid;
|
||||
logic s_dec_read_req_ready;
|
||||
logic [31:0] s_dec_read_addr;
|
||||
logic [31:0] s_dec_read_bytes;
|
||||
logic s_dec_read_data_valid;
|
||||
logic s_dec_read_data_ready;
|
||||
logic [63:0] s_dec_read_data;
|
||||
logic [ 7:0] s_dec_read_keep;
|
||||
logic s_dec_read_last;
|
||||
logic rec_valid_o;
|
||||
logic rec_ready_i = 1'b0;
|
||||
logic [ 3:0] rec_opcode_o;
|
||||
logic [12:0] rec_h_o, rec_w_o, rec_cin_o, rec_cout_o, rec_oh_o, rec_ow_o;
|
||||
logic [31:0] rec_input0_base_o, rec_output_base_o, rec_weight_base_o, rec_param_base_o;
|
||||
logic [31:0] rec_input0_row_bytes_o, rec_output_row_bytes_o;
|
||||
logic [7:0] rec_kh_o, rec_kw_o, rec_sh_o, rec_sw_o;
|
||||
logic [7:0] rec_pad_top_o, rec_pad_left_o, rec_input0_zero_o;
|
||||
logic [31:0] rec_weight_bytes_o, rec_param_bytes_o;
|
||||
logic [15:0] rec_desc_index_o;
|
||||
logic retired_i = 1'b0;
|
||||
logic all_done_o;
|
||||
logic busy_o;
|
||||
logic [15:0] cur_desc_index_o;
|
||||
logic progress_o;
|
||||
logic fault_req_o;
|
||||
logic [ 3:0] fault_code_o;
|
||||
logic [31:0] fault_addr_o;
|
||||
logic [31:0] fault_info_o;
|
||||
logic [31:0] fault_desc_o;
|
||||
|
||||
logic [ 7:0] s_memory [0:MemoryBytes-1];
|
||||
// read channel BFM state
|
||||
logic s_rvalid_q;
|
||||
logic [ 2:0] s_rid_q;
|
||||
logic [ 1:0] s_rresp_q;
|
||||
logic [31:0] s_raddr_q;
|
||||
logic [63:0] s_rdata_q;
|
||||
logic [ 2:0] s_rsize_q;
|
||||
logic [ 8:0] s_rbeats_q;
|
||||
// counters and bookkeeping
|
||||
int unsigned s_ar_count;
|
||||
int unsigned s_rd_outstanding_q;
|
||||
int unsigned s_rec_count;
|
||||
logic s_saw_all_done_q;
|
||||
string s_phase;
|
||||
int unsigned s_phase_cycles_q;
|
||||
logic s_progress;
|
||||
logic s_pause_armed_i;
|
||||
logic s_pause_done_q;
|
||||
|
||||
axi4_if #(
|
||||
.ADDR_WIDTH(32),
|
||||
.DATA_WIDTH(64),
|
||||
.ID_WIDTH (3),
|
||||
.USER_WIDTH(1)
|
||||
) npu_axi4 (
|
||||
.aclk (clk_hp_i),
|
||||
.aresetn(rst_hp_n_i)
|
||||
);
|
||||
|
||||
npu_dma u_dma (
|
||||
.clk_hp_i (clk_hp_i),
|
||||
.rst_hp_n_i (rst_hp_n_i),
|
||||
.clear_i (clear_i),
|
||||
.block_new_i (block_new_i),
|
||||
.pause_ack_o (),
|
||||
.read_req_valid_i (s_dec_read_req_valid),
|
||||
.read_req_ready_o (s_dec_read_req_ready),
|
||||
.read_addr_i (s_dec_read_addr),
|
||||
.read_bytes_i (s_dec_read_bytes),
|
||||
.read_data_valid_o (s_dec_read_data_valid),
|
||||
.read_data_ready_i (s_dec_read_data_ready),
|
||||
.read_data_o (s_dec_read_data),
|
||||
.read_keep_o (s_dec_read_keep),
|
||||
.read_last_o (s_dec_read_last),
|
||||
.write_req_valid_i (1'b0),
|
||||
.write_req_ready_o (),
|
||||
.write_addr_i (32'd0),
|
||||
.write_bytes_i (32'd0),
|
||||
.write_data_valid_i(1'b0),
|
||||
.write_data_ready_o(),
|
||||
.write_data_i (64'd0),
|
||||
.write_keep_i (8'd0),
|
||||
.write_last_i (1'b0),
|
||||
.write_done_o (),
|
||||
.busy_o (),
|
||||
.read_busy_o (s_dma_read_busy),
|
||||
.write_busy_o (),
|
||||
.read_bytes_o (),
|
||||
.write_bytes_o (),
|
||||
.stall_cycles_o (),
|
||||
.fault_o (s_dma_fault),
|
||||
.fault_code_o (s_dma_fault_code),
|
||||
.fault_addr_o (s_dma_fault_addr),
|
||||
.fault_resp_o (s_dma_fault_resp),
|
||||
.read_cmd_err_o (),
|
||||
.write_cmd_err_o (),
|
||||
.axi4 (npu_axi4)
|
||||
);
|
||||
logic s_dma_read_busy;
|
||||
logic s_dma_fault;
|
||||
logic [ 3:0] s_dma_fault_code;
|
||||
logic [31:0] s_dma_fault_addr;
|
||||
logic [ 1:0] s_dma_fault_resp;
|
||||
|
||||
npu_job_decoder u_dut (
|
||||
.clk_hp_i (clk_hp_i),
|
||||
.rst_hp_n_i (rst_hp_n_i),
|
||||
.clear_i (clear_i),
|
||||
.block_new_i (block_new_i),
|
||||
.pause_ok_o (dec_pause_ok),
|
||||
.job_start_i (job_start_i),
|
||||
.job_base_i (job_base_i),
|
||||
.job_count_i (job_count_i),
|
||||
.read_req_valid_o (s_dec_read_req_valid),
|
||||
.read_req_ready_i (s_dec_read_req_ready),
|
||||
.read_addr_o (s_dec_read_addr),
|
||||
.read_bytes_o (s_dec_read_bytes),
|
||||
.read_data_valid_i (s_dec_read_data_valid),
|
||||
.read_data_ready_o (s_dec_read_data_ready),
|
||||
.read_data_i (s_dec_read_data),
|
||||
.read_last_i (s_dec_read_last),
|
||||
.dma_read_busy_i (s_dma_read_busy),
|
||||
.dma_fault_i (s_dma_fault),
|
||||
.dma_fault_code_i (s_dma_fault_code),
|
||||
.dma_fault_addr_i (s_dma_fault_addr),
|
||||
.dma_fault_resp_i (s_dma_fault_resp),
|
||||
.rec_valid_o (rec_valid_o),
|
||||
.rec_ready_i (rec_ready_i),
|
||||
.rec_opcode_o (rec_opcode_o),
|
||||
.rec_h_o (rec_h_o),
|
||||
.rec_w_o (rec_w_o),
|
||||
.rec_cin_o (rec_cin_o),
|
||||
.rec_cout_o (rec_cout_o),
|
||||
.rec_oh_o (rec_oh_o),
|
||||
.rec_ow_o (rec_ow_o),
|
||||
.rec_input0_base_o (rec_input0_base_o),
|
||||
.rec_output_base_o (rec_output_base_o),
|
||||
.rec_weight_base_o (rec_weight_base_o),
|
||||
.rec_param_base_o (rec_param_base_o),
|
||||
.rec_input0_row_bytes_o(rec_input0_row_bytes_o),
|
||||
.rec_output_row_bytes_o(rec_output_row_bytes_o),
|
||||
.rec_kh_o (rec_kh_o),
|
||||
.rec_kw_o (rec_kw_o),
|
||||
.rec_sh_o (rec_sh_o),
|
||||
.rec_sw_o (rec_sw_o),
|
||||
.rec_pad_top_o (rec_pad_top_o),
|
||||
.rec_pad_left_o (rec_pad_left_o),
|
||||
.rec_input0_zero_o (rec_input0_zero_o),
|
||||
.rec_weight_bytes_o (rec_weight_bytes_o),
|
||||
.rec_param_bytes_o (rec_param_bytes_o),
|
||||
.rec_desc_index_o (rec_desc_index_o),
|
||||
.retired_i (retired_i),
|
||||
.all_done_o (all_done_o),
|
||||
.busy_o (busy_o),
|
||||
.cur_desc_index_o (cur_desc_index_o),
|
||||
.progress_o (progress_o),
|
||||
.fault_req_o (fault_req_o),
|
||||
.fault_code_o (fault_code_o),
|
||||
.fault_addr_o (fault_addr_o),
|
||||
.fault_info_o (fault_info_o),
|
||||
.fault_desc_o (fault_desc_o)
|
||||
);
|
||||
|
||||
always #5 clk_hp_i = ~clk_hp_i;
|
||||
|
||||
function automatic logic [7:0] memory_byte(input logic [31:0] address_i);
|
||||
if ((address_i >= SramBase) && (address_i < (SramBase + MemoryBytes))) begin
|
||||
return s_memory[address_i-SramBase];
|
||||
end
|
||||
return 8'd0;
|
||||
endfunction
|
||||
|
||||
function automatic logic [63:0] memory_word(input logic [31:0] address_i);
|
||||
logic [63:0] data;
|
||||
logic [31:0] aligned_address;
|
||||
begin
|
||||
data = '0;
|
||||
aligned_address = {address_i[31:3], 3'b000};
|
||||
for (int unsigned lane = 0; lane < 8; lane++) begin
|
||||
data[lane*8+:8] = memory_byte(aligned_address + lane);
|
||||
end
|
||||
return data;
|
||||
end
|
||||
endfunction
|
||||
|
||||
function automatic logic [31:0] image_word(input int unsigned desc_i, input int unsigned word_i);
|
||||
logic [31:0] value;
|
||||
begin
|
||||
value = '0;
|
||||
for (int unsigned lane = 0; lane < 4; lane++) begin
|
||||
value[lane*8+:8] =
|
||||
memory_byte(s_job_base + 32'(desc_i) * 32'd128 + 32'(word_i) * 32'd4 + 32'(lane));
|
||||
end
|
||||
return value;
|
||||
end
|
||||
endfunction
|
||||
|
||||
function automatic logic [1:0] bfm_read_resp(input logic [31:0] beat_addr_i);
|
||||
if ((s_rderr_addr != 32'hffff_ffff) && (beat_addr_i == s_rderr_addr)) begin
|
||||
return s_rderr_code[1:0];
|
||||
end
|
||||
return 2'b00;
|
||||
endfunction
|
||||
|
||||
// ------------------------- AXI read slave BFM -------------------------
|
||||
assign npu_axi4.arready = !s_hold_ar_i && !s_rvalid_q;
|
||||
assign npu_axi4.rid = s_rid_q;
|
||||
assign npu_axi4.rdata = s_rdata_q;
|
||||
assign npu_axi4.rresp = s_rresp_q;
|
||||
assign npu_axi4.rlast = (s_rbeats_q == 9'd1);
|
||||
assign npu_axi4.ruser = '0;
|
||||
assign npu_axi4.rvalid = !s_hold_r_i && s_rvalid_q;
|
||||
// the decoder never writes
|
||||
assign npu_axi4.awready = 1'b0;
|
||||
assign npu_axi4.wready = 1'b0;
|
||||
assign npu_axi4.bid = '0;
|
||||
assign npu_axi4.bresp = 2'b00;
|
||||
assign npu_axi4.buser = '0;
|
||||
assign npu_axi4.bvalid = 1'b0;
|
||||
|
||||
logic s_hold_ar_i;
|
||||
logic s_hold_r_i;
|
||||
|
||||
always_ff @(posedge clk_hp_i or negedge rst_hp_n_i) begin
|
||||
if (!rst_hp_n_i) begin
|
||||
s_rvalid_q <= 1'b0;
|
||||
s_rid_q <= '0;
|
||||
s_rresp_q <= 2'd0;
|
||||
s_raddr_q <= '0;
|
||||
s_rdata_q <= '0;
|
||||
s_rsize_q <= '0;
|
||||
s_rbeats_q <= '0;
|
||||
s_ar_count <= 0;
|
||||
s_rd_outstanding_q <= 0;
|
||||
end else if (clear_i) begin
|
||||
s_rvalid_q <= 1'b0;
|
||||
s_rbeats_q <= '0;
|
||||
s_rd_outstanding_q <= 0;
|
||||
end else begin
|
||||
if (npu_axi4.arvalid && npu_axi4.arready) begin
|
||||
if (s_rd_outstanding_q != 0) begin
|
||||
$fatal(1, "NPU decoder TB: second outstanding read");
|
||||
end
|
||||
if ((npu_axi4.araddr & ((32'd1 << npu_axi4.arsize) - 32'd1)) != 32'd0) begin
|
||||
$fatal(1, "NPU decoder TB: unaligned read transfer");
|
||||
end
|
||||
if ((npu_axi4.arsize != 3'd3) && (npu_axi4.arlen != 8'd0)) begin
|
||||
$fatal(1, "NPU decoder TB: multi-beat narrow read burst");
|
||||
end
|
||||
if ({1'b0, npu_axi4.araddr[11:0]} + (({5'd0, npu_axi4.arlen} + 13'd1) << npu_axi4.arsize)
|
||||
> 13'd4096) begin
|
||||
$fatal(1, "NPU decoder TB: read burst crossed a 4 KiB boundary");
|
||||
end
|
||||
if ((npu_axi4.araddr < s_job_base) ||
|
||||
(npu_axi4.araddr >= (s_job_base + {16'd0, s_job_count[15:0], 7'd0}))) begin
|
||||
$fatal(1, "NPU decoder TB: read escaped the descriptor region");
|
||||
end
|
||||
s_rvalid_q <= 1'b1;
|
||||
s_rid_q <= ((s_rderr_badid != 32'd0) &&
|
||||
(npu_axi4.araddr == {s_rderr_addr[31:3], 3'b000})) ? 3'd1 : 3'd0;
|
||||
s_rresp_q <= bfm_read_resp(npu_axi4.araddr);
|
||||
s_raddr_q <= npu_axi4.araddr;
|
||||
s_rdata_q <= memory_word(npu_axi4.araddr);
|
||||
s_rsize_q <= npu_axi4.arsize;
|
||||
s_rbeats_q <= {1'b0, npu_axi4.arlen} + 1'b1;
|
||||
s_ar_count <= s_ar_count + 1;
|
||||
s_rd_outstanding_q <= s_rd_outstanding_q + 1;
|
||||
end else if (s_rvalid_q && npu_axi4.rvalid && npu_axi4.rready) begin
|
||||
if (s_rbeats_q == 9'd1) begin
|
||||
s_rvalid_q <= 1'b0;
|
||||
s_rbeats_q <= '0;
|
||||
s_rd_outstanding_q <= s_rd_outstanding_q - 1;
|
||||
end else begin
|
||||
s_raddr_q <= s_raddr_q + (32'd1 << s_rsize_q);
|
||||
s_rdata_q <= memory_word(s_raddr_q + (32'd1 << s_rsize_q));
|
||||
s_rresp_q <= bfm_read_resp(s_raddr_q + (32'd1 << s_rsize_q));
|
||||
s_rbeats_q <= s_rbeats_q - 1'b1;
|
||||
end
|
||||
end
|
||||
if (npu_axi4.awvalid || npu_axi4.wvalid) begin
|
||||
$fatal(1, "NPU decoder TB: unexpected write channel activity");
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// sticky all_done capture (the pulse is one cycle)
|
||||
always_ff @(posedge clk_hp_i or negedge rst_hp_n_i) begin
|
||||
if (!rst_hp_n_i) begin
|
||||
s_saw_all_done_q <= 1'b0;
|
||||
end else if (all_done_o) begin
|
||||
s_saw_all_done_q <= 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
// progress watchdog: any decoder or bus forward motion resets the phase timer
|
||||
assign s_progress = (npu_axi4.arvalid && npu_axi4.arready) ||
|
||||
(npu_axi4.rvalid && npu_axi4.rready) ||
|
||||
(s_dec_read_req_valid && s_dec_read_req_ready) ||
|
||||
(rec_valid_o && rec_ready_i) || retired_i || progress_o;
|
||||
|
||||
always_ff @(posedge clk_hp_i or negedge rst_hp_n_i) begin
|
||||
if (!rst_hp_n_i) begin
|
||||
s_phase_cycles_q <= 0;
|
||||
end else begin
|
||||
if (s_progress) begin
|
||||
s_phase_cycles_q <= 0;
|
||||
end else begin
|
||||
s_phase_cycles_q <= s_phase_cycles_q + 1;
|
||||
if (s_phase_cycles_q >= PhaseTimeout) begin
|
||||
$fatal(1, "NPU decoder TB phase timed out: %s", s_phase);
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// pause stimulus: hold block_new_i for a 30-cycle window once the fetch
|
||||
// command of descriptor 0 is presented
|
||||
logic [5:0] s_pause_cnt_q;
|
||||
always_ff @(posedge clk_hp_i or negedge rst_hp_n_i) begin
|
||||
if (!rst_hp_n_i) begin
|
||||
block_new_i <= 1'b0;
|
||||
s_pause_done_q <= 1'b0;
|
||||
s_pause_cnt_q <= 6'd0;
|
||||
end else begin
|
||||
if (s_pause_armed_i && !s_pause_done_q && s_dec_read_req_valid) begin
|
||||
block_new_i <= 1'b1;
|
||||
s_pause_cnt_q <= 6'd0;
|
||||
s_pause_done_q <= 1'b1;
|
||||
end else if (s_pause_done_q && block_new_i) begin
|
||||
if (s_pause_cnt_q == 6'd30) begin
|
||||
block_new_i <= 1'b0;
|
||||
end else begin
|
||||
s_pause_cnt_q <= s_pause_cnt_q + 6'd1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
task automatic idle_cycles(input int unsigned cycles_i);
|
||||
begin
|
||||
repeat (cycles_i) @(posedge clk_hp_i);
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic set_phase(input string name_i);
|
||||
begin
|
||||
@(negedge clk_hp_i);
|
||||
s_phase = name_i;
|
||||
idle_cycles(1);
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic load_image(input string path_i, input logic [31:0] base_i);
|
||||
logic [31:0] s_img [0:65535];
|
||||
int unsigned s_img_words;
|
||||
begin
|
||||
$readmemh(path_i, s_img);
|
||||
s_img_words = 32'(s_job_count) * 32;
|
||||
for (int unsigned index = 0; index < s_img_words; index++) begin
|
||||
for (int unsigned lane = 0; lane < 4; lane++) begin
|
||||
s_memory[base_i-SramBase+index*4+lane] = s_img[index][lane*8+:8];
|
||||
end
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic launch_job;
|
||||
begin
|
||||
@(negedge clk_hp_i);
|
||||
job_base_i = s_job_base;
|
||||
job_count_i = s_job_count[15:0];
|
||||
job_start_i = 1'b1;
|
||||
@(negedge clk_hp_i);
|
||||
job_start_i = 1'b0;
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic check_record(input int unsigned desc_i);
|
||||
logic [31:0] s_w0, s_w6, s_w7, s_w8, s_w12, s_w13, s_w20;
|
||||
begin
|
||||
s_w0 = image_word(desc_i, 0);
|
||||
s_w6 = image_word(desc_i, 6);
|
||||
s_w7 = image_word(desc_i, 7);
|
||||
s_w8 = image_word(desc_i, 8);
|
||||
s_w12 = image_word(desc_i, 12);
|
||||
s_w13 = image_word(desc_i, 13);
|
||||
s_w20 = image_word(desc_i, 20);
|
||||
if (32'(rec_opcode_o) != {24'd0, s_w0[7:0]}) begin
|
||||
$fatal(1, "record %0d opcode mismatch: %h", desc_i, rec_opcode_o);
|
||||
end
|
||||
if ((20'(rec_h_o) != {4'd0, s_w6[15:0]}) || (20'(rec_w_o) != {4'd0, s_w6[31:16]})) begin
|
||||
$fatal(1, "record %0d input geometry mismatch", desc_i);
|
||||
end
|
||||
if ((20'(rec_cin_o) != {4'd0, s_w7[15:0]}) || (20'(rec_cout_o) != {4'd0, s_w7[31:16]})) begin
|
||||
$fatal(1, "record %0d channels mismatch", desc_i);
|
||||
end
|
||||
if ((20'(rec_oh_o) != {4'd0, s_w8[15:0]}) || (20'(rec_ow_o) != {4'd0, s_w8[31:16]})) begin
|
||||
$fatal(1, "record %0d output geometry mismatch", desc_i);
|
||||
end
|
||||
if ((rec_input0_base_o != image_word(
|
||||
desc_i, 2
|
||||
)) || (rec_output_base_o != image_word(
|
||||
desc_i, 4
|
||||
)) || (rec_weight_base_o != image_word(
|
||||
desc_i, 24
|
||||
)) || (rec_param_base_o != image_word(
|
||||
desc_i, 5
|
||||
))) begin
|
||||
$fatal(1, "record %0d base address mismatch", desc_i);
|
||||
end
|
||||
if ((rec_input0_row_bytes_o != image_word(
|
||||
desc_i, 9
|
||||
)) || (rec_output_row_bytes_o != image_word(
|
||||
desc_i, 10
|
||||
))) begin
|
||||
$fatal(1, "record %0d row stride mismatch", desc_i);
|
||||
end
|
||||
if ((rec_kh_o != s_w12[7:0]) || (rec_kw_o != s_w12[15:8]) ||
|
||||
(rec_sh_o != s_w12[23:16]) || (rec_sw_o != s_w12[31:24])) begin
|
||||
$fatal(1, "record %0d kernel/stride mismatch", desc_i);
|
||||
end
|
||||
if ((rec_pad_top_o != s_w13[7:0]) || (rec_pad_left_o != s_w13[23:16]) ||
|
||||
(rec_input0_zero_o != s_w20[7:0])) begin
|
||||
$fatal(1, "record %0d padding/zero mismatch", desc_i);
|
||||
end
|
||||
if ((rec_weight_bytes_o != image_word(
|
||||
desc_i, 25
|
||||
)) || (rec_param_bytes_o != image_word(
|
||||
desc_i, 19
|
||||
))) begin
|
||||
$fatal(1, "record %0d weight/param bytes mismatch", desc_i);
|
||||
end
|
||||
if (rec_desc_index_o != 16'(desc_i)) begin
|
||||
$fatal(1, "record %0d index mismatch: %0d", desc_i, rec_desc_index_o);
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic retire_record;
|
||||
begin
|
||||
idle_cycles(3);
|
||||
@(negedge clk_hp_i);
|
||||
retired_i = 1'b1;
|
||||
@(negedge clk_hp_i);
|
||||
retired_i = 1'b0;
|
||||
end
|
||||
endtask
|
||||
|
||||
task automatic expect_fault;
|
||||
int unsigned s_wait;
|
||||
begin
|
||||
s_wait = 0;
|
||||
while (!fault_req_o) begin
|
||||
@(posedge clk_hp_i);
|
||||
s_wait++;
|
||||
if (s_wait > 4000) $fatal(1, "NPU decoder fault never reported in %s", s_phase);
|
||||
end
|
||||
if ({28'd0, fault_code_o} != s_exp_fcode) begin
|
||||
$fatal(1, "NPU decoder fault code mismatch: got %0d expected %0d (%s)", fault_code_o,
|
||||
s_exp_fcode, s_phase);
|
||||
end
|
||||
if (fault_desc_o != s_exp_findex) begin
|
||||
$fatal(1, "NPU decoder fault index mismatch: got %0d expected %0d (%s)", fault_desc_o,
|
||||
s_exp_findex, s_phase);
|
||||
end
|
||||
if (s_mode == 32'd2) begin
|
||||
if (fault_addr_o != s_rderr_addr) begin
|
||||
$fatal(1, "NPU decoder fault address mismatch: got %h expected %h", fault_addr_o,
|
||||
s_rderr_addr);
|
||||
end
|
||||
if (s_rderr_badid == 32'd0) begin
|
||||
if (fault_info_o[1:0] != s_rderr_code[1:0] || fault_info_o[3:2] != 2'd1 ||
|
||||
fault_info_o[15:8] != 8'd255) begin
|
||||
$fatal(1, "NPU decoder fault info mismatch: %h", fault_info_o);
|
||||
end
|
||||
end
|
||||
end else begin
|
||||
if (s_exp_wordless) begin
|
||||
if (fault_addr_o != (s_job_base + s_exp_findex * 32'd128)) begin
|
||||
$fatal(1, "NPU decoder wordless fault address mismatch: got %h expected %h",
|
||||
fault_addr_o, s_job_base + s_exp_findex * 32'd128);
|
||||
end
|
||||
end else if (fault_addr_o != (s_job_base + s_exp_findex * 32'd128 + s_exp_fword * 32'd4)) begin
|
||||
$fatal(1, "NPU decoder fault address mismatch: got %h expected %h", fault_addr_o,
|
||||
s_job_base + s_exp_findex * 32'd128 + s_exp_fword * 32'd4);
|
||||
end
|
||||
if (fault_info_o != {16'd0, 8'd255, 4'd0, 2'd0, 2'd0}) begin
|
||||
$fatal(1, "NPU decoder validation fault info mismatch: %h", fault_info_o);
|
||||
end
|
||||
end
|
||||
// terminal: no record emission, no new fetch while the fault is held
|
||||
idle_cycles(20);
|
||||
if (rec_valid_o || s_dec_read_req_valid || !fault_req_o) begin
|
||||
$fatal(1, "NPU decoder kept working after a terminal fault");
|
||||
end
|
||||
// coordinated cancel returns the decoder to idle
|
||||
@(negedge clk_hp_i);
|
||||
clear_i = 1'b1;
|
||||
idle_cycles(2);
|
||||
@(negedge clk_hp_i);
|
||||
clear_i = 1'b0;
|
||||
idle_cycles(10);
|
||||
if (busy_o || fault_req_o) $fatal(1, "NPU decoder did not clear after cancel");
|
||||
end
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
s_phase = "plusargs";
|
||||
s_hold_ar_i = 1'b0;
|
||||
s_hold_r_i = 1'b0;
|
||||
s_pause_armed_i = 1'b0;
|
||||
// keep the plusarg targets procedural variables for Icarus vpi
|
||||
s_job_base = 32'd0;
|
||||
s_job_count = 32'd0;
|
||||
s_mode = 32'd0;
|
||||
s_exp_fcode = 32'd0;
|
||||
s_exp_findex = 32'd0;
|
||||
s_exp_fword = 32'hffff_ffff;
|
||||
s_rderr_addr = 32'hffff_ffff;
|
||||
s_rderr_code = 32'd0;
|
||||
s_rderr_badid = 32'd0;
|
||||
if (!$value$plusargs("IMG=%s", s_img_path)) $fatal(1, "missing +IMG");
|
||||
if (!$value$plusargs("BASE=%h", s_job_base)) $fatal(1, "missing +BASE");
|
||||
if (!$value$plusargs("COUNT=%d", s_job_count)) $fatal(1, "missing +COUNT");
|
||||
if (!$value$plusargs("MODE=%d", s_mode)) $fatal(1, "missing +MODE");
|
||||
if (!$value$plusargs("FCODE=%d", s_exp_fcode)) $fatal(1, "missing +FCODE");
|
||||
if (!$value$plusargs("FINDEX=%d", s_exp_findex)) $fatal(1, "missing +FINDEX");
|
||||
if (!$value$plusargs("FWORD=%d", s_exp_fword)) $fatal(1, "missing +FWORD");
|
||||
if (!$value$plusargs("RDERR_ADDR=%h", s_rderr_addr)) $fatal(1, "missing +RDERR_ADDR");
|
||||
if (!$value$plusargs("RDERR_CODE=%d", s_rderr_code)) $fatal(1, "missing +RDERR_CODE");
|
||||
if (!$value$plusargs("RDERR_BADID=%d", s_rderr_badid)) $fatal(1, "missing +RDERR_BADID");
|
||||
s_exp_wordless = (s_exp_fword == 32'hffff_ffff);
|
||||
for (int unsigned index = 0; index < MemoryBytes; index++) begin
|
||||
s_memory[index] = 8'(index >> 12) ^ 8'(index);
|
||||
end
|
||||
|
||||
set_phase("reset");
|
||||
repeat (4) @(posedge clk_hp_i);
|
||||
@(negedge clk_hp_i);
|
||||
rst_hp_n_i = 1'b1;
|
||||
idle_cycles(4);
|
||||
|
||||
set_phase("load image");
|
||||
load_image(s_img_path, s_job_base);
|
||||
if (s_mode == 32'd3) begin
|
||||
s_pause_armed_i = 1'b1;
|
||||
end
|
||||
|
||||
if ((s_mode == 32'd1) || (s_mode == 32'd2)) begin
|
||||
// validation/fetch fault: the fault may follow earlier valid records
|
||||
set_phase("fault traversal");
|
||||
launch_job();
|
||||
for (int unsigned desc = 0; desc < 32'(s_exp_findex); desc++) begin
|
||||
while (!rec_valid_o) begin
|
||||
@(posedge clk_hp_i);
|
||||
if (fault_req_o) $fatal(1, "NPU decoder faulted early at %0d", desc);
|
||||
end
|
||||
if (rec_desc_index_o != 16'(desc)) $fatal(1, "NPU decoder record order broken");
|
||||
check_record(desc);
|
||||
@(negedge clk_hp_i);
|
||||
rec_ready_i = 1'b1;
|
||||
@(negedge clk_hp_i);
|
||||
rec_ready_i = 1'b0;
|
||||
retire_record();
|
||||
end
|
||||
expect_fault();
|
||||
end else begin
|
||||
// accept (mode 0) and pause-during-fetch (mode 3)
|
||||
set_phase("accept");
|
||||
launch_job();
|
||||
for (int unsigned desc = 0; desc < 32'(s_job_count); desc++) begin
|
||||
while (!rec_valid_o) begin
|
||||
@(posedge clk_hp_i);
|
||||
if (fault_req_o) $fatal(1, "NPU decoder faulted on a valid image at %0d", desc);
|
||||
end
|
||||
if (rec_desc_index_o != 16'(desc)) $fatal(1, "NPU decoder record order broken");
|
||||
check_record(desc);
|
||||
@(negedge clk_hp_i);
|
||||
rec_ready_i = 1'b1;
|
||||
@(negedge clk_hp_i);
|
||||
rec_ready_i = 1'b0;
|
||||
retire_record();
|
||||
end
|
||||
if (!s_saw_all_done_q) $fatal(1, "NPU decoder all_done missing");
|
||||
idle_cycles(10);
|
||||
if (busy_o || fault_req_o) $fatal(1, "NPU decoder stayed busy after all_done");
|
||||
if (s_ar_count != 32'(s_job_count)) begin
|
||||
$fatal(1, "NPU decoder fetch count mismatch: %0d", s_ar_count);
|
||||
end
|
||||
end
|
||||
|
||||
if ((s_mode == 32'd1) || (s_mode == 32'd2)) begin
|
||||
// no reuse of the launch slot after terminal without a fresh start; a
|
||||
// fresh job_start re-fetches and re-validates the same image
|
||||
set_phase("no reuse");
|
||||
idle_cycles(20);
|
||||
if (rec_valid_o || s_dec_read_req_valid) begin
|
||||
$fatal(1, "NPU decoder reused the terminal launch slot");
|
||||
end
|
||||
if ((s_mode == 32'd1) || (s_mode == 32'd2)) begin
|
||||
launch_job();
|
||||
for (int unsigned desc = 0; desc < 32'(s_exp_findex); desc++) begin
|
||||
while (!rec_valid_o) begin
|
||||
@(posedge clk_hp_i);
|
||||
if (fault_req_o) $fatal(1, "NPU decoder refetch faulted early at %0d", desc);
|
||||
end
|
||||
check_record(desc);
|
||||
@(negedge clk_hp_i);
|
||||
rec_ready_i = 1'b1;
|
||||
@(negedge clk_hp_i);
|
||||
rec_ready_i = 1'b0;
|
||||
retire_record();
|
||||
end
|
||||
expect_fault();
|
||||
end
|
||||
end
|
||||
|
||||
set_phase("done");
|
||||
$display("NPU job decoder test passed");
|
||||
$finish;
|
||||
end
|
||||
|
||||
initial begin
|
||||
repeat (500000) @(posedge clk_hp_i);
|
||||
$fatal(1, "NPU decoder TB global timeout in %s", s_phase);
|
||||
end
|
||||
endmodule
|
||||
@@ -725,14 +725,13 @@ module npu_reg_tb;
|
||||
$finish;
|
||||
end
|
||||
|
||||
// The tied-off Phase 2 master must never issue a request and stays ready.
|
||||
// With START still rejected at Phase 3, no software-visible job can launch,
|
||||
// so the master must never issue a request in this shell context. The R/B
|
||||
// receivers are engine-driven at Phase 3 (live while obligations exist).
|
||||
always @(posedge clk_hp_i) begin
|
||||
if (rst_hp_n_i) begin
|
||||
if (npu_axi4.awvalid || npu_axi4.wvalid || npu_axi4.arvalid) begin
|
||||
$fatal(1, "NPU Phase 2 AXI master issued a request");
|
||||
end
|
||||
if (!npu_axi4.bready || !npu_axi4.rready) begin
|
||||
$fatal(1, "NPU Phase 2 AXI receivers not ready");
|
||||
$fatal(1, "NPU shell master issued a request without an accepted job");
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,813 @@
|
||||
"""NPU-P3 job-execution RTL unit tests (Icarus and Verilator).
|
||||
|
||||
``test_npu_dma`` drives the AXI4 DMA engine testbench. ``test_npu_job_decoder``
|
||||
drives the descriptor fetch/validation testbench with descriptor images and
|
||||
expected verdicts generated here, using ``scripts/npu_descriptors.py`` as the
|
||||
golden validator (fault code and ABI word parity). ``test_npu_core`` drives the
|
||||
job-level testbench (production launch interface into npu_core + npu_dma over a
|
||||
byte-memory BFM) with directed transport jobs whose stored bytes are checked
|
||||
against a Python golden model of the documented P3 transport-content echo rule
|
||||
(output byte (oy, ox, c) echoes the gather byte at reduction index c mod G).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
COMMON = ROOT / "rtl/managed/clusterip/common/rtl"
|
||||
MULTIMEDIA = ROOT / "rtl/ip/multimedia"
|
||||
TECH = ROOT / "rtl/tech"
|
||||
|
||||
sys.path.insert(0, str(ROOT / "scripts"))
|
||||
from npu_descriptors import ( # noqa: E402
|
||||
DESCRIPTOR_BYTES,
|
||||
Descriptor,
|
||||
DescriptorError,
|
||||
validate_descriptor,
|
||||
)
|
||||
|
||||
INCDIRS = [COMMON, COMMON / "interface", MULTIMEDIA]
|
||||
COMMON_SOURCES = [
|
||||
COMMON / "interface/axi4_if.sv",
|
||||
]
|
||||
DMA_SOURCES = [
|
||||
MULTIMEDIA / "npu_dma.sv",
|
||||
]
|
||||
DECODER_SOURCES = [
|
||||
MULTIMEDIA / "npu_pkg.sv",
|
||||
MULTIMEDIA / "npu_dma.sv",
|
||||
MULTIMEDIA / "npu_job_decoder.sv",
|
||||
]
|
||||
CORE_SOURCES = [
|
||||
COMMON / "utils/register.sv",
|
||||
MULTIMEDIA / "npu_pkg.sv",
|
||||
TECH / "tc_sram.sv",
|
||||
MULTIMEDIA / "npu_local_sram.sv",
|
||||
MULTIMEDIA / "npu_patch_packer.sv",
|
||||
MULTIMEDIA / "npu_dma.sv",
|
||||
MULTIMEDIA / "npu_job_decoder.sv",
|
||||
MULTIMEDIA / "npu_scheduler.sv",
|
||||
MULTIMEDIA / "npu_core.sv",
|
||||
]
|
||||
|
||||
MEM_BASE = 0x3000_0000
|
||||
DEC_MEM_BYTES = 0x40000 # decoder TB memory: 256 KiB
|
||||
CORE_MEM_BYTES = 0x80000 # core TB memory: 512 KiB
|
||||
GAP_FILL = 0xA5
|
||||
|
||||
# fault codes
|
||||
F_DESCRIPTOR = 1
|
||||
F_UNSUPPORTED = 2
|
||||
F_RANGE = 3
|
||||
F_AXI_READ = 4
|
||||
F_AXI_WRITE = 5
|
||||
F_AXI_PROTOCOL = 6
|
||||
F_ARITHMETIC = 7
|
||||
F_NO_PROGRESS = 8
|
||||
F_LOCAL_STATE = 9
|
||||
F_RESET_CANCELLED = 10
|
||||
|
||||
# result codes
|
||||
R_DONE = 1
|
||||
R_ERROR = 2
|
||||
R_ABORTED = 3
|
||||
R_RESET_CANCELLED = 4
|
||||
|
||||
UNIT_KERNEL_STRIDE = 0x01010101
|
||||
|
||||
|
||||
def _tools() -> dict[str, str]:
|
||||
tools: dict[str, str] = {}
|
||||
missing = []
|
||||
for name in ("iverilog", "vvp", "sv2v", "verilator"):
|
||||
path = shutil.which(name)
|
||||
if path is None:
|
||||
missing.append(name)
|
||||
else:
|
||||
tools[name] = path
|
||||
if missing:
|
||||
pytest.fail(f"required simulation tools missing: {', '.join(missing)}")
|
||||
return tools
|
||||
|
||||
|
||||
def _build_iverilog(tools: dict[str, str], tmp_path: Path, name: str, top: str,
|
||||
sources: list[Path]) -> Path:
|
||||
filelist = tmp_path / f"{name}.fl"
|
||||
filelist.write_text(
|
||||
"\n".join(
|
||||
[
|
||||
"+define+SV_ASSRT_DISABLE",
|
||||
"+define+PDK_BEHAV",
|
||||
*(f"+incdir+{incdir}" for incdir in INCDIRS),
|
||||
*(str(source) for source in sources),
|
||||
"",
|
||||
]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
converted = tmp_path / f"{name}.v"
|
||||
subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(ROOT / "rtl/mini/script/convt_sv2v.py"),
|
||||
"-f",
|
||||
str(filelist),
|
||||
"--output",
|
||||
str(converted),
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
simulation = tmp_path / f"{name}.iverilog"
|
||||
subprocess.run(
|
||||
[tools["iverilog"], "-g2012", "-s", top, "-o", str(simulation), str(converted)],
|
||||
check=True,
|
||||
)
|
||||
return simulation
|
||||
|
||||
|
||||
def _build_verilator(tools: dict[str, str], tmp_path: Path, name: str, top: str,
|
||||
sources: list[Path]) -> Path:
|
||||
object_dir = tmp_path / f"obj-{name}"
|
||||
ccache_dir = tmp_path / f"ccache-{name}"
|
||||
ccache_dir.mkdir()
|
||||
subprocess.run(
|
||||
[
|
||||
tools["verilator"],
|
||||
"--binary",
|
||||
"--timing",
|
||||
"-Wno-fatal",
|
||||
"+define+PDK_BEHAV",
|
||||
"--top-module",
|
||||
top,
|
||||
*(f"+incdir+{incdir}" for incdir in INCDIRS),
|
||||
*(str(source) for source in sources),
|
||||
"-Mdir",
|
||||
str(object_dir),
|
||||
"-o",
|
||||
"simv",
|
||||
],
|
||||
check=True,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
env={
|
||||
**os.environ,
|
||||
"CCACHE_DIR": str(ccache_dir),
|
||||
"CCACHE_TEMPDIR": str(ccache_dir),
|
||||
},
|
||||
)
|
||||
return object_dir / "simv"
|
||||
|
||||
|
||||
def _run_p3_test(tools: dict[str, str], simulator: str, tmp_path: Path, name: str, top: str,
|
||||
testbench: Path, sources: list[Path], marker: str) -> None:
|
||||
full_sources = [*COMMON_SOURCES, *sources, testbench]
|
||||
if simulator == "iverilog":
|
||||
simulation = _build_iverilog(tools, tmp_path, name, top, full_sources)
|
||||
command = [tools["vvp"], str(simulation)]
|
||||
elif simulator == "verilator":
|
||||
command = [str(_build_verilator(tools, tmp_path, name, top, full_sources))]
|
||||
else:
|
||||
raise AssertionError(f"unknown simulator {simulator}")
|
||||
result = subprocess.run(command, check=True, text=True, capture_output=True)
|
||||
assert marker in result.stdout
|
||||
|
||||
|
||||
def _build_sim(tools: dict[str, str], simulator: str, tmp_path: Path, name: str, top: str,
|
||||
testbench: Path, sources: list[Path]) -> list[str]:
|
||||
full_sources = [*COMMON_SOURCES, *sources, testbench]
|
||||
if simulator == "iverilog":
|
||||
return [tools["vvp"], str(_build_iverilog(tools, tmp_path, name, top, full_sources))]
|
||||
if simulator == "verilator":
|
||||
return [str(_build_verilator(tools, tmp_path, name, top, full_sources))]
|
||||
raise AssertionError(f"unknown simulator {simulator}")
|
||||
|
||||
|
||||
def _run(command: list[str], plusargs: list[str], marker: str) -> str:
|
||||
result = subprocess.run([*command, *plusargs], check=True, text=True, capture_output=True)
|
||||
assert marker in result.stdout
|
||||
return result.stdout
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Descriptor construction (mirrors scripts/npu_descriptors.py ABI 1.0)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _slot_bases(slot: int, shift: int = 0) -> dict[str, int]:
|
||||
# fully disjoint operand regions per descriptor slot (decoder validation
|
||||
# is arithmetic-only; the core TB executes the contents)
|
||||
return {
|
||||
"in0": 0x3000_4000 + (slot + shift) * 0x4000,
|
||||
"in1": 0x3001_4000 + (slot + shift) * 0x4000,
|
||||
"out": 0x3002_0000 + (slot + shift) * 0x8000,
|
||||
"w": 0x3004_0000 + (slot + shift) * 0x8000,
|
||||
"p": 0x3006_0000 + (slot + shift) * 0x8000,
|
||||
}
|
||||
|
||||
|
||||
def _valid_descriptor(opcode: int, slot: int, shift: int = 0) -> Descriptor:
|
||||
"""One ABI-1.0-valid record for opcode in operand slot `slot`."""
|
||||
b = _slot_bases(slot, shift)
|
||||
d = Descriptor()
|
||||
d.version_opcode = 0x0100_0000 | opcode
|
||||
d.act_min = -128
|
||||
d.act_max = 127
|
||||
d.input0_base = b["in0"]
|
||||
d.input1_base = 0
|
||||
d.output_base = b["out"]
|
||||
d.param_base = 0
|
||||
d.weight_base = 0
|
||||
d.weight_bytes = 0
|
||||
d.param_bytes = 0
|
||||
d.input1_zero = 0
|
||||
|
||||
if opcode == 1: # CONV2D
|
||||
h, w, cin, cout = 5, 6, 3, 5
|
||||
kh, kw, sh, sw = 3, 2, 1, 2
|
||||
pt, pb, pl, pr = 1, 1, 0, 1
|
||||
oh = (h + pt + pb - kh) // sh + 1
|
||||
ow = (w + pl + pr - kw) // sw + 1
|
||||
d.input_hw = (w << 16) | h
|
||||
d.channels = (cout << 16) | cin
|
||||
d.output_hw = (ow << 16) | oh
|
||||
d.input0_row_bytes = w * cin + 2
|
||||
d.output_row_bytes = ow * cout + 1
|
||||
d.kernel_stride = (sw << 24) | (sh << 16) | (kw << 8) | kh
|
||||
d.padding = (pr << 24) | (pl << 16) | (pb << 8) | pt
|
||||
d.tile_hw = (2 << 8) | 2
|
||||
full_k = kh * kw * cin
|
||||
d.k_slice = 7
|
||||
assert d.k_slice <= full_k
|
||||
d.input0_bytes = (h - 1) * d.input0_row_bytes + w * cin
|
||||
d.output_bytes = (oh - 1) * d.output_row_bytes + ow * cout
|
||||
d.weight_base = b["w"]
|
||||
d.weight_bytes = ((cout + 7) // 8) * full_k * 8
|
||||
d.param_base = b["p"]
|
||||
d.param_bytes = cout * 16
|
||||
elif opcode == 2: # DEPTHWISE3X3
|
||||
h, w, cin = 6, 7, 5
|
||||
sh = sw = 2
|
||||
pt, pl = 1, 1
|
||||
oh = (h + pt - 3) // sh + 1
|
||||
ow = (w + pl - 3) // sw + 1
|
||||
d.input_hw = (w << 16) | h
|
||||
d.channels = (cin << 16) | cin
|
||||
d.output_hw = (ow << 16) | oh
|
||||
d.input0_row_bytes = w * cin + 1
|
||||
d.output_row_bytes = ow * cin + 1
|
||||
d.kernel_stride = (sw << 24) | (sh << 16) | (3 << 8) | 3
|
||||
d.padding = pl << 16 | pt
|
||||
d.tile_hw = (2 << 8) | 2
|
||||
d.k_slice = 9
|
||||
d.input0_bytes = (h - 1) * d.input0_row_bytes + w * cin
|
||||
d.output_bytes = (oh - 1) * d.output_row_bytes + ow * cin
|
||||
d.weight_base = b["w"]
|
||||
d.weight_bytes = ((cin + 7) // 8) * 72
|
||||
d.param_base = b["p"]
|
||||
d.param_bytes = cin * 16
|
||||
elif opcode == 3: # FULLY_CONNECTED
|
||||
cin, cout = 13, 4
|
||||
d.input_hw = (1 << 16) | 1
|
||||
d.channels = (cout << 16) | cin
|
||||
d.output_hw = (1 << 16) | 1
|
||||
d.input0_row_bytes = cin + 3
|
||||
d.output_row_bytes = cout + 4
|
||||
d.kernel_stride = UNIT_KERNEL_STRIDE
|
||||
d.padding = 0
|
||||
d.tile_hw = (1 << 8) | 1
|
||||
d.k_slice = 13
|
||||
d.input0_bytes = cin
|
||||
d.output_bytes = cout
|
||||
d.weight_base = b["w"]
|
||||
d.weight_bytes = ((cout + 7) // 8) * cin * 8
|
||||
d.param_base = b["p"]
|
||||
d.param_bytes = cout * 16
|
||||
elif opcode == 4: # ADD
|
||||
h, w, cin = 2, 3, 3
|
||||
d.input_hw = (w << 16) | h
|
||||
d.channels = (cin << 16) | cin
|
||||
d.output_hw = (w << 16) | h
|
||||
d.input0_row_bytes = w * cin + 3
|
||||
d.output_row_bytes = w * cin + 3
|
||||
d.input1_row_bytes = w * cin + 3
|
||||
d.input1_base = b["in1"]
|
||||
d.input1_bytes = (h - 1) * d.input1_row_bytes + w * cin
|
||||
d.input1_zero = -5
|
||||
d.kernel_stride = UNIT_KERNEL_STRIDE
|
||||
d.padding = 0
|
||||
d.tile_hw = (2 << 8) | 2
|
||||
d.k_slice = 1
|
||||
d.input0_bytes = (h - 1) * d.input0_row_bytes + w * cin
|
||||
d.output_bytes = (h - 1) * d.output_row_bytes + w * cin
|
||||
d.param_base = b["p"]
|
||||
d.param_bytes = 32
|
||||
elif opcode in (5, 6): # MAX_POOL / AVERAGE_POOL
|
||||
h, w, cin = 6, 6, 4
|
||||
kh = kw = 3 if opcode == 5 else 2
|
||||
sh = sw = 1 if opcode == 5 else 2
|
||||
pt = pb = pl = pr = 1
|
||||
oh = (h + pt + pb - kh) // sh + 1
|
||||
ow = (w + pl + pr - kw) // sw + 1
|
||||
d.input_hw = (w << 16) | h
|
||||
d.channels = (cin << 16) | cin
|
||||
d.output_hw = (ow << 16) | oh
|
||||
d.input0_row_bytes = w * cin
|
||||
d.output_row_bytes = ow * cin
|
||||
d.kernel_stride = (sw << 24) | (sh << 16) | (kw << 8) | kh
|
||||
d.padding = (pr << 24) | (pl << 16) | (pb << 8) | pt
|
||||
d.tile_hw = (2 << 8) | 2
|
||||
d.k_slice = min(5, kh * kw)
|
||||
d.input0_bytes = (h - 1) * d.input0_row_bytes + w * cin
|
||||
d.output_bytes = (oh - 1) * d.output_row_bytes + ow * cin
|
||||
d.input0_zero = 11
|
||||
d.output_zero = 11
|
||||
elif opcode == 7: # GLOBAL_AVERAGE_POOL
|
||||
h, w, cin = 4, 5, 6
|
||||
d.input_hw = (w << 16) | h
|
||||
d.channels = (cin << 16) | cin
|
||||
d.output_hw = (1 << 16) | 1
|
||||
d.input0_row_bytes = w * cin + 2
|
||||
d.output_row_bytes = cin
|
||||
d.kernel_stride = 0
|
||||
d.padding = 0
|
||||
d.tile_hw = (1 << 8) | 1
|
||||
d.k_slice = 9
|
||||
d.input0_bytes = (h - 1) * d.input0_row_bytes + w * cin
|
||||
d.output_bytes = cin
|
||||
d.input0_zero = -3
|
||||
d.output_zero = -3
|
||||
elif opcode == 8: # CLAMP
|
||||
h, w, cin = 3, 5, 4
|
||||
d.input_hw = (w << 16) | h
|
||||
d.channels = (cin << 16) | cin
|
||||
d.output_hw = (w << 16) | h
|
||||
d.input0_row_bytes = w * cin + 4
|
||||
d.output_row_bytes = w * cin + 12
|
||||
d.kernel_stride = UNIT_KERNEL_STRIDE
|
||||
d.padding = 0
|
||||
d.tile_hw = (2 << 8) | 2
|
||||
d.k_slice = 1
|
||||
d.input0_bytes = (h - 1) * d.input0_row_bytes + w * cin
|
||||
d.output_bytes = (h - 1) * d.output_row_bytes + w * cin
|
||||
d.input0_zero = 7
|
||||
d.output_zero = 7
|
||||
else:
|
||||
raise AssertionError(f"no factory for opcode {opcode}")
|
||||
validate_descriptor(d, descriptor_region=(MEM_BASE, 4 * DESCRIPTOR_BYTES))
|
||||
return d
|
||||
|
||||
|
||||
def _words_of(desc: Descriptor) -> list[int]:
|
||||
return list(struct.unpack(f"<{DESCRIPTOR_BYTES // 4}I", desc.to_bytes()))
|
||||
|
||||
|
||||
def _image_hex(path: Path, words: list[int]) -> None:
|
||||
path.write_text("\n".join(f"{word & 0xFFFFFFFF:08x}" for word in words) + "\n",
|
||||
encoding="utf-8")
|
||||
|
||||
|
||||
def _descriptor_case_image(records: list[Descriptor], count: int) -> list[int]:
|
||||
words: list[int] = []
|
||||
for record in records:
|
||||
words.extend(_words_of(record))
|
||||
while len(words) < count * 32:
|
||||
words.append(0xDEAD0000 | (len(words) & 0xFFFF))
|
||||
return words
|
||||
|
||||
|
||||
def _expected_fault(records: list[Descriptor], count: int):
|
||||
"""(code, word, index) of the first validator failure, or None."""
|
||||
region = (MEM_BASE, count * DESCRIPTOR_BYTES)
|
||||
for index, record in enumerate(records[:count]):
|
||||
wire = Descriptor.from_bytes(record.to_bytes())
|
||||
try:
|
||||
validate_descriptor(wire, descriptor_region=region)
|
||||
except DescriptorError as error:
|
||||
return error.code, error.word, index
|
||||
return None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Decoder testbench cases
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _decoder_cases(tmp_path: Path) -> list[list[str]]:
|
||||
cases: list[list[str]] = []
|
||||
|
||||
def emit(name: str, records: list[Descriptor], count: int, mode: int, fcode: int,
|
||||
findex: int, fword: int, rderr_addr: int = 0xFFFFFFFF,
|
||||
rderr_code: int = 0, rderr_badid: int = 0) -> None:
|
||||
image = tmp_path / f"dec_{name}.hex"
|
||||
_image_hex(image, _descriptor_case_image(records, count))
|
||||
cases.append(
|
||||
[
|
||||
f"+IMG={image}",
|
||||
f"+BASE={MEM_BASE:x}",
|
||||
f"+COUNT={count}",
|
||||
f"+MODE={mode}",
|
||||
f"+FCODE={fcode}",
|
||||
f"+FINDEX={findex}",
|
||||
f"+FWORD={fword & 0xFFFFFFFF}",
|
||||
f"+RDERR_ADDR={rderr_addr:x}",
|
||||
f"+RDERR_CODE={rderr_code}",
|
||||
f"+RDERR_BADID={rderr_badid}",
|
||||
f"+NAME={name}",
|
||||
]
|
||||
)
|
||||
|
||||
# valid accept per opcode, plus one multi-record in-order traversal, plus
|
||||
# the ADD input0/input1 alias legality case, plus pause-during-fetch
|
||||
for opcode in range(1, 9):
|
||||
emit(f"valid_op{opcode}", [_valid_descriptor(opcode, 0)], 1, 0, 0, 0, 0xFFFFFFFF)
|
||||
multi = [
|
||||
_valid_descriptor(8, 0),
|
||||
_valid_descriptor(1, 1),
|
||||
_valid_descriptor(7, 2),
|
||||
_valid_descriptor(4, 3),
|
||||
]
|
||||
assert _expected_fault(multi, 4) is None
|
||||
emit("valid_multi4", multi, 4, 0, 0, 0, 0xFFFFFFFF)
|
||||
alias = _valid_descriptor(4, 0)
|
||||
alias.input1_base = alias.input0_base
|
||||
validate_descriptor(alias, descriptor_region=(MEM_BASE, 2 * DESCRIPTOR_BYTES))
|
||||
emit("valid_add_alias", [alias], 1, 0, 0, 0, 0xFFFFFFFF)
|
||||
emit("pause_fetch", [_valid_descriptor(8, 0), _valid_descriptor(1, 1)], 2, 3, 0, 0,
|
||||
0xFFFFFFFF)
|
||||
|
||||
# validation rejection classes
|
||||
def mutate(name: str, opcode: int, fn) -> None:
|
||||
base_record = _valid_descriptor(opcode, 0)
|
||||
words = _words_of(base_record)
|
||||
fn(words)
|
||||
record = Descriptor.from_bytes(struct.pack(f"<{len(words)}I", *words))
|
||||
expected = _expected_fault([record], 3)
|
||||
assert expected is not None, f"mutation {name} unexpectedly valid"
|
||||
code, word, index = expected
|
||||
assert index == 0
|
||||
emit(name, [record], 3, 1, code, index, 0xFFFFFFFF if word is None else word)
|
||||
|
||||
mutate("bad_version", 8, lambda w: w.__setitem__(0, 0x0200_0000 | 8))
|
||||
mutate("bad_version_mid", 8, lambda w: w.__setitem__(0, 0x0100_0100 | 8))
|
||||
mutate("bad_reserved1", 8, lambda w: w.__setitem__(1, 1))
|
||||
mutate("bad_reserved26", 8, lambda w: w.__setitem__(26, 1))
|
||||
mutate("bad_reserved31", 8, lambda w: w.__setitem__(31, 0x8000_0000))
|
||||
mutate("bad_tile_upper", 8, lambda w: w.__setitem__(14, w[14] | 0x0001_0000))
|
||||
mutate("bad_in0zero_upper", 8, lambda w: w.__setitem__(20, w[20] | 0x100))
|
||||
mutate("bad_outzero_upper", 8, lambda w: w.__setitem__(22, w[22] | 0x0100_0000))
|
||||
mutate("bad_bounds_upper", 8, lambda w: w.__setitem__(23, w[23] | 0x0001_0000))
|
||||
mutate("bad_bounds_order", 8, lambda w: w.__setitem__(23, 0x0000_0305))
|
||||
mutate("bad_tile_h0", 8, lambda w: w.__setitem__(14, w[14] & 0xFFFF_FF00))
|
||||
mutate("bad_tile_product", 8, lambda w: w.__setitem__(14, 0x0000_0303))
|
||||
mutate("bad_kslice0", 8, lambda w: w.__setitem__(15, 0))
|
||||
mutate("bad_kslice1025", 8, lambda w: w.__setitem__(15, 1025))
|
||||
mutate("bad_opcode0", 8, lambda w: w.__setitem__(0, w[0] & 0xFFFF_FF00))
|
||||
mutate("bad_opcode9", 8, lambda w: w.__setitem__(0, (w[0] & 0xFFFF_FF00) | 9))
|
||||
mutate("unused_input1_base", 8, lambda w: w.__setitem__(3, 0x3001_4000))
|
||||
mutate("unused_input1_row", 8, lambda w: w.__setitem__(11, 4))
|
||||
mutate("unused_input1_bytes", 8, lambda w: w.__setitem__(17, 4))
|
||||
mutate("unused_input1_zero", 8, lambda w: w.__setitem__(21, 1))
|
||||
mutate("unused_weight_base", 8, lambda w: w.__setitem__(24, 0x3002_8000))
|
||||
mutate("unused_weight_bytes", 8, lambda w: w.__setitem__(25, 8))
|
||||
mutate("unused_param_base", 8, lambda w: w.__setitem__(5, 0x3003_0000))
|
||||
mutate("unused_param_bytes", 8, lambda w: w.__setitem__(19, 8))
|
||||
mutate("gap_kernel_word", 7, lambda w: w.__setitem__(12, 1))
|
||||
mutate("gap_padding", 7, lambda w: w.__setitem__(13, 1))
|
||||
mutate("dim_h0", 8, lambda w: w.__setitem__(6, w[6] & 0xFFFF_0000))
|
||||
mutate("dim_w0", 8, lambda w: w.__setitem__(6, w[6] & 0x0000_FFFF))
|
||||
mutate("dim_cin0", 8, lambda w: w.__setitem__(7, w[7] & 0xFFFF_0000))
|
||||
mutate("dim_cout0", 8, lambda w: w.__setitem__(7, w[7] & 0x0000_FFFF))
|
||||
mutate("dim_oh0", 8, lambda w: w.__setitem__(8, w[8] & 0xFFFF_0000))
|
||||
mutate("dim_ow0", 8, lambda w: w.__setitem__(8, w[8] & 0x0000_FFFF))
|
||||
mutate("dim_h4097", 8, lambda w: w.__setitem__(6, (w[6] & 0xFFFF_0000) | 4097))
|
||||
mutate("conv_kh0", 1, lambda w: w.__setitem__(12, w[12] & 0xFFFF_FF00))
|
||||
mutate("conv_pad_ge_kernel", 1, lambda w: w.__setitem__(13, (w[13] & 0xFFFF_FF00) | 3))
|
||||
mutate("conv_extent_negative", 1,
|
||||
lambda w: (w.__setitem__(6, (w[6] & 0xFFFF_0000) | 2), w.__setitem__(13, 0)))
|
||||
mutate("conv_extent_mismatch", 1, lambda w: w.__setitem__(8, w[8] + 1))
|
||||
mutate("conv_kh17", 1, lambda w: w.__setitem__(12, w[12] | 17))
|
||||
mutate("conv_stride3", 1, lambda w: w.__setitem__(12, (w[12] & 0xFF00_FFFF) | (3 << 16)))
|
||||
mutate("dw_kh5", 2, lambda w: w.__setitem__(12, w[12] | 5))
|
||||
mutate("dw_cout_ne", 2, lambda w: w.__setitem__(7, w[7] + (1 << 16)))
|
||||
mutate("fc_h2", 3, lambda w: w.__setitem__(6, w[6] | 2))
|
||||
mutate("fc_oh2", 3, lambda w: w.__setitem__(8, w[8] | 2))
|
||||
mutate("fc_kernel0", 3, lambda w: w.__setitem__(12, 0))
|
||||
mutate("fc_padding", 3, lambda w: w.__setitem__(13, 1))
|
||||
mutate("add_input1_zero_upper", 4, lambda w: w.__setitem__(21, w[21] | 0x100))
|
||||
mutate("add_input1_base0", 4, lambda w: w.__setitem__(3, 0))
|
||||
mutate("add_broadcast", 4, lambda w: w.__setitem__(8, w[8] + 1))
|
||||
mutate("add_cout_ne", 4, lambda w: w.__setitem__(7, w[7] + (1 << 16)))
|
||||
mutate("add_kernel", 4, lambda w: w.__setitem__(12, 0))
|
||||
mutate("add_padding", 4, lambda w: w.__setitem__(13, 1))
|
||||
mutate("pool_kh17", 5, lambda w: w.__setitem__(12, w[12] | 17))
|
||||
mutate("pool_cout_ne", 5, lambda w: w.__setitem__(7, w[7] + (1 << 16)))
|
||||
mutate("pool_zero_mismatch", 5, lambda w: w.__setitem__(22, w[22] ^ 1))
|
||||
mutate("gap_oh2", 7, lambda w: w.__setitem__(8, w[8] | 2))
|
||||
mutate("gap_cout_ne", 7, lambda w: w.__setitem__(7, w[7] + (1 << 16)))
|
||||
mutate("gap_zero_mismatch", 7, lambda w: w.__setitem__(22, w[22] ^ 1))
|
||||
mutate("clamp_oh_ne", 8, lambda w: w.__setitem__(8, w[8] + 1))
|
||||
mutate("clamp_zero_mismatch", 8, lambda w: w.__setitem__(22, w[22] ^ 1))
|
||||
mutate("clamp_kernel", 8, lambda w: w.__setitem__(12, 0))
|
||||
mutate("clamp_padding", 8, lambda w: w.__setitem__(13, 1))
|
||||
mutate("kslice_gt_full", 1, lambda w: w.__setitem__(15, 19))
|
||||
mutate("tile_gt_oh", 8, lambda w: w.__setitem__(14, (w[14] & 0xFFFF_FF00) | 4))
|
||||
mutate("weight_base0", 1, lambda w: w.__setitem__(24, 0))
|
||||
mutate("weight_bytes_bad", 1, lambda w: w.__setitem__(25, w[25] + 8))
|
||||
mutate("param_base0", 1, lambda w: w.__setitem__(5, 0))
|
||||
mutate("param_bytes_bad", 1, lambda w: w.__setitem__(19, w[19] + 16))
|
||||
mutate("add_param_bytes_bad", 4, lambda w: w.__setitem__(19, 16))
|
||||
clamp_min_in = 5 * 4 # W*Cin of the CLAMP factory
|
||||
clamp_min_out = 5 * 4 # OW*Cout of the CLAMP factory
|
||||
add_min_in = 3 * 3 # W*Cin of the ADD factory
|
||||
mutate("stride_in0_low", 8, lambda w: w.__setitem__(9, clamp_min_in - 1))
|
||||
mutate("stride_out_low", 8, lambda w: w.__setitem__(10, clamp_min_out - 1))
|
||||
mutate("add_stride1_low", 4, lambda w: w.__setitem__(11, add_min_in - 1))
|
||||
mutate("span_in0", 8, lambda w: w.__setitem__(16, w[16] - 1))
|
||||
mutate("span_out", 8, lambda w: w.__setitem__(18, w[18] - 1))
|
||||
mutate("wrap_in0_alloc", 8,
|
||||
lambda w: (w.__setitem__(2, 0xFFFF_F000), w.__setitem__(16, 0x2000)))
|
||||
mutate("wrap_out", 8,
|
||||
lambda w: (w.__setitem__(4, 0xFFFF_FFC0), w.__setitem__(18, 96)))
|
||||
mutate("wrap_weight", 1, lambda w: w.__setitem__(24, 0xFFFF_FF80))
|
||||
mutate("wrap_param", 1, lambda w: w.__setitem__(5, 0xFFFF_FFC0))
|
||||
mutate("align_param", 1, lambda w: w.__setitem__(5, w[5] | 4))
|
||||
mutate("align_weight", 1, lambda w: w.__setitem__(24, w[24] | 4))
|
||||
mutate("overlap_in0_out", 8, lambda w: w.__setitem__(4, w[2]))
|
||||
mutate("overlap_out_desc", 8, lambda w: w.__setitem__(4, MEM_BASE))
|
||||
mutate("overlap_weight_param", 1, lambda w: w.__setitem__(5, w[24]))
|
||||
# first-fault order: earlier validator stage wins
|
||||
mutate("priority_struct", 8,
|
||||
lambda w: (w.__setitem__(0, 0x0300_0000 | 8), w.__setitem__(1, 7)))
|
||||
mutate("priority_unused_over_geometry", 8,
|
||||
lambda w: (w.__setitem__(3, 0x3001_4000), w.__setitem__(6, 0)))
|
||||
|
||||
# validation fault after a valid prefix record
|
||||
prefix = _valid_descriptor(8, 0)
|
||||
faulty = _valid_descriptor(1, 1)
|
||||
words = _words_of(faulty)
|
||||
words[15] = 19 # K_SLICE above full K
|
||||
faulty = Descriptor.from_bytes(struct.pack(f"<{len(words)}I", *words))
|
||||
expected = _expected_fault([prefix, faulty], 4)
|
||||
assert expected == (F_UNSUPPORTED, 15, 1)
|
||||
emit("fault_at_index1", [prefix, faulty], 4, 1, expected[0], 1, expected[1])
|
||||
|
||||
# fetch bus faults
|
||||
emit("fetch_slverr", [_valid_descriptor(8, 0)], 1, 2, F_AXI_READ, 0, 0xFFFFFFFF,
|
||||
rderr_addr=MEM_BASE + 24, rderr_code=2)
|
||||
emit("fetch_decerr_index1", [_valid_descriptor(8, 0), _valid_descriptor(1, 1)], 2, 2,
|
||||
F_AXI_READ, 1, 0xFFFFFFFF, rderr_addr=MEM_BASE + 128, rderr_code=3)
|
||||
emit("fetch_bad_rid", [_valid_descriptor(8, 0)], 1, 2, F_AXI_PROTOCOL, 0, 0xFFFFFFFF,
|
||||
rderr_addr=MEM_BASE, rderr_badid=1)
|
||||
# truncation: the third record does not exist in memory
|
||||
emit("fetch_truncated", [_valid_descriptor(8, 0), _valid_descriptor(1, 1)], 3, 2,
|
||||
F_AXI_READ, 2, 0xFFFFFFFF, rderr_addr=MEM_BASE + 256, rderr_code=3)
|
||||
return cases
|
||||
|
||||
|
||||
@pytest.mark.parametrize("simulator", ("iverilog", "verilator"))
|
||||
def test_npu_dma(tmp_path: Path, simulator: str) -> None:
|
||||
tools = _tools()
|
||||
_run_p3_test(
|
||||
tools,
|
||||
simulator,
|
||||
tmp_path,
|
||||
f"npu_dma_{simulator}",
|
||||
"npu_dma_tb",
|
||||
ROOT / "tests/rtl/npu_dma_tb.sv",
|
||||
DMA_SOURCES,
|
||||
"NPU DMA test passed",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("simulator", ("iverilog", "verilator"))
|
||||
def test_npu_job_decoder(tmp_path: Path, simulator: str) -> None:
|
||||
tools = _tools()
|
||||
command = _build_sim(
|
||||
tools,
|
||||
simulator,
|
||||
tmp_path,
|
||||
f"npu_job_decoder_{simulator}",
|
||||
"npu_job_decoder_tb",
|
||||
ROOT / "tests/rtl/npu_job_decoder_tb.sv",
|
||||
DECODER_SOURCES,
|
||||
)
|
||||
cases = _decoder_cases(tmp_path)
|
||||
assert len(cases) >= 70
|
||||
for plusargs in cases:
|
||||
_run(command, plusargs, "NPU job decoder test passed")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Core testbench scenarios
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _pat(address: int) -> int:
|
||||
"""Deterministic input/weight/param content byte."""
|
||||
return (address ^ (address >> 7) ^ 0x36) & 0xFF
|
||||
|
||||
|
||||
def _fill_descriptor_content(mem: bytearray, desc: Descriptor) -> None:
|
||||
for offset in range(desc.input0_bytes):
|
||||
mem[desc.input0_base - MEM_BASE + offset] = _pat(desc.input0_base + offset)
|
||||
if desc.input1_base:
|
||||
for offset in range(desc.input1_bytes):
|
||||
mem[desc.input1_base - MEM_BASE + offset] = _pat(desc.input1_base + 3 + offset)
|
||||
if desc.weight_bytes:
|
||||
for offset in range(desc.weight_bytes):
|
||||
mem[desc.weight_base - MEM_BASE + offset] = _pat(desc.weight_base + 1 + offset)
|
||||
if desc.param_bytes:
|
||||
for offset in range(desc.param_bytes):
|
||||
mem[desc.param_base - MEM_BASE + offset] = _pat(desc.param_base + 2 + offset)
|
||||
|
||||
|
||||
def _gather_byte(desc: Descriptor, mem: bytearray, oy: int, ox: int, g: int) -> int:
|
||||
"""Gather byte at reduction index g of output position (oy, ox): the P3
|
||||
transport-content rule. Reduction order kh, kw, cin; GAP rasters H*W."""
|
||||
cin = desc.cin
|
||||
kp, ci = divmod(g, cin)
|
||||
if desc.opcode == 7: # GLOBAL_AVERAGE_POOL: whole-input raster
|
||||
iy, ix = divmod(kp, desc.w)
|
||||
else:
|
||||
kh, kw = divmod(kp, desc.kw)
|
||||
iy = oy * desc.sh + kh - desc.pad_top
|
||||
ix = ox * desc.sw + kw - desc.pad_left
|
||||
if 0 <= iy < desc.h and 0 <= ix < desc.w:
|
||||
return mem[desc.input0_base - MEM_BASE + iy * desc.input0_row_bytes + ix * cin + ci]
|
||||
return desc.input0_zero & 0xFF
|
||||
|
||||
|
||||
def _golden_window(descs: list[Descriptor], mem: bytearray) -> tuple[int, bytes]:
|
||||
"""Byte-exact output window content after a full run of the job."""
|
||||
lo = min(d.output_base for d in descs)
|
||||
hi = max(d.output_base + (d.oh - 1) * d.output_row_bytes + d.ow * d.cout for d in descs)
|
||||
window = bytearray(mem[lo - MEM_BASE : hi - MEM_BASE])
|
||||
for desc in descs:
|
||||
if desc.opcode == 7:
|
||||
kp_count = desc.h * desc.w
|
||||
elif desc.opcode in (1, 2, 5, 6):
|
||||
kp_count = desc.kh * desc.kw
|
||||
else:
|
||||
kp_count = 1
|
||||
gather_len = kp_count * desc.cin
|
||||
for oy in range(desc.oh):
|
||||
for ox in range(desc.ow):
|
||||
for channel in range(desc.cout):
|
||||
g = channel % gather_len
|
||||
byte = _gather_byte(desc, mem, oy, ox, g)
|
||||
window[desc.output_base - lo +
|
||||
oy * desc.output_row_bytes + ox * desc.cout + channel] = byte
|
||||
return lo, bytes(window)
|
||||
|
||||
|
||||
def _core_case(tmp_path: Path, name: str, descs: list[Descriptor], scen: int,
|
||||
timeout: int = 20_000_000, rderr_addr: int = 0xFFFFFFFF, rderr_code: int = 0,
|
||||
werr_addr: int = 0xFFFFFFFF, werr_code: int = 0, exp_faddr: int = 0,
|
||||
wchk: bool = False) -> list[str]:
|
||||
for desc in descs:
|
||||
validate_descriptor(desc, descriptor_region=(MEM_BASE, len(descs) * DESCRIPTOR_BYTES))
|
||||
mem = bytearray([GAP_FILL] * CORE_MEM_BYTES)
|
||||
for index, desc in enumerate(descs):
|
||||
words = _words_of(desc)
|
||||
struct.pack_into(f"<{len(words)}I", mem, MEM_BASE - MEM_BASE + index * 128, *words)
|
||||
_fill_descriptor_content(mem, desc)
|
||||
image = tmp_path / f"core_{name}.hex"
|
||||
_image_hex(image, list(struct.unpack(f"<{CORE_MEM_BYTES // 4}I", bytes(mem))))
|
||||
gold_base, gold = _golden_window(descs, mem)
|
||||
golden = tmp_path / f"core_{name}_gold.hex"
|
||||
pad = (-len(gold)) % 4
|
||||
_image_hex(golden, list(struct.unpack(f"<{(len(gold) + pad) // 4}I", bytes(gold) +
|
||||
bytes(pad))))
|
||||
first = descs[0]
|
||||
return [
|
||||
f"+IMG={image}",
|
||||
f"+GOLD={golden}",
|
||||
f"+BASE={MEM_BASE:x}",
|
||||
f"+COUNT={len(descs)}",
|
||||
"+JOBID=1a2b3c4d",
|
||||
f"+TIMEOUT={timeout}",
|
||||
f"+SCEN={scen}",
|
||||
f"+GOLD_BASE={gold_base:x}",
|
||||
f"+GOLD_BYTES={len(gold)}",
|
||||
f"+RDERR_ADDR={rderr_addr:x}",
|
||||
f"+RDERR_CODE={rderr_code}",
|
||||
f"+WERR_ADDR={werr_addr:x}",
|
||||
f"+WERR_CODE={werr_code}",
|
||||
f"+EXP_FADDR={exp_faddr:x}",
|
||||
f"+WCHK={1 if wchk else 0}",
|
||||
f"+WCHK_BASE={first.weight_base:x}",
|
||||
f"+WCHK_BYTES={first.weight_bytes}",
|
||||
f"+NAME={name}",
|
||||
]
|
||||
|
||||
|
||||
def _conv_kws_shape(slot: int) -> Descriptor:
|
||||
"""KWS conv1 shape: 10x4 kernel, stride 2, Cin=1, reduced geometry."""
|
||||
b = _slot_bases(slot)
|
||||
d = Descriptor()
|
||||
d.version_opcode = 0x0100_0000 | 1
|
||||
d.act_min = -128
|
||||
d.act_max = 127
|
||||
h, w, cin, cout = 13, 8, 1, 8
|
||||
kh, kw, sh, sw = 10, 4, 2, 2
|
||||
oh = (h - kh) // sh + 1
|
||||
ow = (w - kw) // sw + 1
|
||||
d.input_hw = (w << 16) | h
|
||||
d.channels = (cout << 16) | cin
|
||||
d.output_hw = (ow << 16) | oh
|
||||
d.input0_base = b["in0"]
|
||||
d.output_base = b["out"]
|
||||
d.input0_row_bytes = w * cin
|
||||
d.output_row_bytes = ow * cout
|
||||
d.kernel_stride = (sw << 24) | (sh << 16) | (kw << 8) | kh
|
||||
d.padding = 0
|
||||
d.tile_hw = (2 << 8) | 2
|
||||
d.k_slice = 40
|
||||
d.input0_bytes = (h - 1) * d.input0_row_bytes + w * cin
|
||||
d.output_bytes = (oh - 1) * d.output_row_bytes + ow * cout
|
||||
d.weight_base = b["w"]
|
||||
d.weight_bytes = ((cout + 7) // 8) * kh * kw * cin * 8
|
||||
d.param_base = b["p"]
|
||||
d.param_bytes = cout * 16
|
||||
validate_descriptor(d, descriptor_region=(MEM_BASE, 4 * DESCRIPTOR_BYTES))
|
||||
return d
|
||||
|
||||
|
||||
def _clamp_strided(slot: int) -> Descriptor:
|
||||
d = _valid_descriptor(8, slot)
|
||||
d.input0_row_bytes = d.w * d.cin + 7
|
||||
d.output_row_bytes = d.ow * d.cout + 11
|
||||
d.input0_bytes = (d.h - 1) * d.input0_row_bytes + d.w * d.cin
|
||||
d.output_bytes = (d.oh - 1) * d.output_row_bytes + d.ow * d.cout
|
||||
validate_descriptor(d, descriptor_region=(MEM_BASE, 4 * DESCRIPTOR_BYTES))
|
||||
return d
|
||||
|
||||
|
||||
def _core_cases(tmp_path: Path) -> list[list[str]]:
|
||||
cases: list[list[str]] = []
|
||||
# directed transport jobs, one invocation per shape
|
||||
cases.append(_core_case(tmp_path, "clamp_small", [_valid_descriptor(8, 0)], 0))
|
||||
cases.append(_core_case(tmp_path, "clamp_strided", [_clamp_strided(0)], 0))
|
||||
cases.append(_core_case(tmp_path, "conv_kws_shape", [_conv_kws_shape(0)], 0, wchk=True))
|
||||
cases.append(_core_case(tmp_path, "dw33", [_valid_descriptor(2, 0)], 0))
|
||||
cases.append(_core_case(tmp_path, "maxpool", [_valid_descriptor(5, 0)], 0))
|
||||
cases.append(_core_case(tmp_path, "avgpool", [_valid_descriptor(6, 0)], 0))
|
||||
cases.append(_core_case(tmp_path, "gap", [_valid_descriptor(7, 0)], 0))
|
||||
cases.append(_core_case(tmp_path, "fc", [_valid_descriptor(3, 0)], 0))
|
||||
cases.append(_core_case(tmp_path, "add", [_valid_descriptor(4, 0)], 0))
|
||||
cases.append(_core_case(
|
||||
tmp_path, "multi3",
|
||||
[_valid_descriptor(8, 0), _conv_kws_shape(1), _clamp_strided(2)], 0))
|
||||
# lifecycle scenarios use a two-descriptor job
|
||||
pair = [_valid_descriptor(8, 0), _clamp_strided(1)]
|
||||
cases.append(_core_case(tmp_path, "b_order", pair, 1))
|
||||
cases.append(_core_case(tmp_path, "watchdog", [_valid_descriptor(8, 0)], 2,
|
||||
timeout=2000))
|
||||
cases.append(_core_case(tmp_path, "abort", pair, 3))
|
||||
cases.append(_core_case(tmp_path, "quiesce", pair, 4))
|
||||
cases.append(_core_case(tmp_path, "reset", pair, 5))
|
||||
cases.append(_core_case(tmp_path, "flush", pair, 6))
|
||||
cases.append(_core_case(
|
||||
tmp_path, "snapshot_race",
|
||||
[_valid_descriptor(8, 0), _conv_kws_shape(1), _clamp_strided(2)], 7))
|
||||
# write fault: error on the first store burst of descriptor 0
|
||||
fault_job = _valid_descriptor(8, 0)
|
||||
burst_base = fault_job.output_base & ~7
|
||||
span_beats = ((fault_job.output_base & 7) + fault_job.cout + 7) >> 3
|
||||
beats_4k = (4096 - (burst_base & 0xFFF)) >> 3
|
||||
beats = min(16, span_beats, beats_4k)
|
||||
cases.append(_core_case(tmp_path, "write_fault", [fault_job], 8,
|
||||
werr_addr=burst_base, werr_code=2,
|
||||
exp_faddr=burst_base + (beats - 1) * 8))
|
||||
# fetch fault: SLVERR on descriptor 0's first beat
|
||||
cases.append(_core_case(tmp_path, "read_fault", [_valid_descriptor(8, 0)], 9,
|
||||
rderr_addr=MEM_BASE, rderr_code=2, exp_faddr=MEM_BASE))
|
||||
# administrative pause mid-job with a short job watchdog timeout: the
|
||||
# pause must be excluded from the watchdog and the job must resume whole
|
||||
cases.append(_core_case(tmp_path, "pause", [_conv_kws_shape(0)], 10, timeout=2000))
|
||||
return cases
|
||||
|
||||
|
||||
@pytest.mark.parametrize("simulator", ("iverilog", "verilator"))
|
||||
def test_npu_core(tmp_path: Path, simulator: str) -> None:
|
||||
tools = _tools()
|
||||
command = _build_sim(
|
||||
tools,
|
||||
simulator,
|
||||
tmp_path,
|
||||
f"npu_core_{simulator}",
|
||||
"npu_core_tb",
|
||||
ROOT / "tests/rtl/npu_core_tb.sv",
|
||||
CORE_SOURCES,
|
||||
)
|
||||
for plusargs in _core_cases(tmp_path):
|
||||
_run(command, plusargs, "NPU core test passed")
|
||||
@@ -27,6 +27,13 @@ COMMON_SOURCES = [
|
||||
COMMON / "cdc/async_reqack.sv",
|
||||
]
|
||||
SHELL_SOURCES = [
|
||||
MULTIMEDIA / "npu_pkg.sv",
|
||||
ROOT / "rtl/tech/tc_sram.sv",
|
||||
MULTIMEDIA / "npu_local_sram.sv",
|
||||
MULTIMEDIA / "npu_patch_packer.sv",
|
||||
MULTIMEDIA / "npu_dma.sv",
|
||||
MULTIMEDIA / "npu_job_decoder.sv",
|
||||
MULTIMEDIA / "npu_scheduler.sv",
|
||||
MULTIMEDIA / "npu_reg.sv",
|
||||
MULTIMEDIA / "npu_control_cdc.sv",
|
||||
MULTIMEDIA / "npu_core.sv",
|
||||
|
||||
@@ -335,6 +335,7 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non
|
||||
apu_loader_filelist = tmp_path / "apu_loader.fl"
|
||||
apu_sequencer_filelist = tmp_path / "apu_sequencer.fl"
|
||||
gateway_a_filelist = tmp_path / "gateway_a.fl"
|
||||
npu_dma_filelist = tmp_path / "npu_dma.fl"
|
||||
assert generate_formal_filelist("bus", bus_filelist, memory_map, topology, user_extensions)
|
||||
assert generate_formal_filelist(
|
||||
"rib_adapter", rib_adapter_filelist, memory_map, topology, user_extensions
|
||||
@@ -374,6 +375,9 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non
|
||||
assert generate_formal_filelist(
|
||||
"gateway_a", gateway_a_filelist, memory_map, topology, user_extensions
|
||||
)
|
||||
assert generate_formal_filelist(
|
||||
"npu_dma", npu_dma_filelist, memory_map, topology, user_extensions
|
||||
)
|
||||
|
||||
bus = parse_filelists([bus_filelist], require_files=False)
|
||||
rib_adapter = parse_filelists([rib_adapter_filelist], require_files=False)
|
||||
@@ -392,6 +396,7 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non
|
||||
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)
|
||||
npu_dma = parse_filelists([npu_dma_filelist], require_files=False)
|
||||
assert "+define+SV_ASSRT_DISABLE" in bus.defines
|
||||
assert "+define+PDK_BEHAV" in onchip_ram.defines
|
||||
assert "+define+SYNTHESIS" in onchip_ram.defines
|
||||
@@ -483,6 +488,10 @@ def test_formal_filelists_are_scoped_to_the_protocol_duts(tmp_path: Path) -> Non
|
||||
ROOT / "rtl/managed/clusterip/common/rtl/stream/round_robin_arbiter.sv" in gateway_a.files
|
||||
)
|
||||
assert ROOT / "rtl/mini/formal/gateway_a_formal.sv" in gateway_a.files
|
||||
assert "+define+SV_ASSRT_DISABLE" in npu_dma.defines
|
||||
assert ROOT / "rtl/ip/multimedia/npu_dma.sv" in npu_dma.files
|
||||
assert ROOT / "rtl/managed/clusterip/common/rtl/interface/axi4_if.sv" in npu_dma.files
|
||||
assert ROOT / "rtl/mini/formal/npu_dma_formal.sv" in npu_dma.files
|
||||
|
||||
|
||||
def test_opipsram_formal_keeps_full_depth_with_hosted_runner_budget() -> None:
|
||||
@@ -548,6 +557,49 @@ def test_ga2d_formal_target_uses_the_p5_production_hierarchy() -> None:
|
||||
assert "FormatA8" in properties
|
||||
|
||||
|
||||
def test_npu_dma_formal_target_pins_the_bounded_axi4_contract() -> None:
|
||||
formal_makefile = (ROOT / "rtl/mini/mk/formal.mk").read_text(encoding="utf-8")
|
||||
filelist_generator = (
|
||||
ROOT / "rtl/mini/formal/generate_formal_filelist.py"
|
||||
).read_text(encoding="utf-8")
|
||||
design = (ROOT / "rtl/mini/formal/npu_dma_formal.sv").read_text(encoding="utf-8")
|
||||
properties = (ROOT / "rtl/mini/formal/npu_dma_formal_props.sv").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
|
||||
assert re.search(r"^FORMAL_NPU_DMA_DEPTH\s+\?= 16$", formal_makefile, re.MULTILINE)
|
||||
assert re.search(r"^FORMAL_NPU_DMA_COVER_DEPTH\s+\?= 32$", formal_makefile, re.MULTILINE)
|
||||
assert re.search(r"^FORMAL_NPU_DMA_TIMEOUT\s+\?= 7200$", formal_makefile, re.MULTILINE)
|
||||
assert re.search(r"^FORMAL_TARGETS\s+:=.* npu_dma$", formal_makefile, re.MULTILINE)
|
||||
assert "formal-npu_dma: $(FORMAL_DIR)/npu_dma/.stamp | manifest" in formal_makefile
|
||||
assert 'if target == "npu_dma":' in filelist_generator
|
||||
for source in ("npu_dma.sv", "npu_dma_formal.sv"):
|
||||
assert source in filelist_generator
|
||||
assert "npu_dma u_dut" in design
|
||||
assert "(* anyconst *)logic [ 1:0] f_read_fault;" in design
|
||||
assert "(* anyconst *)logic [ 3:0] f_block_start;" in design
|
||||
assert "(* anyseq *)logic f_write_last;" in design
|
||||
assert "s_r_proto_rlast_q" in design
|
||||
assert "s_w_bid_bad_q" in design
|
||||
assert "npu_dma_formal_design u_design" in properties
|
||||
assert "assert (arlen <= 8'd15);" in properties
|
||||
assert "assert (awlen <= 8'd15);" in properties
|
||||
assert "assert (awsize == 3'd3);" in properties
|
||||
assert "assert ((arsize == 3'd3) || (arlen == 8'd0));" in properties
|
||||
assert "assert (araddr == f_rd_next_burst_q);" in properties
|
||||
assert "assert ({4'd0, wfifo_count} == ({1'b0, awlen} + 9'd1));" in properties
|
||||
assert "assert (!f_rd_outstanding_q);" in properties
|
||||
assert "assert (f_wr_aw_q);" in properties
|
||||
assert "assert (fault_code == 4'd6);" in properties
|
||||
assert "assert (fault_code == 4'd4);" in properties
|
||||
assert "assert (fault_code == 4'd5);" in properties
|
||||
assert "assume (read_bytes <= 32'd128);" in properties
|
||||
assert "cover (pause_ack && arvalid && !arready);" in properties
|
||||
assert "cover (clear && f_rd_outstanding_q);" in properties
|
||||
assert "cover (fault && (fault_code == 4'd6));" in properties
|
||||
assert "cover (write_done);" in properties
|
||||
|
||||
|
||||
def test_sysctrl_formal_properties_use_exported_user_core_shape() -> None:
|
||||
design = (ROOT / "rtl/mini/formal/sysctrl_formal.sv").read_text(encoding="utf-8")
|
||||
properties = (ROOT / "rtl/mini/formal/sysctrl_formal_props.sv").read_text(encoding="utf-8")
|
||||
|
||||
Reference in new issue
Block a user