mirror of
https://github.com/Ed94/pikuma_ps1.git
synced 2026-08-06 15:48:49 +00:00
improvmenets to delay slot modeling (lua metaprogram)
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#endif
|
#endif
|
||||||
// Auto-generated by ps1_meta.lua — DO NOT EDIT
|
// Auto-generated by ps1_meta.lua — DO NOT EDIT
|
||||||
// Source: C:\projects\pikuma\ps1\code\duffle\lottes_tape.h
|
// Source: C:\projects\Pikuma\ps1\code\duffle\lottes_tape.h
|
||||||
// Component atoms (MipsAtomComp_(ac_*)) -> macro variants (mac_*)
|
// Component atoms (MipsAtomComp_(ac_*)) -> macro variants (mac_*)
|
||||||
|
|
||||||
#ifndef WORD_COUNT
|
#ifndef WORD_COUNT
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Auto-generated by ps1_meta.lua (passes/offsets.lua) — DO NOT EDIT
|
// Auto-generated by ps1_meta.lua (passes/offsets.lua) — DO NOT EDIT
|
||||||
// Source: C:\projects\pikuma\ps1\code\duffle\lottes_tape.h
|
// Source: C:\projects\Pikuma\ps1\code\duffle\lottes_tape.h
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#pragma region lottes_tape
|
#pragma region lottes_tape
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#endif
|
#endif
|
||||||
// Auto-generated by ps1_meta.lua — DO NOT EDIT
|
// Auto-generated by ps1_meta.lua — DO NOT EDIT
|
||||||
// Source: C:\projects\pikuma\ps1\code\hello_joypad\hello_joypad.tape.c
|
// Source: C:\projects\Pikuma\ps1\code\hello_joypad\hello_joypad.tape.c
|
||||||
// Component atoms (MipsAtomComp_(ac_*)) -> macro variants (mac_*)
|
// Component atoms (MipsAtomComp_(ac_*)) -> macro variants (mac_*)
|
||||||
|
|
||||||
#ifndef WORD_COUNT
|
#ifndef WORD_COUNT
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Auto-generated by ps1_meta.lua (passes/offsets.lua) — DO NOT EDIT
|
// Auto-generated by ps1_meta.lua (passes/offsets.lua) — DO NOT EDIT
|
||||||
// Source: C:\projects\pikuma\ps1\code\hello_joypad\hello_joypad.tape.c
|
// Source: C:\projects\Pikuma\ps1\code\hello_joypad\hello_joypad.tape.c
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#pragma region hello_joypad.tape
|
#pragma region hello_joypad.tape
|
||||||
|
|||||||
+28
-5
@@ -1700,22 +1700,45 @@ M.HARDWARE_RELATIONS = {
|
|||||||
},
|
},
|
||||||
-- Memory -> COP2 data register (LWC2).
|
-- Memory -> COP2 data register (LWC2).
|
||||||
-- The memory-side timing is not measured by the vendored GTE latch experiment, so this relation has no numeric retirement threshold.
|
-- The memory-side timing is not measured by the vendored GTE latch experiment, so this relation has no numeric retirement threshold.
|
||||||
-- The forward walker emits one info edge at the first command-input consumer and then clears the pending relation.
|
-- The LWC2 destination has TWO retirement regimes (per PSX-SPX):
|
||||||
|
-- * GTE-command consumer (`gte_cmdw_*`): the GTE pipeline LATCHES the LWC2 result, so a `gte_cmdw_*`
|
||||||
|
-- in the very next slot uses the latched value. Gap = 0 is allowed.
|
||||||
|
-- (Per `docs/psx-spx/docs/gtepipelinetimings.md:271-274`.)
|
||||||
|
-- * Any other consumer: standard MIPS load delay applies. Gap = 1 required.
|
||||||
|
-- (Per `docs/psx-spx/docs/cpuspecifications.md:407-419`.)
|
||||||
|
-- Two separate relations so the walker can dispatch by consumer type and emit
|
||||||
|
-- different severities (the GTE-command path is `info` because the latch is intentional;
|
||||||
|
-- the non-GTE-consumer path is `error` because the missing nop is a real bug).
|
||||||
{
|
{
|
||||||
id = "lwc2_unknown_visibility",
|
id = "lwc2_to_gte_command",
|
||||||
semantic = "LWC2",
|
semantic = "LWC2_to_GTE",
|
||||||
token = "gte_lw",
|
token = "gte_lw",
|
||||||
direction = "memory_to_cop2_data",
|
direction = "memory_to_cop2_data",
|
||||||
reads = { domain = "memory", arg = 2 },
|
reads = { domain = "memory", arg = 2 },
|
||||||
writes = { domain = "cop2.data", arg = 1 },
|
writes = { domain = "cop2.data", arg = 1 },
|
||||||
visibility = { kind = "unknown_consumer", required = nil },
|
required = 0, -- GTE-command consumer: gap = 0 OK (latched).
|
||||||
evidence = {
|
evidence = {
|
||||||
confidence = "unknown",
|
confidence = "measured",
|
||||||
source = "gtepipelinetimings.md:271-274",
|
source = "gtepipelinetimings.md:271-274",
|
||||||
},
|
},
|
||||||
violation_kind = "info",
|
violation_kind = "info",
|
||||||
clear_on_consumer = true,
|
clear_on_consumer = true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id = "lwc2_to_other_consumer",
|
||||||
|
semantic = "LWC2_to_other",
|
||||||
|
token = "gte_lw",
|
||||||
|
direction = "memory_to_cop2_data",
|
||||||
|
reads = { domain = "memory", arg = 2 },
|
||||||
|
writes = { domain = "cop2.data", arg = 1 },
|
||||||
|
required = 1, -- Non-GTE-consumer: standard MIPS load delay.
|
||||||
|
evidence = {
|
||||||
|
confidence = "inferred",
|
||||||
|
source = "cpuspecifications.md:407-419",
|
||||||
|
},
|
||||||
|
violation_kind = "error",
|
||||||
|
clear_on_consumer = true,
|
||||||
|
},
|
||||||
-- COP2 data register -> memory (SWC2). A read of C2 state, not a CPU-to-COP2 write.
|
-- COP2 data register -> memory (SWC2). A read of C2 state, not a CPU-to-COP2 write.
|
||||||
-- The policy row stays in for direction/provenance; staging it as a later command-input producer is suppressed.
|
-- The policy row stays in for direction/provenance; staging it as a later command-input producer is suppressed.
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -401,6 +401,18 @@ end
|
|||||||
-- therefore counts ONLY words strictly between the producer and the consumer.
|
-- therefore counts ONLY words strictly between the producer and the consumer.
|
||||||
-- ─────────────────────────────────────────────────────────────────────────
|
-- ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
-- True iff `consumer_event` is a GTE command (gte_cmdw_* or one of the human-readable aliases
|
||||||
|
-- mapped in `duffle.GTE_COMMAND_ALIASES`). Used by the LWC2 retirement-regime dispatch in the
|
||||||
|
-- forward walker: a GTE-command consumer can read the LWC2 result in the very next slot (the GTE
|
||||||
|
-- pipeline latches the LWC2 data); any other consumer must observe the standard MIPS load delay
|
||||||
|
-- (gap >= 1).
|
||||||
|
local function is_gte_command(consumer_event)
|
||||||
|
local tok = consumer_event.encoder or consumer_event.ident or ""
|
||||||
|
if tok:sub(1, 9) == "gte_cmdw_" then return true end
|
||||||
|
local aliases = duffle.GTE_COMMAND_ALIASES or {}
|
||||||
|
return aliases[tok] ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
-- True iff `consumer_word` falls inside the COP2 command's input set OR inside the producer's `fanout_to` set (for IRGB writes).
|
-- True iff `consumer_word` falls inside the COP2 command's input set OR inside the producer's `fanout_to` set (for IRGB writes).
|
||||||
-- Used by the consumer-match step of the forward walker.
|
-- Used by the consumer-match step of the forward walker.
|
||||||
local function is_cop2_consumer_of(consumer_event, destination, producer_rel)
|
local function is_cop2_consumer_of(consumer_event, destination, producer_rel)
|
||||||
@@ -817,9 +829,17 @@ local function analyze_hardware_relations(atom)
|
|||||||
local relation = prod.relation
|
local relation = prod.relation
|
||||||
local semantic = relation.semantic
|
local semantic = relation.semantic
|
||||||
local is_match = false
|
local is_match = false
|
||||||
if semantic == "MTC2" or semantic == "CTC2" or semantic == "LWC2" then
|
if semantic == "MTC2" or semantic == "CTC2" or semantic == "LWC2_to_GTE" or semantic == "LWC2_to_other" then
|
||||||
-- Consumer is a GTE command whose input set contains the producer's COP2 destination (or a fan-out target).
|
-- Consumer is a GTE command whose input set contains the producer's COP2 destination (or a fan-out target).
|
||||||
is_match = is_cop2_consumer_of(ev, prod.destination, relation)
|
-- LWC2_to_GTE — GTE-command consumer: gap = 0 OK (the pipeline latches the LWC2 result).
|
||||||
|
-- LWC2_to_other — non-GTE consumer: standard load delay applies.
|
||||||
|
if relation.id == "lwc2_to_gte_command" then
|
||||||
|
is_match = is_gte_command(ev) and is_cop2_consumer_of(ev, prod.destination, relation)
|
||||||
|
elseif relation.id == "lwc2_to_other_consumer" then
|
||||||
|
is_match = (not is_gte_command(ev)) and is_cop2_consumer_of(ev, prod.destination, relation)
|
||||||
|
else
|
||||||
|
is_match = is_cop2_consumer_of(ev, prod.destination, relation)
|
||||||
|
end
|
||||||
elseif semantic == "MFC2" or semantic == "CFC2" or semantic == "MFC0" then
|
elseif semantic == "MFC2" or semantic == "CFC2" or semantic == "MFC0" then
|
||||||
-- Consumer is any encoder that reads the producer's GPR destination as an operand.
|
-- Consumer is any encoder that reads the producer's GPR destination as an operand.
|
||||||
is_match = is_gpr_consumer_of(ev, prod.destination)
|
is_match = is_gpr_consumer_of(ev, prod.destination)
|
||||||
@@ -1163,6 +1183,8 @@ end
|
|||||||
local function check_hazard_nop_use(atom, _pipe_ctx, findings)
|
local function check_hazard_nop_use(atom, _pipe_ctx, findings)
|
||||||
local forward = atom.paths and atom.paths.forward_state
|
local forward = atom.paths and atom.paths.forward_state
|
||||||
local events = atom.paths.word_events or {}
|
local events = atom.paths.word_events or {}
|
||||||
|
-- GPR effects table used to resolve load destinations when classifying load-delay-slot nops.
|
||||||
|
local gpr_effects = duffle.INSTRUCTION_GPR_EFFECTS or {}
|
||||||
if not events or #events == 0 then return end
|
if not events or #events == 0 then return end
|
||||||
-- Runtime-helper atoms / components (e.g. tape_exit, ac_yield) carry `debug_skip = true` from the bare
|
-- Runtime-helper atoms / components (e.g. tape_exit, ac_yield) carry `debug_skip = true` from the bare
|
||||||
-- `atom_dbg_skip` marker; their structural nops are part of the fixed handshake and not author choices.
|
-- `atom_dbg_skip` marker; their structural nops are part of the fixed handshake and not author choices.
|
||||||
@@ -1180,9 +1202,10 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings)
|
|||||||
|
|
||||||
-- Classify the nop BEFORE its event is applied to the pending state.
|
-- Classify the nop BEFORE its event is applied to the pending state.
|
||||||
if ev_ident == "nop" and prev_ev ~= nil then
|
if ev_ident == "nop" and prev_ev ~= nil then
|
||||||
-- Skip BD-slot nops: they are exclusively owned by control_transfer_delay_slot_use.
|
-- Skip BD-slot nops that are exclusively owned by control_transfer_delay_slot_use
|
||||||
|
-- (the nop after a branch/jump — covered by that check separately).
|
||||||
-- Every BD-slot nop is structural; this check never reports on it.
|
-- Every BD-slot nop is structural; this check never reports on it.
|
||||||
-- (The earlier `if not suppressed then is_bd_slot = true end` form inverted the suppression — the `mac_yield()` handshake's `jump_reg(R_AtomJmp)` was incorrectly flagged.)
|
-- (The earlier `if not suppressed then is_bd_slot = true end` form inverted the suppression - the `mac_yield()` handshake's `jump_reg(R_AtomJmp)` was incorrectly flagged.)
|
||||||
local prev_ident = prev_ev.encoder or ""
|
local prev_ident = prev_ev.encoder or ""
|
||||||
local bd_policies = duffle.CONTROL_TRANSFER_DELAY_SLOT_POLICIES or {}
|
local bd_policies = duffle.CONTROL_TRANSFER_DELAY_SLOT_POLICIES or {}
|
||||||
local is_bd_slot = bd_policies[prev_ident] ~= nil
|
local is_bd_slot = bd_policies[prev_ident] ~= nil
|
||||||
@@ -1243,20 +1266,52 @@ local function check_hazard_nop_use(atom, _pipe_ctx, findings)
|
|||||||
else
|
else
|
||||||
-- Track the slot_kind so the BD-separation case can assert the mac_yield handshake is still suppressed.
|
-- Track the slot_kind so the BD-separation case can assert the mac_yield handshake is still suppressed.
|
||||||
local slot_kind = "plain"
|
local slot_kind = "plain"
|
||||||
findings[#findings + 1] = {
|
-- MIPS load-delay slot: a `load_*` wrote a register in the previous slot, and the result
|
||||||
check = "hazard_nop_use",
|
-- is unavailable for 1 cycle. This `nop` is structurally required; classifying it as
|
||||||
kind = "info",
|
-- `modeled-required` is the correct signal (removing it would make the following
|
||||||
atom = atom.name,
|
-- instruction read the OLD value of the loaded register, a load-use hazard). The
|
||||||
line = ev_line,
|
-- `load_delay_violations` check (Concern 3) catches the actual read-side error; here
|
||||||
source = ev.def_path or ev.source or "",
|
-- we suppress the `modeled-redundant` misclassification.
|
||||||
nop_classification = "modeled-redundant",
|
-- The set of load instructions mirrors the LOAD_INSTRUCTION_IDENTS in `check_load_delay_slots`.
|
||||||
nop_word_index = ev_word,
|
local load_idents = { load_word = true, load_half = true, load_half_u = true,
|
||||||
retired_relation = nil,
|
load_byte = true, load_byte_u = true, gte_lw = true, gte_lwc2 = true }
|
||||||
slot_kind = slot_kind,
|
local is_load_delay = load_idents[prev_ident] == true
|
||||||
msg = string.format("%s at line %d: nop at word %d is modeled-redundant (no pending modeled relation)"
|
if is_load_delay then
|
||||||
, atom.name, ev_line, ev_word
|
-- Determine the destination register from the load's `writes` field.
|
||||||
),
|
local prev_writes = gpr_effects[prev_ident] and gpr_effects[prev_ident].writes or {}
|
||||||
}
|
local prev_args = prev_ev.args or {}
|
||||||
|
local load_dest = prev_writes[1] and prev_args[prev_writes[1]] or "<load-destination>"
|
||||||
|
findings[#findings + 1] = {
|
||||||
|
check = "hazard_nop_use",
|
||||||
|
kind = "info",
|
||||||
|
atom = atom.name,
|
||||||
|
line = ev_line,
|
||||||
|
source = ev.def_path or ev.source or "",
|
||||||
|
nop_classification = "modeled-required",
|
||||||
|
nop_word_index = ev_word,
|
||||||
|
retired_relation = "load_delay_slot",
|
||||||
|
producer_destination = load_dest,
|
||||||
|
consumer_token = "<would-be-consumer>",
|
||||||
|
msg = string.format("%s at line %d: nop at word %d is modeled-required (load-delay slot for %s)"
|
||||||
|
, atom.name, ev_line, ev_word, load_dest
|
||||||
|
),
|
||||||
|
}
|
||||||
|
else
|
||||||
|
findings[#findings + 1] = {
|
||||||
|
check = "hazard_nop_use",
|
||||||
|
kind = "info",
|
||||||
|
atom = atom.name,
|
||||||
|
line = ev_line,
|
||||||
|
source = ev.def_path or ev.source or "",
|
||||||
|
nop_classification = "modeled-redundant",
|
||||||
|
nop_word_index = ev_word,
|
||||||
|
retired_relation = nil,
|
||||||
|
slot_kind = slot_kind,
|
||||||
|
msg = string.format("%s at line %d: nop at word %d is modeled-redundant (no pending modeled relation)"
|
||||||
|
, atom.name, ev_line, ev_word
|
||||||
|
),
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user