Skip to content

Commit e8cd3c2

Browse files
committed
fix(pipeline): build the out-of-order engine wherever a mode is set
PipelineConfig::apply_to_state assigned the mode field directly, so a config asking for Scoreboard or Tomasulo + ROB - `raven run --pipeline --config`, or a config imported in the TUI - got no engine, and the in-order datapath ran in its place without a word. Copying the config to the other harts did the same. All three go through set_mode now, and set_mode builds the engine even when the field was already assigned. reset_stages and redirect_pc touched only the in-order latches, so under a dynamic model a reload, a reset or a PC edit left the engine fetching where the old program was, with its old buffer and statistics. Both now reach the engine, and a reset also applies the unit counts configured since it was built. Enabling or disabling the pipeline reaches it too. With the models really running, riscv-tests passes 71/71 under Tomasulo + ROB and 69/71 under Scoreboard; the two failures (rv32uf fcvt, ldst) come from #86 and from flw/fsw not being ordered as memory accesses.
1 parent c2cc9d1 commit e8cd3c2

7 files changed

Lines changed: 142 additions & 22 deletions

File tree

‎crates/raven-engine/src/architectures/riscv32/falcon/pipeline/mod.rs‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,9 @@ impl crate::falcon::machine::JournaledPipeline for PipelineSimState {
725725
impl crate::capability::PipelineControl for PipelineSimState {
726726
fn set_enabled(&mut self, enabled: bool) {
727727
self.enabled = enabled;
728+
if let Some(engine) = &mut self.ooo {
729+
crate::capability::PipelineControl::set_enabled(engine, enabled);
730+
}
728731
}
729732

730733
/// Start a fresh run from `address`: stages, units, counters and history
@@ -782,7 +785,13 @@ impl PipelineSimState {
782785
self.exec_regions.extend_from_slice(regions);
783786
}
784787

788+
/// Follow a jump the host made. Under a dynamically scheduled model the
789+
/// engine owns the front end, so it is told too; otherwise it would go on
790+
/// fetching where it was.
785791
pub fn redirect_pc(&mut self, new_pc: u32) {
792+
if let Some(engine) = &mut self.ooo {
793+
crate::capability::PipelineControl::redirect(engine, u64::from(new_pc));
794+
}
786795
self.stages = Default::default();
787796
self.fu_bank = std::array::from_fn(|_| Vec::new());
788797
self.fu_busy = [0; 7];
@@ -805,7 +814,18 @@ impl PipelineSimState {
805814
self.class_counts = [0; InstrClass::COUNT];
806815
}
807816

817+
/// Start a fresh run from `base_pc`.
818+
///
819+
/// A dynamically scheduled engine starts over too — with the unit counts
820+
/// configured now, which may have changed since it was built — or it would
821+
/// keep the old program's front end, buffer and statistics.
808822
pub fn reset_stages(&mut self, base_pc: u32) {
823+
if let Some(engine) = &mut self.ooo {
824+
for (unit, instances) in self.fu_capacity.iter().enumerate() {
825+
engine.set_capacity(unit, (*instances).max(1) as usize);
826+
}
827+
crate::capability::PipelineControl::reset(engine, u64::from(base_pc));
828+
}
809829
self.fetch_pc = base_pc;
810830
self.stages = Default::default();
811831
self.fu_bank = std::array::from_fn(|_| Vec::new());
@@ -844,8 +864,12 @@ impl PipelineSimState {
844864
/// nothing had executed — an instruction changes the machine at commit and
845865
/// nowhere else — so refetching from the current PC loses nothing, and the
846866
/// counters start over because they describe a model.
867+
///
868+
/// This is the only way to change [`Self::mode`]: assigning the field
869+
/// directly leaves a dynamic mode with no engine, and the in-order
870+
/// datapath quietly runs in its place.
847871
pub fn set_mode(&mut self, mode: PipelineMode, timing: &PipelineTiming) {
848-
if self.mode == mode {
872+
if self.mode == mode && self.ooo.is_some() == mode.is_out_of_order() {
849873
return;
850874
}
851875
self.mode = mode;

‎src/cli/mod.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1032,7 +1032,7 @@ fn run_headless_pipeline(
10321032
capture_trace: bool,
10331033
) -> (Result<PipelineReport, String>, Vec<PipelineTraceStep>) {
10341034
let mut state = raven_engine::falcon::pipeline::PipelineSimState::new();
1035-
pcfg.apply_to_state(&mut state);
1035+
pcfg.apply_to_state(&mut state, cpi);
10361036
state.reset_stages(cpu.pc);
10371037
let mut trace_steps = Vec::new();
10381038

‎src/ui/app/runtime.rs‎

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ impl App {
44
fn copy_pipeline_config_to_hart(
55
src: &raven_engine::falcon::pipeline::PipelineSimState,
66
dst: &mut raven_engine::falcon::pipeline::PipelineSimState,
7+
timing: &CpiConfig,
78
) {
89
dst.enabled = src.enabled;
910
dst.sequential_mode = src.sequential_mode;
1011
dst.bypass = src.bypass;
1112
dst.branch_resolve = src.branch_resolve;
12-
dst.mode = src.mode;
13+
dst.fu_capacity = src.fu_capacity;
14+
// Through `set_mode`, so a dynamically scheduled model gets its engine.
15+
dst.set_mode(src.mode, timing);
1316
dst.set_predict(src.predict);
1417
dst.exec_regions = src.exec_regions.clone();
15-
dst.fu_capacity = src.fu_capacity;
1618
}
1719

1820
fn generic_pipeline_snapshot(&self, scope: &str) -> Option<PipelineResultsSnapshot> {
@@ -383,7 +385,7 @@ impl App {
383385
if let Some(p) = hart.pipeline.as_mut()
384386
&& let Some(model) = rv32_runtime(&*self.machine)
385387
{
386-
Self::copy_pipeline_config_to_hart(model.pipeline(), p);
388+
Self::copy_pipeline_config_to_hart(model.pipeline(), p, &self.session.cpi_config);
387389
p.reset_stages(hart.cpu.pc);
388390
}
389391
}
@@ -447,7 +449,7 @@ impl App {
447449
} else if let Some(p) = runtime.pipeline.as_mut()
448450
&& let Some(model) = self.rv32()
449451
{
450-
Self::copy_pipeline_config_to_hart(model.pipeline(), p);
452+
Self::copy_pipeline_config_to_hart(model.pipeline(), p, &self.session.cpi_config);
451453
p.reset_stages(runtime.cpu.pc);
452454
}
453455
self.harts.push(runtime);
@@ -511,7 +513,11 @@ impl App {
511513
.take()
512514
.unwrap_or_else(raven_engine::falcon::pipeline::PipelineSimState::new);
513515
if let Some(model) = self.rv32() {
514-
Self::copy_pipeline_config_to_hart(model.pipeline(), &mut pipeline);
516+
Self::copy_pipeline_config_to_hart(
517+
model.pipeline(),
518+
&mut pipeline,
519+
&self.session.cpi_config,
520+
);
515521
}
516522
if pipeline.fetch_pc == 0 && pipeline.cycle_count == 0 {
517523
pipeline.reset_stages(self.program_counter() as u32);
@@ -651,7 +657,11 @@ impl App {
651657
child.cpu.heap_break = self.rv32().map_or(0, |rv32| rv32.cpu().heap_break);
652658
child.prev_pc = child.cpu.pc;
653659
if let Some(p) = child.pipeline.as_mut() {
654-
Self::copy_pipeline_config_to_hart(&self.native().pipeline(), p);
660+
Self::copy_pipeline_config_to_hart(
661+
&self.native().pipeline(),
662+
p,
663+
&self.session.cpi_config,
664+
);
655665
p.reset_stages(child.cpu.pc);
656666
}
657667

@@ -742,7 +752,11 @@ impl App {
742752
child.cpu.heap_break = self.harts[core_idx].cpu.heap_break;
743753
child.prev_pc = child.cpu.pc;
744754
if let Some(p) = child.pipeline.as_mut() {
745-
Self::copy_pipeline_config_to_hart(&self.native().pipeline(), p);
755+
Self::copy_pipeline_config_to_hart(
756+
&self.native().pipeline(),
757+
p,
758+
&self.session.cpi_config,
759+
);
746760
p.reset_stages(child.cpu.pc);
747761
}
748762

‎src/ui/input/keyboard/serialization.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,9 @@ fn apply_config_v3(app: &mut App, cfg: ConfigV3) {
298298
.tlb
299299
.reconfigure(cfg.tlb);
300300
}
301+
let timing = app.session.cpi_config.clone();
301302
if let Some(pipeline) = app.pipeline_config_mut() {
302-
cfg.pipeline.apply_to_state(pipeline);
303+
cfg.pipeline.apply_to_state(pipeline, &timing);
303304
}
304305
app.run.pipeline_view_mut().speed = cfg.pipeline.speed;
305306
// Sim settings applied last: may trigger a simulation restart.

‎src/ui/pipeline/mod.rs‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,15 @@ impl PipelineConfig {
142142
}
143143
}
144144

145-
pub fn apply_to_state(self, state: &mut PipelineSimState) {
145+
/// Configure `state` as this config says. The mode goes through
146+
/// [`PipelineSimState::set_mode`], which builds the engine a dynamically
147+
/// scheduled model needs — with the unit counts set just before it.
148+
pub fn apply_to_state(self, state: &mut PipelineSimState, timing: &PipelineTiming) {
146149
state.enabled = self.enabled;
147150
state.bypass = self.bypass;
148151
state.branch_resolve = self.branch_resolve;
149-
state.mode = self.mode;
150152
state.fu_capacity = self.fu_capacity;
153+
state.set_mode(self.mode, timing);
151154
state.set_predict(self.predict);
152155
}
153156
}

‎tests/support/ui_pipeline_mod.rs‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::{
22
BranchPredict, BranchResolve, GanttCell, GanttRow, InstrClass, PipelineBypassConfig,
3-
PipelineConfig, PipelineMode, PipelineSimState, PipelineSpeed, Stage, gantt_max_scroll,
3+
PipelineConfig, PipelineMode, PipelineSimState, PipelineSpeed, PipelineTiming, Stage,
4+
gantt_max_scroll,
45
gantt_view_rows, gantt_window_bounds, parse_pipeline_config, serialize_pipeline_config,
56
};
67
use std::collections::VecDeque;
@@ -198,3 +199,23 @@ fn gantt_max_scroll_matches_visible_history_capacity() {
198199

199200
assert_eq!(gantt_max_scroll(&state, 7), 7);
200201
}
202+
203+
#[test]
204+
fn applying_a_dynamic_mode_builds_its_engine() {
205+
// apply_to_state assigned the mode field directly, so a config file (CLI
206+
// --config, or an import in the TUI) asking for Scoreboard or Tomasulo got
207+
// the in-order datapath instead, with no warning.
208+
for mode in [PipelineMode::Scoreboard, PipelineMode::TomasuloRob] {
209+
let cfg = PipelineConfig {
210+
mode,
211+
..PipelineConfig::default()
212+
};
213+
let mut state = PipelineSimState::new();
214+
cfg.apply_to_state(&mut state, &PipelineTiming::default());
215+
assert_eq!(state.mode, mode);
216+
assert!(state.ooo.is_some(), "{mode:?} has no engine");
217+
}
218+
let mut state = PipelineSimState::new();
219+
PipelineConfig::default().apply_to_state(&mut state, &PipelineTiming::default());
220+
assert!(state.ooo.is_none());
221+
}

‎tests/support/ui_pipeline_sim.rs‎

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4102,9 +4102,6 @@ fn run_pipeline_mode_cached(
41024102
state.set_mode(mode, &cpi);
41034103
load_program_into_mem(asm, &mut cpu, &mut mem);
41044104
state.reset_stages(0);
4105-
if let Some(engine) = state.ooo.as_mut() {
4106-
crate::capability::PipelineControl::reset(engine, 0);
4107-
}
41084105
for _ in 0..4096 {
41094106
pipeline_tick(&mut state, &mut cpu, &mut mem, &cpi, console);
41104107
if state.halted || state.faulted || console.reading {
@@ -4257,9 +4254,6 @@ buf: .space 16
42574254
state.set_mode(mode, &cpi);
42584255
load_program_into_mem(asm, &mut cpu, &mut mem);
42594256
state.reset_stages(0);
4260-
if let Some(engine) = state.ooo.as_mut() {
4261-
crate::capability::PipelineControl::reset(engine, 0);
4262-
}
42634257
let mut tick = |cpu: &mut Cpu, console: &mut Console| {
42644258
for _ in 0..512 {
42654259
pipeline_tick(&mut state, cpu, &mut mem, &cpi, console);
@@ -4354,9 +4348,6 @@ fn run_user_code_under_sv32(
43544348
let mut state = PipelineSimState::new();
43554349
state.set_mode(mode, &cpi);
43564350
state.reset_stages(0);
4357-
if let Some(engine) = state.ooo.as_mut() {
4358-
crate::capability::PipelineControl::reset(engine, 0);
4359-
}
43604351
for _ in 0..4096 {
43614352
pipeline_tick(&mut state, &mut cpu, &mut mem, &cpi, &mut console);
43624353
if state.halted || state.faulted {
@@ -4412,3 +4403,69 @@ fn a_fetch_page_fault_enters_the_handler_only_when_the_program_gets_there() {
44124403
assert_eq!(cpu.mepc, 0x5000, "{mode:?}");
44134404
}
44144405
}
4406+
4407+
#[test]
4408+
fn reset_and_redirect_reach_the_out_of_order_engine() {
4409+
// reset_stages and redirect_pc used to touch only the in-order latches,
4410+
// so under Scoreboard or Tomasulo a reload, a reset or a PC edit left the
4411+
// engine fetching where the old program was.
4412+
let asm = r#"
4413+
.text
4414+
li a0, 1
4415+
li a0, 2
4416+
li a0, 3
4417+
halt
4418+
"#;
4419+
for mode in [
4420+
crate::falcon::pipeline::PipelineMode::Scoreboard,
4421+
crate::falcon::pipeline::PipelineMode::TomasuloRob,
4422+
] {
4423+
let mut console = Console::default();
4424+
let (mut cpu, mut state) = run_pipeline_mode_cached(asm, mode, &mut console);
4425+
assert!(state.halted, "{mode:?}");
4426+
let engine = |state: &PipelineSimState| {
4427+
let engine = state.ooo.as_ref().expect("a dynamic mode has an engine");
4428+
(
4429+
crate::capability::PipelineInspect::stats(engine).cycles,
4430+
engine.fetch_pc(),
4431+
)
4432+
};
4433+
assert_ne!(engine(&state).0, 0, "{mode:?}");
4434+
4435+
state.reset_stages(0);
4436+
assert_eq!(engine(&state), (0, 0), "{mode:?}: reset left the engine running");
4437+
4438+
state.redirect_pc(8);
4439+
assert_eq!(engine(&state).1, 8, "{mode:?}: redirect left the engine behind");
4440+
4441+
// And a fresh run from the reset really runs the program again.
4442+
let mut mem = CacheController::new(
4443+
CacheConfig::default(),
4444+
CacheConfig::default(),
4445+
vec![],
4446+
0x4000,
4447+
);
4448+
load_program_into_mem(asm, &mut cpu, &mut mem);
4449+
cpu.write(10, 0);
4450+
state.reset_stages(0);
4451+
let cpi = CpiConfig::default();
4452+
for _ in 0..256 {
4453+
pipeline_tick(&mut state, &mut cpu, &mut mem, &cpi, &mut console);
4454+
if state.halted || state.faulted {
4455+
break;
4456+
}
4457+
}
4458+
assert!(state.halted && !state.faulted, "{mode:?}");
4459+
assert_eq!(cpu.read(10), 3, "{mode:?}");
4460+
}
4461+
}
4462+
4463+
#[test]
4464+
fn set_mode_builds_the_engine_even_if_mode_was_assigned_directly() {
4465+
let cpi = CpiConfig::default();
4466+
let mut state = PipelineSimState::new();
4467+
state.mode = crate::falcon::pipeline::PipelineMode::TomasuloRob;
4468+
assert!(state.ooo.is_none());
4469+
state.set_mode(crate::falcon::pipeline::PipelineMode::TomasuloRob, &cpi);
4470+
assert!(state.ooo.is_some());
4471+
}

0 commit comments

Comments
 (0)