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
5 changes: 5 additions & 0 deletions VX_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ VX_CFG_NUM_VPU_BLOCKS = 1
# VM page-table format is a HW<->SW contract, moved to VX_types.toml [vm]; TLB depth stays here.
VX_CFG_TLB_SIZE = 32

# L1 TLB banks (power of two, dividing the TLB sizes): the entry array is
# split by low VPN bits into single-lookup-port banks; lanes contending for
# a bank serialize, misses go to the shared miss station below.
VX_CFG_L1_TLB_NUM_BANKS = 4

# L1 TLB stage (per core): D-side / I-side entries and the per-instance
# miss station depth (distinct outstanding VPN misses).
VX_CFG_DTLB_SIZE = 16
Expand Down
62 changes: 25 additions & 37 deletions hw/rtl/VX_cluster.sv
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ module VX_cluster import VX_gpu_pkg::*, VX_tlb_pkg::*;
`ifdef VX_CFG_VM_ENABLE
// Device MMU sideband from the top DCR surface.
input wire [`VX_CFG_XLEN-1:0] mmu_satp,
// Device-level walker: the L2 TLB's miss bus, exported to the device.
VX_tlb_bus_if.master dev_ptw_if,
input wire mmu_flush_req,
output wire mmu_flush_done,
output wire mmu_fault_valid,
Expand Down Expand Up @@ -328,7 +330,7 @@ module VX_cluster import VX_gpu_pkg::*, VX_tlb_pkg::*;

VX_tlb_flush_if l2_flush_if ();
VX_tlb_flush_if ptw_flush_if ();
wire l2_empty, ptw_empty;
wire l2_empty;

VX_tlb_l2 #(
.INSTANCE_ID (`SFORMATF(("%s-l2tlb", INSTANCE_ID))),
Expand All @@ -342,44 +344,30 @@ module VX_cluster import VX_gpu_pkg::*, VX_tlb_pkg::*;
.empty (l2_empty)
);

VX_mmu_fault_if ptw_fault_if ();
VX_mem_bus_if #(
.DATA_SIZE (`VX_CFG_L1_LINE_SIZE),
.TAG_WIDTH (L2_TAG_WIDTH)
) ptw_mem_if ();

VX_ptw #(
.ID_WIDTH (L2_TLB_SLOT_WIDTH),
.MEM_TAG_WIDTH (L2_TAG_WIDTH)
) ptw (
.clk (clk),
.reset (reset),
.satp (mmu_satp),
.miss_if (l2_ptw_if),
.mem_bus_if (ptw_mem_if),
.flush_if (ptw_flush_if),
.fault_if (ptw_fault_if),
.empty (ptw_empty)
);
// The walker lives at the device: export the L2 TLB's miss bus. The
// L2 cache carries no PTE-fetch client; its bandwidth is all sockets'.
assign dev_ptw_if.req_valid = l2_ptw_if.req_valid;
assign dev_ptw_if.req_data = l2_ptw_if.req_data;
assign l2_ptw_if.req_ready = dev_ptw_if.req_ready;
assign l2_ptw_if.rsp_valid = dev_ptw_if.rsp_valid;
assign l2_ptw_if.rsp_data = dev_ptw_if.rsp_data;
assign dev_ptw_if.rsp_ready = l2_ptw_if.rsp_ready;

// Flush root fans to the cluster L2; each socket self-times its own L1
// TLB flush off the SATP DCR write, and the device walker reports its
// own done leg at the top.
assign l2_flush_if.req = mmu_flush_req;
assign mmu_flush_done = l2_flush_if.done;

