Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions Bender.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package:

dependencies:
tech_cells_generic: { git: "https://github.com/pulp-platform/tech_cells_generic.git", version: 0.2.2 }
common_cells: { git: "https://github.com/pulp-platform/common_cells.git", version: 1.39 }

sources:
- include_dirs:
Expand All @@ -17,13 +18,14 @@ sources:
- rtl/hwpe_ctrl_package.sv
- rtl/hwpe_ctrl_interfaces.sv
# Level 1
- rtl/hwpe_ctrl_regfile_ff.sv
- rtl/hwpe_ctrl_regfile_latch.sv
- rtl/deprecated/hwpe_ctrl_regfile_ff.sv
- rtl/deprecated/hwpe_ctrl_regfile_latch.sv
- rtl/hwpe_ctrl_seq_mult.sv
- rtl/hwpe_ctrl_uloop.sv
- rtl/hwpe_ctrl_target.sv
# Level 2
- rtl/hwpe_ctrl_regfile_latch_test_wrap.sv
- rtl/deprecated/hwpe_ctrl_regfile_latch_test_wrap.sv
# Level 3
- rtl/hwpe_ctrl_regfile.sv
- rtl/deprecated/hwpe_ctrl_regfile.sv
# Level 4
- rtl/hwpe_ctrl_slave.sv
- rtl/deprecated/hwpe_ctrl_slave.sv
File renamed without changes.
File renamed without changes.
File renamed without changes.
128 changes: 128 additions & 0 deletions rtl/hwpe_ctrl_regif_example.rdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* hwpe_ctrl_regif_example.rdl
* Francesco Conti <f.conti@unibo.it>
*
* Copyright (C) 2025 ETH Zurich, University of Bologna
* Copyright and related rights are licensed under the Solderpad Hardware
* License, Version 0.51 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
* or agreed to in writing, software, hardware and materials distributed under
* this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/

/*
* This file contains a sample address map for a HWPE with mandatory registers
* (not to be changed) and sample job-independent and job-dependent registers.
*/

