Skip to content

Commit 34584e8

Browse files
committed
test: improve testbench
1 parent eb2a2e4 commit 34584e8

4 files changed

Lines changed: 344 additions & 3 deletions

File tree

Bender.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ sources:
307307
- test/hw/cva6tb_pkg.sv
308308
- test/hw/bootrom/cva6tb_bootrom.sv
309309
- test/hw/cva6tb_clk_rst_gen.sv
310+
- test/hw/cva6tb_hwmon.sv
310311
- test/hw/cva6tb_regs.sv
311312
- test/hw/cva6tb_rtc_timer.sv
312313
- test/hw/cva6tb_sim_console.sv

test/hw/cva6tb_hwmon.sv

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
package cva6tb_hwmon_pkg;
2+
3+
// Internal address width; 5 bits covers offsets 0x00–0x10.
4+
localparam int unsigned IntAddrWidth = 5;
5+
6+
// Register map (write-only):
7+
// RELEASE 0x00 wdata = task_id → record release cycle, reset per-task state,
8+
// compute deadline = time_i + period_q[task_id]
9+
// PERIOD_LO 0x04 wdata[31:0] → stage low 32 bits of task period (init only)
10+
// PERIOD_HI 0x08 wdata[31:0] → commit {HI,LO} as period for last RELEASE'd task
11+
// START 0x0C wdata = task_id → open measurement window; handles preemption
12+
// STOP 0x10 wdata = task_id → close window, flush CSV row
13+
localparam logic [IntAddrWidth-1:0] CVA6TB_HWMON_RELEASE_OFFSET = 5'h00;
14+
localparam logic [IntAddrWidth-1:0] CVA6TB_HWMON_PERIOD_LO_OFFSET = 5'h04;
15+
localparam logic [IntAddrWidth-1:0] CVA6TB_HWMON_PERIOD_HI_OFFSET = 5'h08;
16+
localparam logic [IntAddrWidth-1:0] CVA6TB_HWMON_START_OFFSET = 5'h0C;
17+
localparam logic [IntAddrWidth-1:0] CVA6TB_HWMON_STOP_OFFSET = 5'h10;
18+
19+
localparam int unsigned NumTasks = 32;
20+
localparam int unsigned TaskIdWidth = $clog2(NumTasks);
21+
typedef logic [TaskIdWidth-1:0] task_id_t;
22+
23+
function automatic void mon_log(string msg);
24+
$display("@%t | [MON] %s", $realtime, msg);
25+
endfunction
26+
27+
endpackage
28+
29+
module cva6tb_hwmon
30+
#(
31+
parameter type reg_req_t = logic,
32+
parameter type reg_rsp_t = logic
33+
) (
34+
input logic clk_i,
35+
input logic rst_ni,
36+
input reg_req_t reg_req_i,
37+
output reg_rsp_t reg_rsp_o,
38+
input logic [63:0] time_i,
39+
// Single-cycle event pulses tapped directly from CVA6 RTL.
40+
// Each signal is high for exactly one cycle per miss event.
41+
input logic l1_icache_miss_i,
42+
input logic l1_dcache_miss_i,
43+
input logic itlb_miss_i,
44+
input logic dtlb_miss_i
45+
);
46+
47+
import cva6tb_hwmon_pkg::*;
48+
49+
// Write-only peripheral: always ready, never errors, no read data.
50+
assign reg_rsp_o.ready = 1'b1;
51+
assign reg_rsp_o.error = 1'b0;
52+
assign reg_rsp_o.rdata = '0;
53+
54+
// ---------------------------------------------------------------------------
55+
// Decode incoming bus transaction
56+
// ---------------------------------------------------------------------------
57+
logic req_write;
58+
logic [IntAddrWidth-1:0] req_addr;
59+
task_id_t req_task_id;
60+
logic [31:0] req_wdata32;
61+
62+
assign req_write = reg_req_i.valid & reg_req_i.write;
63+
assign req_addr = reg_req_i.addr[IntAddrWidth-1:0];
64+
assign req_task_id = task_id_t'(reg_req_i.wdata);
65+
assign req_wdata32 = reg_req_i.wdata[31:0];
66+
67+
// ---------------------------------------------------------------------------
68+
// Internal state
69+
// ---------------------------------------------------------------------------
70+
71+
// Free-running cycle counter.
72+
logic [63:0] cycle_q;
73+
74+
// Per-task accumulated state (indexed by task_id).
75+
logic [63:0] release_cycle_q [NumTasks]; // cycle at last RELEASE
76+
logic [63:0] deadline_q [NumTasks]; // absolute deadline for current job
77+
logic [63:0] first_start_cycle_q[NumTasks]; // cycle of first CPU grant this job
78+
logic first_start_pend_q [NumTasks]; // 1 = first CPU grant not yet seen
79+
logic [63:0] exec_time_q [NumTasks]; // accumulated CPU time (across preemptions)
80+
logic [63:0] icache_count_q [NumTasks]; // miss pulses since last RELEASE
81+
logic [63:0] dcache_count_q [NumTasks];
82+
logic [63:0] itlb_count_q [NumTasks];
83+
logic [63:0] dtlb_count_q [NumTasks];
84+
logic [31:0] preempt_count_q [NumTasks]; // preemptions this job
85+
86+
// Active window state.
87+
logic active_q; // measurement window is open
88+
task_id_t current_task_q; // task currently on CPU
89+
logic [63:0] window_start_q; // cycle_q at last START write
90+
91+
// Per-task period (written once at init via PERIOD_LO/HI).
92+
logic [63:0] period_q [NumTasks];
93+
94+
// PERIOD staging: two-phase write for 64-bit period over 32-bit bus.
95+
logic [31:0] period_lo_q;
96+
task_id_t deadline_target_q; // task whose period is being staged
97+
98+
// ---------------------------------------------------------------------------
99+
// Free-running cycle counter
100+
// ---------------------------------------------------------------------------
101+
always_ff @(posedge clk_i or negedge rst_ni) begin
102+
if (!rst_ni) cycle_q <= '0;
103+
else cycle_q <= cycle_q + 1;
104+
end
105+
106+
// ---------------------------------------------------------------------------
107+
// Per-task state machine
108+
//
109+
// RELEASE write : resets all per-task accumulators for the released task.
110+
// START write : opens a CPU window. If another task was active (preemption),
111+
// the outgoing task's partial exec time is committed first.
112+
// STOP write : finalises the CPU window; total exec_time is committed so the
113+
// logging block can read the correct final value.
114+
// Active cycles : miss pulses accumulated for current_task_q.
115+
//
116+
// Misses on the START and STOP write cycles are excluded (scheduler/ecall
117+
// overhead) because req_write suppresses the else-if accumulation branch.
118+
// ---------------------------------------------------------------------------
119+
always_ff @(posedge clk_i or negedge rst_ni) begin
120+
if (!rst_ni) begin
121+
active_q <= 1'b0;
122+
current_task_q <= '0;
123+
window_start_q <= '0;
124+
period_lo_q <= '0;
125+
deadline_target_q <= '0;
126+
for (int i = 0; i < NumTasks; i++) begin
127+
release_cycle_q[i] <= '0;
128+
deadline_q[i] <= '0;
129+
period_q[i] <= '0;
130+
first_start_cycle_q[i] <= '0;
131+
first_start_pend_q[i] <= 1'b0;
132+
exec_time_q[i] <= '0;
133+
icache_count_q[i] <= '0;
134+
dcache_count_q[i] <= '0;
135+
itlb_count_q[i] <= '0;
136+
dtlb_count_q[i] <= '0;
137+
preempt_count_q[i] <= '0;
138+
end
139+
end else if (req_write) begin
140+
case (req_addr)
141+
142+
// --- New job released: reset all per-task accumulators,
143+
// deadline auto-computed from stored period ---
144+
CVA6TB_HWMON_RELEASE_OFFSET: begin
145+
release_cycle_q[req_task_id] <= cycle_q;
146+
deadline_q[req_task_id] <= time_i + period_q[req_task_id];
147+
first_start_pend_q[req_task_id] <= 1'b1;
148+
exec_time_q[req_task_id] <= '0;
149+
icache_count_q[req_task_id] <= '0;
150+
dcache_count_q[req_task_id] <= '0;
151+
itlb_count_q[req_task_id] <= '0;
152+
dtlb_count_q[req_task_id] <= '0;
153+
preempt_count_q[req_task_id] <= '0;
154+
deadline_target_q <= req_task_id;
155+
end
156+
157+
// --- Stage low 32 bits of 64-bit period (init only) ---
158+
CVA6TB_HWMON_PERIOD_LO_OFFSET: begin
159+
period_lo_q <= req_wdata32;
160+
end
161+
162+
// --- Commit full 64-bit period for the task from the last RELEASE ---
163+
CVA6TB_HWMON_PERIOD_HI_OFFSET: begin
164+
period_q[deadline_target_q] <= {req_wdata32, period_lo_q};
165+
end
166+
167+
// --- Open measurement window; handle preemption transparently ---
168+
CVA6TB_HWMON_START_OFFSET: begin
169+
if (active_q && (current_task_q != req_task_id)) begin
170+
// Preemption: commit partial exec time for the task being kicked off.
171+
exec_time_q[current_task_q] <=
172+
exec_time_q[current_task_q] + (cycle_q - window_start_q);
173+
preempt_count_q[current_task_q] <= preempt_count_q[current_task_q] + 1;
174+
end
175+
active_q <= 1'b1;
176+
current_task_q <= req_task_id;
177+
window_start_q <= cycle_q;
178+
// Record the very first CPU grant this job (used for sched_jitter).
179+
if (first_start_pend_q[req_task_id]) begin
180+
first_start_cycle_q[req_task_id] <= cycle_q;
181+
first_start_pend_q[req_task_id] <= 1'b0;
182+
end
183+
end
184+
185+
// --- Close measurement window ---
186+
CVA6TB_HWMON_STOP_OFFSET: begin
187+
if (active_q) begin
188+
exec_time_q[current_task_q] <=
189+
exec_time_q[current_task_q] + (cycle_q - window_start_q);
190+
active_q <= 1'b0;
191+
end
192+
end
193+
194+
default: ;
195+
endcase
196+
197+
end else if (active_q) begin
198+
// Accumulate miss pulses for the task currently on CPU.
199+
icache_count_q[current_task_q] <=
200+
icache_count_q[current_task_q] + 64'(l1_icache_miss_i);
201+
dcache_count_q[current_task_q] <=
202+
dcache_count_q[current_task_q] + 64'(l1_dcache_miss_i);
203+
itlb_count_q[current_task_q] <=
204+
itlb_count_q[current_task_q] + 64'(itlb_miss_i);
205+
dtlb_count_q[current_task_q] <=
206+
dtlb_count_q[current_task_q] + 64'(dtlb_miss_i);
207+
end
208+
end
209+
210+
// ---------------------------------------------------------------------------
211+
// Combinational signals for CSV logging
212+
//
213+
// These are evaluated in the active region using pre-NBA _q values,
214+
// which are the fully accumulated totals before the STOP update fires.
215+
// exec_cycles manually adds the current window so the log sees the final total.
216+
// ---------------------------------------------------------------------------
217+
logic [63:0] log_exec_cycles;
218+
logic [63:0] log_response_time;
219+
logic [63:0] log_sched_jitter;
220+
logic log_deadline_miss;
221+
222+
assign log_exec_cycles = exec_time_q[current_task_q] + (cycle_q - window_start_q);
223+
assign log_response_time = cycle_q - release_cycle_q[current_task_q];
224+
assign log_sched_jitter = first_start_cycle_q[current_task_q] -
225+
release_cycle_q[current_task_q];
226+
assign log_deadline_miss = (time_i > deadline_q[current_task_q]) ? 1'b1 : 1'b0;
227+
228+
// ---------------------------------------------------------------------------
229+
// CSV logging
230+
//
231+
// Runs in the active region of the STOP posedge. Because the always_ff above
232+
// uses non-blocking assignments, all _q values (and the combinational signals
233+
// derived from them) reflect the pre-update totals — the correct final values
234+
// for the completed job.
235+
//
236+
// Columns:
237+
// task_id : task identifier
238+
// exec_cycles : total CPU time (excluding scheduler and ecall cycles)
239+
// response_time : STOP cycle − RELEASE cycle
240+
// sched_jitter : first START cycle − RELEASE cycle
241+
// deadline_miss : 1 if time_i at STOP > absolute deadline (in time_i ticks)
242+
// preemptions : number of times the task was preempted this job
243+
// icache_misses : I-cache miss pulses attributed to this task
244+
// dcache_misses : D-cache miss pulses
245+
// itlb_misses : ITLB miss pulses
246+
// dtlb_misses : DTLB miss pulses
247+
// ---------------------------------------------------------------------------
248+
localparam string FileName = "hwmon_log.csv";
249+
integer log_file;
250+
251+
function automatic void setup();
252+
log_file = $fopen(FileName, "w");
253+
if (log_file == 0) begin
254+
$display("ERROR: Could not open log file %s", FileName);
255+
$finish;
256+
end
257+
$fwrite(log_file, "task_id,exec_cycles,response_time,sched_jitter,deadline_miss,preemptions,icache_misses,dcache_misses,itlb_misses,dtlb_misses\n");
258+
endfunction
259+
260+
function automatic void cleanup();
261+
$fclose(log_file);
262+
endfunction
263+
264+
initial setup();
265+
266+
always @(posedge clk_i) begin
267+
if (req_write) begin
268+
case (req_addr)
269+
CVA6TB_HWMON_RELEASE_OFFSET:
270+
mon_log($sformatf("RELEASE task %0d @time=%0d, deadline=%0d",
271+
req_task_id, time_i, time_i + period_q[req_task_id]));
272+
CVA6TB_HWMON_START_OFFSET: begin
273+
if (active_q && current_task_q != req_task_id)
274+
mon_log($sformatf("START task %0d @time=%0d (preempts task=%0d)",
275+
req_task_id, time_i, current_task_q));
276+
else
277+
mon_log($sformatf("START task %0d @time=%0d",
278+
req_task_id, time_i));
279+
end
280+
CVA6TB_HWMON_STOP_OFFSET: begin
281+
if (active_q)
282+
mon_log($sformatf("STOP task %0d @time=%0d, %s",
283+
current_task_q, time_i, log_deadline_miss ? "MISS" : "ok"));
284+
end
285+
default: ;
286+
endcase
287+
end
288+
if (req_write && req_addr == CVA6TB_HWMON_STOP_OFFSET && active_q) begin
289+
$fwrite(log_file, "%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d,%0d\n",
290+
current_task_q,
291+
log_exec_cycles,
292+
log_response_time,
293+
log_sched_jitter,
294+
log_deadline_miss,
295+
preempt_count_q[current_task_q],
296+
icache_count_q[current_task_q],
297+
dcache_count_q[current_task_q],
298+
itlb_count_q[current_task_q],
299+
dtlb_count_q[current_task_q]);
300+
$fflush(log_file);
301+
end
302+
end
303+
304+
final cleanup();
305+
306+
endmodule