// PTE fetches attach as one more L2-cache client (like ocache/rcache).
`ASSIGN_VX_MEM_BUS_IF (per_socket_mem_bus_if[L2_PTW_IDX], ptw_mem_if);
// Structural faults surface at the device walker; L1 permission faults
// are not reported here — cluster_tlb_bus_if stays a pure translation
// fabric across the socket boundary.
assign mmu_fault_valid = 1'b0;
assign mmu_fault_va = '0;
assign mmu_fault_access = 2'b0;
assign mmu_fault_amo = 1'b0;

// Flush root fans to the cluster L2 + walker; each socket self-times its own
// L1 TLB flush off the SATP DCR write, so only these two legs report done.
assign l2_flush_if.req = mmu_flush_req;
assign ptw_flush_if.req = mmu_flush_req;
assign mmu_flush_done = l2_flush_if.done && ptw_flush_if.done;

// Only the shared walker's structural faults are surfaced. L1 permission
// faults are not reported: cluster_tlb_bus_if is the sole MMU signal
// crossing the socket boundary, and it stays a pure translation fabric.
assign mmu_fault_valid = ptw_fault_if.valid;
assign mmu_fault_va = ptw_fault_if.va;
assign mmu_fault_access = ptw_fault_if.access;
assign mmu_fault_amo = ptw_fault_if.amo;

wire mmu_busy = ~l2_empty || ~ptw_empty;
wire mmu_busy = ~l2_empty;
`else
wire mmu_busy = 1'b0;
`endif
Expand Down
11 changes: 8 additions & 3 deletions hw/rtl/VX_gpu_pkg.sv
Original file line number Diff line number Diff line change
Expand Up @@ -1704,8 +1704,7 @@ package VX_gpu_pkg;

// The shared page-table walker attaches one PTE-fetch port under VM, right
// after the socket and graphics ports (like ocache/rcache).
localparam L2_PTW_REQS = `VX_CFG_VM_ENABLED;
localparam L2_PTW_IDX = L2_SOCKET_REQS + L2_GFX_REQS;
localparam L2_PTW_REQS = 0; // walker is device-level (LLC client)

localparam L2_NUM_REQS = L2_SOCKET_REQS + L2_GFX_REQS + L2_PTW_REQS;

Expand All @@ -1720,6 +1719,8 @@ package VX_gpu_pkg;
localparam TLB_SOCKET_ID_WIDTH = L1_TLB_ID_WIDTH + `ARB_SEL_BITS(2, 1);
localparam TLB_CLUSTER_ID_WIDTH = TLB_SOCKET_ID_WIDTH + `ARB_SEL_BITS(NUM_SOCKETS, 1);
localparam L2_TLB_SLOT_WIDTH = `CLOG2(`VX_CFG_L2_TLB_MSHR_SIZE);
// Device-level walker: cluster L2-TLB miss buses arb into one walker.
localparam TLB_DEV_ID_WIDTH = L2_TLB_SLOT_WIDTH + `ARB_SEL_BITS(`VX_CFG_NUM_CLUSTERS, 1);

// Memory request data bits (mem transacts in sectors)
localparam L2_MEM_DATA_WIDTH = (L2_SECTOR_SIZE * 8);
Expand All @@ -1743,7 +1744,11 @@ package VX_gpu_pkg;
localparam L3_SECTOR_SIZE = `VX_CFG_L3_SECTOR_SIZE;

// Input request size
localparam L3_NUM_REQS = `VX_CFG_NUM_CLUSTERS * L2_MEM_PORTS;
// The device-level walker attaches its PTE fetches as one more LLC
// client on the last requestor slot.
localparam L3_PTW_IDX = `VX_CFG_NUM_CLUSTERS * L2_MEM_PORTS;
localparam L3_NUM_REQS = `VX_CFG_NUM_CLUSTERS * L2_MEM_PORTS
+ `VX_CFG_VM_ENABLED;

// Core request tag bits
localparam L3_TAG_WIDTH = L2_MEM_TAG_WIDTH;
Expand Down
85 changes: 79 additions & 6 deletions hw/rtl/Vortex.sv
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ module Vortex import VX_gpu_pkg::*, VX_trace_pkg::*, VX_tlb_pkg::*; (
VX_mem_bus_if #(
.DATA_SIZE (L2_SECTOR_SIZE),
.TAG_WIDTH (L3_TAG_WIDTH)
) per_cluster_mem_bus_if[`VX_CFG_NUM_CLUSTERS * L2_MEM_PORTS]();
) per_cluster_mem_bus_if[L3_NUM_REQS]();