addrmap hwpe_ctrl_regif_example {
name = "HWPE Control port";
desc = "Control register map for the HWPE, including mandatory control/status registers and example job-independent and job-dependent configuration registers.";

// Mandatory COMMIT_TRIGGER register. Not to be updated inside HWPEs.
reg hwpe_commit_trigger {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove the hwpe_ prefix here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I will fix this.

field {
name = "reserved";
desc = "Reserved.";
hw = r;
sw = r;
} r0[31:2] = 0;
field {
name = "commit_trigger";
desc = "Write 0 to commit job & start execution, unlock controller; write `0x1` value to commit job & unlock controller without starting execution, which will be started when the next job is committed and triggered; write `0x2` value to trigger the current job queue without committing any new job.";
hw = r;
sw = w;
swacc = true;
} commit_trigger[1:0] = 0;
};
// Mandatory ACQUIRE register. Not to be updated inside HWPEs.
reg hwpe_acquire {
field {
name = "acquire";
desc = "On read starts a job offload, locks controller. Returns job ID.";
hw = w;
sw = r;
swacc = true;
} acquire[31:0] = 0;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit annoying in SystemRDL that you need to create a field container in reg, even if it uses the full regwidth. Because then accessing it requires hwpe_acquire.acquire, which feels a bit redundant. One way to (slightly) improve this is to call the the reg by its name i.e. acquire and then the field just value or val.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I will do that.

};
// Mandatory RESERVED register. Not to be updated inside HWPEs.
reg hwpe_reserved {
field {
name = "reserved";
desc = "Reserved.";
hw = r;
sw = r;
} reserved[31:0] = 0;
};
// Mandatory STATUS register. Not to be updated inside HWPEs.
reg hwpe_status {
field {
name = "status";
desc = "Status of currently running job.";
hw = w;
sw = r;
} status0[31:0] = 0;
};
// Mandatory RUNNING_JOB register. Not to be updated inside HWPEs.
reg hwpe_running_job {
field {
name = "reserved";
desc = "Reserved.";
hw = r;
sw = r;
} r0[31:8] = 0;
field {
name = "running_job";
desc = "Returns ID of currently running job if any job is running; otherwise, of the last job that has been run.";
hw = w;
sw = r;
} running_job[7:0] = 0;
};
// Mandatory SOFT_CLEAR register. Not to be updated inside HWPEs.
reg hwpe_soft_clear {
field {
name = "reserved";
desc = "Reserved.";
hw = r;
sw = r;
} r0[31:2] = 0;
field {
name = "soft_clear";
desc = "Write `0x0` to clear the full status of the accelerator IP, including the register file; write `0x1` to clear the status of the accelerator IP, except for the register file; write `0x2` to clear only the register file.";
hw = r;
sw = w;
swacc = true;
} soft_clear[1:0] = 0;
};

// "mandatory" set of HWPE registers (CONTROL regs). Not to be updated inside HWPEs.
regfile hwpe_ctrl_mandatory {
hwpe_commit_trigger commit_trigger @ 0x00;
hwpe_acquire acquire @ 0x04;
hwpe_reserved reserved0 @ 0x08;
hwpe_status status @ 0x0c;
hwpe_running_job running_job @ 0x10;
hwpe_soft_clear soft_clear @ 0x14;
hwpe_reserved reserved1 @ 0x18;
hwpe_reserved reserved2 @ 0x1c;
};

// "generic" set of HWPE registers. Update inside HWPEs.
regfile hwpe_ctrl_job_indep {
hwpe_reserved rr;
};

// "job-dependent" set of HWPE registers. Update inside HWPEs.
regfile hwpe_ctrl_job_dep {
hwpe_reserved rr;
};

// HWPE control address map. Update inside HWPEs
hwpe_ctrl_mandatory hwpe_ctrl @ 0x00;
hwpe_ctrl_job_indep hwpe_generic @ 0x20;
hwpe_ctrl_job_dep hwpe_job_dep @ 0x40;

};
235 changes: 235 additions & 0 deletions rtl/hwpe_ctrl_target.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
/*
* hwpe_ctrl_target.sv
* Francesco Conti <f.conti@unibo.it>
*
* Copyright (C) 2014-2025 ETH Zurich, University of Bologna
* Copyright and related rights are licensed under the Solderpad Hardware
* License, Version 0.51 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
* or agreed to in writing, software, hardware and materials distributed under
* this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/

/*
* This module exposes a hwpe_ctrl_intf_periph target (slave) port like the
* deprecated `hwpe_ctrl_slave`. However, it differs from the previous module
* in that it exploits SystemRDL to generate a register file interface.
* This module must be coupled with a register interface generated out of
* a SystemRDL description. This repository contains a reference SystemRDL
* `hwpe_ctrl_regif_example.rdl` and a `rdl.sh` script to generate the register
* interface with PeakRDL. HWPEs must internalize this and modify the content to
* align with their required set of job-independent and job-dependent registers,
* without modifying the mandatory registers.
* Then, the `hwpe_ctrl_target` can be integrated by 1) overriding the parametric
* types; 2) plugging the OBI interface signals; 3) plugging `hwif_in` and
* `hwif_out`.
*/

module hwpe_ctrl_target
import hwpe_ctrl_package::*;
#(
parameter int unsigned NB_CONTEXT = 2,
parameter int unsigned NB_CLEAR_CYCLES = 3,
parameter int unsigned ID_WIDTH = 2,
parameter int unsigned ADDR_WIDTH = 16,
parameter type hwpe_ctrl_regif_in_t = logic, // must be overridden!
parameter type hwpe_ctrl_regif_out_t = logic, // must be overridden!
parameter type hwpe_ctrl_job_indep_t = logic, // must be overridden!
parameter type hwpe_ctrl_job_dep_t = logic // must be overridden!
)
(
input logic clk_i,
input logic rst_ni,
output logic clear_o,

// peripheral interconnect side
hwpe_ctrl_intf_periph.slave target,

// job triggering completion & status
output logic job_trigger_o,
input logic job_done_i,
input logic [31:0] job_status_i,

// job-independent registers
output hwpe_ctrl_job_indep_t job_indep_regs_o,

// job-dependent registers
output logic job_dep_regs_valid_o,
output hwpe_ctrl_job_dep_t job_dep_regs_o,

// OBI interface to target SystemRDL-generated register interface
output logic target_obi_req_o,
input logic target_obi_gnt_i,
output logic [31:0] target_obi_addr_o,
output logic target_obi_we_o,
output logic [3:0] target_obi_be_o,
output logic [31:0] target_obi_wdata_o,
output logic [ID_WIDTH-1:0] target_obi_aid_o,
input logic target_obi_rvalid_i,
output logic target_obi_rready_o,
input logic [31:0] target_obi_rdata_i,
input logic target_obi_err_i,
input logic [ID_WIDTH-1:0] target_obi_rid_i,

// wrap -> register interface signals
output hwpe_ctrl_regif_in_t hwif_in,

// register interface -> wrap signals
input hwpe_ctrl_regif_out_t hwif_out
);