test/hw/cva6tb_pkg.sv

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,17 @@ package cva6tb_pkg;
4040
localparam int unsigned AxiSlvIdDram = 2;
4141
localparam int unsigned AxiSlvIdSpm = 3;
4242

43-
localparam int unsigned RegbusOutNum = 8;
44-
localparam int unsigned RegbusRulesNum = 8;
43+
localparam int unsigned RegbusOutNum = 9;
44+
localparam int unsigned RegbusRulesNum = 9;
4545
localparam int unsigned RegbusErrId = 0;
4646
localparam int unsigned RegbusBootromId = 1;
4747
localparam int unsigned RegbusClintId = 2;
4848
localparam int unsigned RegbusPlicId = 3;
4949
localparam int unsigned RegbusPCRsId = 4;
5050
localparam int unsigned RegbusConsoleId = 5;
5151
localparam int unsigned RegbusRTCTimerId = 6;
52-
localparam int unsigned RegbusClicId = 7;
52+
localparam int unsigned RegbusHwmonId = 7;
53+
localparam int unsigned RegbusClicId = 8;
5354

5455
localparam int unsigned NumClicIntIrqs = 18;
5556
localparam int unsigned NumClicExtIrqs = 256 - NumClicIntIrqs;
@@ -164,6 +165,9 @@ package cva6tb_pkg;
164165
// RTC timer region
165166
localparam reg_addr_t RTCTimerBaseAddr = 32'h1000_2000;
166167
localparam reg_addr_t RTCTimerSize = 32'h0000_1000;
168+
// HWMon region
169+
localparam reg_addr_t HwmonBaseAddr = 32'h1000_3000;
170+
localparam reg_addr_t HwmonSize = 32'h0000_1000;
167171
// CLIC region
168172
localparam reg_addr_t ClicBaseAddr = 32'h1004_0000;
169173
localparam reg_addr_t ClicSize = 32'h0003_0000;
@@ -184,6 +188,7 @@ package cva6tb_pkg;
184188
reg_addr_rule_t'{ idx: RegbusPCRsId, start_addr: PCRsBaseAddr, end_addr: (PCRsBaseAddr + PCRsSize) }, // Platform Control Registers
185189
reg_addr_rule_t'{ idx: RegbusConsoleId, start_addr: ConsoleBaseAddr, end_addr: (ConsoleBaseAddr + ConsoleSize) }, // Sim console
186190
reg_addr_rule_t'{ idx: RegbusRTCTimerId, start_addr: RTCTimerBaseAddr, end_addr: (RTCTimerBaseAddr + RTCTimerSize) }, // RTC timer
191+
reg_addr_rule_t'{ idx: RegbusHwmonId, start_addr: HwmonBaseAddr, end_addr: (HwmonBaseAddr + HwmonSize) }, // HWMonitor unit
187192
reg_addr_rule_t'{ idx: RegbusClicId, start_addr: ClicBaseAddr, end_addr: (ClicBaseAddr + ClicSize) } // CLIC
188193
};
189194

0 commit comments

Comments
 (0)