VX_mem_bus_if #(
.DATA_SIZE (L3_SECTOR_SIZE),
Expand Down Expand Up @@ -219,18 +219,90 @@ module Vortex import VX_gpu_pkg::*, VX_trace_pkg::*, VX_tlb_pkg::*; (
wire [`VX_CFG_NUM_CLUSTERS-1:0][1:0] cl_mmu_fault_access;
wire [`VX_CFG_NUM_CLUSTERS-1:0] cl_mmu_fault_amo;

// Per-cluster L2-TLB miss export buses (used by the device-level walker).
VX_tlb_bus_if #(.ID_WIDTH (L2_TLB_SLOT_WIDTH)) per_cluster_dev_ptw_if [`VX_CFG_NUM_CLUSTERS] ();
wire [`VX_CFG_NUM_CLUSTERS-1:0] cl_mmu_flush_done_in;

// One shared walker at the device: the clusters' L2-TLB miss buses arb
// into it, PTE fetches ride a dedicated LLC client port, and the flush
// done-tree gains the walker's leg. Structural faults surface here.
VX_tlb_bus_if #(.ID_WIDTH (TLB_DEV_ID_WIDTH)) dev_ptw_bus_if ();

VX_tlb_bus_arb #(
.NUM_INPUTS (`VX_CFG_NUM_CLUSTERS),
.ID_WIDTH_IN (L2_TLB_SLOT_WIDTH),
.OUT_BUF (3)
) tlb_dev_arb (
.clk (clk),
.reset (reset),
.bus_in_if (per_cluster_dev_ptw_if),
.bus_out_if (dev_ptw_bus_if)
);

VX_tlb_flush_if dev_ptw_flush_if ();
VX_mmu_fault_if dev_ptw_fault_if ();
wire dev_ptw_empty;
`UNUSED_VAR (dev_ptw_empty)

VX_mem_bus_if #(
.DATA_SIZE (L2_SECTOR_SIZE),
.TAG_WIDTH (L3_TAG_WIDTH)
) dev_ptw_mem_if ();

VX_ptw #(
.ID_WIDTH (TLB_DEV_ID_WIDTH),
.DATA_SIZE (L2_SECTOR_SIZE),
.MEM_TAG_WIDTH (L3_TAG_WIDTH)
) dev_ptw (
.clk (clk),
.reset (reset),
.satp (mmu_satp),
.miss_if (dev_ptw_bus_if),
.mem_bus_if (dev_ptw_mem_if),
.flush_if (dev_ptw_flush_if),
.fault_if (dev_ptw_fault_if),
.empty (dev_ptw_empty)
);

`ASSIGN_VX_MEM_BUS_IF (per_cluster_mem_bus_if[L3_PTW_IDX], dev_ptw_mem_if);

assign dev_ptw_flush_if.req = mmu_flush_req;
// The walker's done leg joins cluster 0's slot of the done-tree; the
// fault report likewise lands on cluster 0's lines (the DCR fault latch
// is device-global, so attribution is not lost).
for (genvar c = 0; c < `VX_CFG_NUM_CLUSTERS; ++c) begin : g_dev_flush_done
assign cl_mmu_flush_done_in[c] = (c == 0)
? (cl_mmu_flush_done[c] && dev_ptw_flush_if.done)
: cl_mmu_flush_done[c];
end
wire mmu_fault_valid_in = dev_ptw_fault_if.valid;
wire [`VX_CFG_XLEN-1:0] mmu_fault_va_in = dev_ptw_fault_if.va;
wire [1:0] mmu_fault_access_in = dev_ptw_fault_if.access;
wire mmu_fault_amo_in = dev_ptw_fault_if.amo;

wire [`VX_CFG_NUM_CLUSTERS-1:0] cl_mmu_fault_valid_in;
wire [`VX_CFG_NUM_CLUSTERS-1:0][`VX_CFG_XLEN-1:0] cl_mmu_fault_va_in;
wire [`VX_CFG_NUM_CLUSTERS-1:0][1:0] cl_mmu_fault_access_in;
wire [`VX_CFG_NUM_CLUSTERS-1:0] cl_mmu_fault_amo_in;
for (genvar c = 0; c < `VX_CFG_NUM_CLUSTERS; ++c) begin : g_dev_fault_mux
assign cl_mmu_fault_valid_in[c] = cl_mmu_fault_valid[c] || ((c == 0) && mmu_fault_valid_in);
assign cl_mmu_fault_va_in[c] = ((c == 0) && mmu_fault_valid_in) ? mmu_fault_va_in : cl_mmu_fault_va[c];
assign cl_mmu_fault_access_in[c] = ((c == 0) && mmu_fault_valid_in) ? mmu_fault_access_in : cl_mmu_fault_access[c];
assign cl_mmu_fault_amo_in[c] = ((c == 0) && mmu_fault_valid_in) ? mmu_fault_amo_in : cl_mmu_fault_amo[c];
end