// unroll periph interconnect signals into OBI
assign target_obi_req_o = target.req;
assign target.gnt = target_obi_gnt_i;
assign target_obi_addr_o = {{(32-ADDR_WIDTH){1'b0}} , target.add[ADDR_WIDTH-1:0]};
assign target_obi_we_o = ~target.wen;
assign target_obi_be_o = target.be;
assign target_obi_wdata_o = target.data;
assign target_obi_aid_o = target.id;
assign target.r_data = target_obi_rdata_i;
assign target.r_valid = target_obi_rvalid_i;
assign target.r_id = target_obi_rid_i;
assign target_obi_rready_o = '1;

// error codes for job offload
localparam logic [31:0] HWPE_CTRL_JOB_QUEUE_FULL_ERR_CODE = 32'hffff_ffff;
localparam logic [31:0] HWPE_CTRL_JOB_ACQUIRED_ERR_CODE = 32'hffff_fffe;

// state of job offload procedure
typedef enum logic { IDLE, ACQUIRE } job_offload_state_t;
job_offload_state_t job_offload_state_d, job_offload_state_q;

// current and next job ID
logic [7:0] job_id_d, job_id_q;

// job commit signal
logic job_commit;

// job queue control signals
logic job_fifo_full, job_fifo_empty;

// internal clear signals
logic soft_clear_regfile_d, soft_clear_state_d;
logic [NB_CLEAR_CYCLES-1:0] soft_clear_regfile_q, soft_clear_state_q;
logic soft_clear_regfile_en, soft_clear_state_en;

// SOFT_CLEAR register:
// clear regfile if SOFT_CLEAR[0] is 0, clear state if SOFT_CLEAR[1] is 0
assign soft_clear_regfile_d = hwif_out.hwpe_ctrl.soft_clear.soft_clear.swacc & ~hwif_out.hwpe_ctrl.soft_clear.soft_clear.value[0];
assign soft_clear_state_d = hwif_out.hwpe_ctrl.soft_clear.soft_clear.swacc & ~hwif_out.hwpe_ctrl.soft_clear.soft_clear.value[1];

// generate clear enables
assign soft_clear_regfile_en = soft_clear_regfile_d | (|soft_clear_regfile_q);
assign soft_clear_state_en = soft_clear_state_d | (|soft_clear_state_q);

// activate clear for NB_CLEAR_CYCLES cycles in case of a SOFT_CLEAR write
always_ff @(posedge clk_i or negedge rst_ni)
begin
if(~rst_ni) begin
soft_clear_regfile_q <= '0;
end
else if(soft_clear_regfile_en) begin
soft_clear_regfile_q[0] <= soft_clear_regfile_d;
for(int i=1; i<NB_CLEAR_CYCLES; i++) begin
soft_clear_regfile_q[i] <= soft_clear_regfile_q[i-1];
end
end
end
always_ff @(posedge clk_i or negedge rst_ni)
begin
if(~rst_ni) begin
soft_clear_state_q <= '0;
end
else if(soft_clear_state_en) begin
soft_clear_state_q[0] <= soft_clear_state_d;
for(int i=1; i<NB_CLEAR_CYCLES; i++) begin
soft_clear_state_q[i] <= soft_clear_state_q[i-1];
end
end
end

// propagate the soft_clear_state everywhere
assign clear_o = |soft_clear_state_q;

// COMMIT_TRIGGER register:
// Commit a job in the job queue if COMMIT_TRIGGER[0] is 1'b0. Trigger the job queue execution if COMMIT_TRIGGER[1] is 1'b0.
assign job_commit = hwif_out.hwpe_ctrl.commit_trigger.commit_trigger.swacc & ~hwif_out.hwpe_ctrl.commit_trigger.commit_trigger.value[0];
assign job_trigger_o = hwif_out.hwpe_ctrl.commit_trigger.commit_trigger.swacc & ~hwif_out.hwpe_ctrl.commit_trigger.commit_trigger.value[1];

// ACQUIRE register:
// 1. if in ACQUIRE state, respond to any new reads with error code (-2)
// 2. if job_fifo_full, respond with error code (-1)
// 3. else, return the next job ID
assign hwif_in.hwpe_ctrl.acquire.acquire.next = job_offload_state_q == ACQUIRE ? HWPE_CTRL_JOB_ACQUIRED_ERR_CODE :
job_fifo_full ? HWPE_CTRL_JOB_QUEUE_FULL_ERR_CODE :
{ 24'h0 , job_id_d };
always_ff @(posedge clk_i or negedge rst_ni)
begin
if(~rst_ni) begin
job_id_q <= '1;
end
else if(|soft_clear_regfile_q) begin
job_id_q <= '1;
end
else if(job_done_i) begin
job_id_q <= job_id_d;
end
end
assign job_id_d = job_id_q + 1;

// enable state change on ACQUIRE / COMMIT_TRIGGER register access
logic job_offload_state_en;
assign job_offload_state_en = hwif_out.hwpe_ctrl.acquire.acquire.swacc | job_commit;

// update state (cleared together with register file)
assign job_offload_state_d = hwif_out.hwpe_ctrl.acquire.acquire.swacc & ~job_fifo_full ? ACQUIRE :
job_commit ? IDLE :
job_offload_state_q;
always_ff @(posedge clk_i or negedge rst_ni)
begin
if(~rst_ni) begin
job_offload_state_q <= IDLE;
end
else if(|soft_clear_regfile_q) begin
job_offload_state_q <= IDLE;
end
else if(job_offload_state_en) begin
job_offload_state_q <= job_offload_state_d;
end
end

// STATUS register:
// propagate status defined externally
assign hwif_in.hwpe_ctrl.status.status0.next = job_status_i;

// RUNNING_JOB register:
// return the current job ID
assign hwif_in.hwpe_ctrl.running_job.running_job.next = job_id_q;

// queue for incoming jobs
fifo_v3 #(
.FALL_THROUGH ( 0 ),
.DEPTH ( NB_CONTEXT ),
.dtype ( hwpe_ctrl_job_dep_t )
) i_job_fifo (
.clk_i ( clk_i ),
.rst_ni ( rst_ni ),
.flush_i ( |soft_clear_regfile_q ),
.testmode_i ( '0 ),
.full_o ( job_fifo_full ),
.empty_o ( job_fifo_empty ),
.usage_o ( ),
.data_i ( hwif_out.hwpe_job_dep ),
.push_i ( job_commit ),
.data_o ( job_dep_regs_o ),
.pop_i ( job_done_i )
);
assign job_dep_regs_valid_o = ~job_fifo_empty;

// job-independent registers
assign job_indep_regs_o = hwif_out.hwpe_job_indep;

endmodule // hwpe_ctrl_target
6 changes: 6 additions & 0 deletions rtl/rdl.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash
peakrdl regblock hwpe_ctrl_regif_example.rdl -o rdl-example/ --cpuif obi-flat --default-reset arst_n --hwif-report --addr-width 32
peakrdl html hwpe_ctrl_regif_example.rdl -o rdl-example/html/
peakrdl c-header hwpe_ctrl_regif_example.rdl -o rdl-example/hwpe_ctrl_target.h
# PeakRDL uses unpacked structs to avoid issues at compile time, which is commendable, but incompatible with FIFOing the output of the job!
sed -i 's/typedef[[:space:]]\+struct\b/typedef struct packed/g' rdl-example/hwpe_ctrl_regif_example_pkg.sv