VX_mmu_dcr mmu_dcr (
.clk (clk),
.reset (reset),
.dcr_bus_if (dcr_bus_if),
.dcr_bus_out_if (dcr_cluster_src_if),
.satp (mmu_satp),
.flush_req (mmu_flush_req),
.cluster_flush_done (cl_mmu_flush_done),
.cluster_fault_valid (cl_mmu_fault_valid),
.cluster_fault_va (cl_mmu_fault_va),
.cluster_fault_access (cl_mmu_fault_access),
.cluster_fault_amo (cl_mmu_fault_amo)
.cluster_flush_done (cl_mmu_flush_done_in),
.cluster_fault_valid (cl_mmu_fault_valid_in),
.cluster_fault_va (cl_mmu_fault_va_in),
.cluster_fault_access (cl_mmu_fault_access_in),
.cluster_fault_amo (cl_mmu_fault_amo_in)
);
`else
assign dcr_cluster_src_if.req_valid = dcr_bus_if.req_valid;
Expand Down Expand Up @@ -278,6 +350,7 @@ module Vortex import VX_gpu_pkg::*, VX_trace_pkg::*, VX_tlb_pkg::*; (

`ifdef VX_CFG_VM_ENABLE
.mmu_satp (mmu_satp),
.dev_ptw_if (per_cluster_dev_ptw_if[cluster_id]),
.mmu_flush_req (mmu_flush_req),
.mmu_flush_done (cl_mmu_flush_done[cluster_id]),
.mmu_fault_valid (cl_mmu_fault_valid[cluster_id]),
Expand Down
15 changes: 11 additions & 4 deletions hw/rtl/vm/VX_mmu.sv
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,14 @@ module VX_mmu import VX_gpu_pkg::*, VX_tlb_pkg::*; #(

wire flush_clear;

wire [NUM_REQS-1:0] bank_conflict;

// L1 storage: banked single-port CAMs (losing lanes hold via
// bank_conflict) in front of the shared non-blocking miss station.
VX_tlb_l1 #(
.NUM_REQS (NUM_REQS),
.NUM_REQS (NUM_REQS),
.TLB_SIZE (TLB_SIZE),
.NUM_BANKS (`VX_CFG_L1_TLB_NUM_BANKS),
.MSHR_SIZE (MSHR_SIZE),
.REPLAY_DEPTH (REPLAY_DEPTH),
.PAYLOAD_W (PAYLOAD_W),
Expand All @@ -159,9 +164,11 @@ module VX_mmu import VX_gpu_pkg::*, VX_tlb_pkg::*; #(
.mmu_perf (mmu_perf),
`endif
.lookup_vpn (cam_vpn),
.lookup_valid (req_valid & ~req_bypass),
.lookup_hit (cam_hit),
.lookup_ppn (cam_ppn),
.lookup_flags (cam_flags),
.bank_conflict (bank_conflict),
.access_hit (cam_access_hit),
.mshr_match (mshr_match),
.park_valid (park_valid),
Expand Down Expand Up @@ -199,9 +206,9 @@ module VX_mmu import VX_gpu_pkg::*, VX_tlb_pkg::*; #(
for (genvar l = 0; l < NUM_REQS; ++l) begin : g_cat
assign perm_hit[l] = tlb_perm_ok(cam_flags[l], req_acc[l], req_amo[l]);
assign cat_bypass[l] = req_valid[l] && req_bypass[l];
assign cat_park[l] = req_valid[l] && !req_bypass[l] && (mshr_match[l] || !cam_hit[l]);
assign cat_hit[l] = req_valid[l] && !req_bypass[l] && !mshr_match[l] && cam_hit[l] && perm_hit[l];
assign cat_pfault[l] = req_valid[l] && !req_bypass[l] && !mshr_match[l] && cam_hit[l] && !perm_hit[l];
assign cat_park[l] = req_valid[l] && !req_bypass[l] && !bank_conflict[l] && (mshr_match[l] || !cam_hit[l]);
assign cat_hit[l] = req_valid[l] && !req_bypass[l] && !bank_conflict[l] && !mshr_match[l] && cam_hit[l] && perm_hit[l];
assign cat_pfault[l] = req_valid[l] && !req_bypass[l] && !bank_conflict[l] && !mshr_match[l] && cam_hit[l] && !perm_hit[l];
end

// Park arbitration: at most one lane parks a miss per cycle (lowest lane).
Expand Down
Loading
Loading