前言
本文开始分析Vortex GPGPU
的RTL代码了。按照架构来看,我们首先分析cache
设计。在GPGPU Core
的设计中,cache
出现在ICache
中,图示并没有表明是否存在DCache
,但作者给出的另一张slide里面显示有DCache
。
此外,在Core
之上的Processor
、Cluster
和Socket
中也存在L3
、L2
和L1
,如下:
一些必要的Cache
架构图:
零、关于Cache的通用知识和Vortex GPGPU的知识补充
跳转《Vortex GPGPU的硬件设计和代码结构分析》中的2.5 Vortex GPGPU的Cache串行流水线设计和Cache多端口设计方法
。
目前暂时用不上VIPT Virtual L1 Cache
的知识,先这么着吧!
一、Cache代码结构
纯IP
设计代码如下:
VX_cache_bank.sv
VX_cache_bypass.sv
VX_cache_cluster.sv
VX_cache_data.sv
VX_cache_define.vh
VX_cache_init.sv
VX_cache_mshr.sv
VX_cache_tags.sv
VX_cache_top.sv
VX_cache_wrap.sv
VX_cache.sv
关于以上文件的关系如下:
二、VX_cache.sv代码部分解读
删除了perf_enable
之后的代码如下,这样便于分析!
`include "VX_cache_define.vh"
module VX_cache import VX_gpu_pkg::*; #(
parameter `STRING INSTANCE_ID = "",
// Number of Word requests per cycle
parameter NUM_REQS = 4,
// Size of cache in bytes
parameter CACHE_SIZE = 4096,
// Size of line inside a bank in bytes
parameter LINE_SIZE = 64,
// Number of banks
parameter NUM_BANKS = 1,
// Number of associative ways
parameter NUM_WAYS = 1,
// Size of a word in bytes
parameter WORD_SIZE = `XLEN/8,
// Core Response Queue Size
parameter CRSQ_SIZE = 2,
// Miss Reserv Queue Knob
parameter MSHR_SIZE = 8,
// Memory Response Queue Size
parameter MRSQ_SIZE = 0,
// Memory Request Queue Size
parameter MREQ_SIZE = 4,
// Enable cache writeable
parameter WRITE_ENABLE = 1,
// Request debug identifier
parameter UUID_WIDTH = 0,
// core request tag size
parameter TAG_WIDTH = UUID_WIDTH + 1,
// Core response output register
parameter CORE_OUT_BUF = 0,
// Memory request output register
parameter MEM_OUT_BUF = 0
) (
input wire clk,
input wire reset,
VX_mem_bus_if.slave core_bus_if [NUM_REQS],
VX_mem_bus_if.master mem_bus_if
);
`STATIC_ASSERT(NUM_BANKS == (1 << `CLOG2(NUM_BANKS)), ("invalid parameter"))
localparam REQ_SEL_WIDTH = `UP(`CS_REQ_SEL_BITS);
localparam WORD_SEL_WIDTH = `UP(`CS_WORD_SEL_BITS);
localparam MSHR_ADDR_WIDTH = `LOG2UP(MSHR_SIZE);
localparam MEM_TAG_WIDTH = MSHR_ADDR_WIDTH + `CS_BANK_SEL_BITS;
localparam WORDS_PER_LINE = LINE_SIZE / WORD_SIZE;
localparam WORD_WIDTH = WORD_SIZE * 8;
localparam WORD_SEL_BITS = `CLOG2(WORDS_PER_LINE);
localparam BANK_SEL_BITS = `CLOG2(NUM_BANKS);
localparam BANK_SEL_WIDTH = `UP(BANK_SEL_BITS);
localparam LINE_ADDR_WIDTH = (`CS_WORD_ADDR_WIDTH - BANK_SEL_BITS - WORD_SEL_BITS);
localparam CORE_REQ_DATAW = LINE_ADDR_WIDTH + 1 + WORD_SEL_WIDTH + WORD_SIZE + WORD_WIDTH + TAG_WIDTH;
localparam CORE_RSP_DATAW = WORD_WIDTH + TAG_WIDTH;
localparam CORE_REQ_BUF_ENABLE = (NUM_BANKS != 1) || (NUM_REQS != 1);
localparam MEM_REQ_BUF_ENABLE = (NUM_BANKS != 1);
wire [NUM_REQS-1:0] core_req_valid;
wire [NUM_REQS-1:0][`CS_WORD_ADDR_WIDTH-1:0] core_req_addr;
wire [NUM_REQS-1:0] core_req_rw;
wire [NUM_REQS-1:0][WORD_SIZE-1:0] core_req_byteen;
wire [NUM_REQS-1:0][`CS_WORD_WIDTH-1:0] core_req_data;
wire [NUM_REQS-1:0][TAG_WIDTH-1:0] core_req_tag;
wire [NUM_REQS-1:0] core_req_ready;
for (genvar i = 0; i < NUM_REQS; ++i) begin
assign core_req_valid[i] = core_bus_if[i].req_valid;
assign core_req_rw[i] = core_bus_if[i].req_data.rw;
assign core_req_byteen[i] = core_bus_if[i].req_data.byteen;
assign core_req_addr[i] = core_bus_if[i].req_data.addr;
assign core_req_data[i] = core_bus_if[i].req_data.data;
assign core_req_tag[i] = core_bus_if[i].req_data.tag;
assign core_bus_if[i].req_ready = core_req_ready[i];
`UNUSED_VAR (core_bus_if[i].req_data.atype)
end
///
// Core response buffering
wire [NUM_REQS-1:0] core_rsp_valid_s;
wire [NUM_REQS-1:0][`CS_WORD_WIDTH-1:0] core_rsp_data_s;
wire [NUM_REQS-1:0][TAG_WIDTH-1:0] core_rsp_tag_s;
wire [NUM_REQS-1:0] core_rsp_ready_s;
`RESET_RELAY (core_rsp_reset, reset);
for (genvar i = 0; i < NUM_REQS; ++i) begin
VX_elastic_buffer #(
.DATAW (`CS_WORD_WIDTH + TAG_WIDTH),
.SIZE (CORE_REQ_BUF_ENABLE ? `TO_OUT_BUF_SIZE(CORE_OUT_BUF) : 0),
.OUT_REG (`TO_OUT_BUF_REG(CORE_OUT_BUF))
) core_rsp_buf (
.clk (clk),
.reset (core_rsp_reset),
.valid_in (core_rsp_valid_s[i]),
.ready_in (core_rsp_ready_s[i]),
.data_in ({core_rsp_data_s[i], core_rsp_tag_s[i]}),
.data_out ({core_bus_if[i].rsp_data.data, core_bus_if[i].rsp_data.tag}),
.valid_out (core_bus_if[i].rsp_valid),
.ready_out (core_bus_if[i].rsp_ready)
);
end
///
// Memory request buffering
wire mem_req_valid_s;
wire [`CS_MEM_ADDR_WIDTH-1:0] mem_req_addr_s;
wire mem_req_rw_s;
wire [LINE_SIZE-1:0] mem_req_byteen_s;
wire [`CS_LINE_WIDTH-1:0] mem_req_data_s;
wire [MEM_TAG_WIDTH-1:0] mem_req_tag_s;
wire mem_req_ready_s;
VX_elastic_buffer #(
.DATAW (1 + LINE_SIZE + `CS_MEM_ADDR_WIDTH + `CS_LINE_WIDTH + MEM_TAG_WIDTH),
.SIZE (MEM_REQ_BUF_ENABLE ? `TO_OUT_BUF_SIZE(MEM_OUT_BUF) : 0),
.OUT_REG (`TO_OUT_BUF_REG(MEM_OUT_BUF))
) mem_req_buf (
.clk (clk),
.reset (reset),
.valid_in (mem_req_valid_s),
.ready_in (mem_req_ready_s),
.data_in ({mem_req_rw_s, mem_req_byteen_s, mem_req_addr_s, mem_req_data_s, mem_req_tag_s}),
.data_out ({mem_bus_if.req_data.rw, mem_bus_if.req_data.byteen, mem_bus_if.req_data.addr, mem_bus_if.req_data.data, mem_bus_if.req_data.tag}),
.valid_out (mem_bus_if.req_valid),
.ready_out (mem_bus_if.req_ready)
);
assign mem_bus_if.req_data.atype = '0;
///
// Memory response buffering
wire mem_rsp_valid_s;
wire [`CS_LINE_WIDTH-1:0] mem_rsp_data_s;
wire [MEM_TAG_WIDTH-1:0] mem_rsp_tag_s;
wire mem_rsp_ready_s;
VX_elastic_buffer #(
.DATAW (MEM_TAG_WIDTH + `CS_LINE_WIDTH),
.SIZE (MRSQ_SIZE),
.OUT_REG (MRSQ_SIZE > 2)
) mem_rsp_queue (
.clk (clk),
.reset (reset),
.valid_in (mem_bus_if.rsp_valid),
.ready_in (mem_bus_if.rsp_ready),
.data_in ({mem_bus_if.rsp_data.tag, mem_bus_if.rsp_data.data}),
.data_out ({mem_rsp_tag_s, mem_rsp_data_s}),
.valid_out (mem_rsp_valid_s),
.ready_out (mem_rsp_ready_s)
);
///
wire [`CS_LINE_SEL_BITS-1:0] init_line_sel;
wire init_enable;
// this reset relay is required to sync with bank initialization
`RESET_RELAY (init_reset, reset);
VX_cache_init #(
.CACHE_SIZE (CACHE_SIZE),
.LINE_SIZE (LINE_SIZE),
.NUM_BANKS (NUM_BANKS),
.NUM_WAYS (NUM_WAYS)
) cache_init (
.clk (clk),
.reset (init_reset),
.addr_out (init_line_sel),
.valid_out (init_enable)
);
///
wire [NUM_BANKS-1:0] per_bank_core_req_valid;
wire [NUM_BANKS-1:0][`CS_LINE_ADDR_WIDTH-1:0] per_bank_core_req_addr;
wire [NUM_BANKS-1:0] per_bank_core_req_rw;
wire [NUM_BANKS-1:0][WORD_SEL_WIDTH-1:0] per_bank_core_req_wsel;
wire [NUM_BANKS-1:0][WORD_SIZE-1:0] per_bank_core_req_byteen;
wire [NUM_BANKS-1:0][`CS_WORD_WIDTH-1:0] per_bank_core_req_data;
wire [NUM_BANKS-1:0][TAG_WIDTH-1:0] per_bank_core_req_tag;
wire [NUM_BANKS-1:0][REQ_SEL_WIDTH-1:0] per_bank_core_req_idx;
wire [NUM_BANKS-1:0] per_bank_core_req_ready;
wire [NUM_BANKS-1:0] per_bank_core_rsp_valid;
wire [NUM_BANKS-1:0][`CS_WORD_WIDTH-1:0] per_bank_core_rsp_data;
wire [NUM_BANKS-1:0][TAG_WIDTH-1:0] per_bank_core_rsp_tag;
wire [NUM_BANKS-1:0][REQ_SEL_WIDTH-1:0] per_bank_core_rsp_idx;
wire [NUM_BANKS-1:0] per_bank_core_rsp_ready;
wire [NUM_BANKS-1:0] per_bank_mem_req_valid;
wire [NUM_BANKS-1:0][`CS_MEM_ADDR_WIDTH-1:0] per_bank_mem_req_addr;
wire [NUM_BANKS-1:0] per_bank_mem_req_rw;
wire [NUM_BANKS-1:0][WORD_SEL_WIDTH-1:0] per_bank_mem_req_wsel;
wire [NUM_BANKS-1:0][WORD_SIZE-1:0] per_bank_mem_req_byteen;
wire [NUM_BANKS-1:0][`CS_WORD_WIDTH-1:0] per_bank_mem_req_data;
wire [NUM_BANKS-1:0][MSHR_ADDR_WIDTH-1:0] per_bank_mem_req_id;
wire [NUM_BANKS-1:0] per_bank_mem_req_ready;
wire [NUM_BANKS-1:0] per_bank_mem_rsp_ready;
if (NUM_BANKS == 1) begin
assign mem_rsp_ready_s = per_bank_mem_rsp_ready;
end else begin
assign mem_rsp_ready_s = per_bank_mem_rsp_ready[`CS_MEM_TAG_TO_BANK_ID(mem_rsp_tag_s)];
end
// Bank requests dispatch
wire [NUM_REQS-1:0][CORE_REQ_DATAW-1:0] core_req_data_in;
wire [NUM_BANKS-1:0][CORE_REQ_DATAW-1:0] core_req_data_out;
wire [NUM_REQS-1:0][LINE_ADDR_WIDTH-1:0] core_req_line_addr;
wire [NUM_REQS-1:0][BANK_SEL_WIDTH-1:0] core_req_bid;
wire [NUM_REQS-1:0][WORD_SEL_WIDTH-1:0] core_req_wsel;
for (genvar i = 0; i < NUM_REQS; ++i) begin
if (WORDS_PER_LINE > 1) begin
assign core_req_wsel[i] = core_req_addr[i][0 +: WORD_SEL_BITS];
end else begin
assign core_req_wsel[i] = '0;
end
assign core_req_line_addr[i] = core_req_addr[i][(BANK_SEL_BITS + WORD_SEL_BITS) +: LINE_ADDR_WIDTH];
end
if (NUM_BANKS > 1) begin
for (genvar i = 0; i < NUM_REQS; ++i) begin
assign core_req_bid[i] = core_req_addr[i][WORD_SEL_BITS +: BANK_SEL_BITS];
end
end else begin
assign core_req_bid = '0;
end
for (genvar i = 0; i < NUM_REQS; ++i) begin
assign core_req_data_in[i] = {
core_req_line_addr[i],
core_req_rw[i],
core_req_wsel[i],
core_req_byteen[i],
core_req_data[i],
core_req_tag[i]};
end
`RESET_RELAY (req_xbar_reset, reset);
VX_stream_xbar #(
.NUM_INPUTS (NUM_REQS),
.NUM_OUTPUTS (NUM_BANKS),
.DATAW (CORE_REQ_DATAW),
.PERF_CTR_BITS (`PERF_CTR_BITS),
.OUT_BUF ((NUM_REQS > 4) ? 2 : 0)
) req_xbar (
.clk (clk),
.reset (req_xbar_reset),
`UNUSED_PIN(collisions),
.valid_in (core_req_valid),
.data_in (core_req_data_in),
.sel_in (core_req_bid),
.ready_in (core_req_ready),
.valid_out (per_bank_core_req_valid),
.data_out (core_req_data_out),
.sel_out (per_bank_core_req_idx),
.ready_out (per_bank_core_req_ready)
);
for (genvar i = 0; i < NUM_BANKS; ++i) begin
assign {
per_bank_core_req_addr[i],
per_bank_core_req_rw[i],
per_bank_core_req_wsel[i],
per_bank_core_req_byteen[i],
per_bank_core_req_data[i],
per_bank_core_req_tag[i]} = core_req_data_out[i];
end
// Banks access
for (genvar i = 0; i < NUM_BANKS; ++i) begin
wire [`CS_LINE_ADDR_WIDTH-1:0] curr_bank_mem_req_addr;
wire curr_bank_mem_rsp_valid;
if (NUM_BANKS == 1) begin
assign curr_bank_mem_rsp_valid = mem_rsp_valid_s;
end else begin
assign curr_bank_mem_rsp_valid = mem_rsp_valid_s && (`CS_MEM_TAG_TO_BANK_ID(mem_rsp_tag_s) == i);
end
`RESET_RELAY (bank_reset, reset);
VX_cache_bank #(
.BANK_ID (i),
.INSTANCE_ID (INSTANCE_ID),
.CACHE_SIZE (CACHE_SIZE),
.LINE_SIZE (LINE_SIZE),
.NUM_BANKS (NUM_BANKS),
.NUM_WAYS (NUM_WAYS),
.WORD_SIZE (WORD_SIZE),
.NUM_REQS (NUM_REQS),
.CRSQ_SIZE (CRSQ_SIZE),
.MSHR_SIZE (MSHR_SIZE),
.MREQ_SIZE (MREQ_SIZE),
.WRITE_ENABLE (WRITE_ENABLE),
.UUID_WIDTH (UUID_WIDTH),
.TAG_WIDTH (TAG_WIDTH),
.CORE_OUT_BUF (CORE_REQ_BUF_ENABLE ? 0 : CORE_OUT_BUF),
.MEM_OUT_BUF (MEM_REQ_BUF_ENABLE ? 0 : MEM_OUT_BUF)
) bank (
.clk (clk),
.reset (bank_reset),
// Core request
.core_req_valid (per_bank_core_req_valid[i]),
.core_req_addr (per_bank_core_req_addr[i]),
.core_req_rw (per_bank_core_req_rw[i]),
.core_req_wsel (per_bank_core_req_wsel[i]),
.core_req_byteen (per_bank_core_req_byteen[i]),
.core_req_data (per_bank_core_req_data[i]),
.core_req_tag (per_bank_core_req_tag[i]),
.core_req_idx (per_bank_core_req_idx[i]),
.core_req_ready (per_bank_core_req_ready[i]),
// Core response
.core_rsp_valid (per_bank_core_rsp_valid[i]),
.core_rsp_data (per_bank_core_rsp_data[i]),
.core_rsp_tag (per_bank_core_rsp_tag[i]),
.core_rsp_idx (per_bank_core_rsp_idx[i]),
.core_rsp_ready (per_bank_core_rsp_ready[i]),
// Memory request
.mem_req_valid (per_bank_mem_req_valid[i]),
.mem_req_addr (curr_bank_mem_req_addr),
.mem_req_rw (per_bank_mem_req_rw[i]),
.mem_req_wsel (per_bank_mem_req_wsel[i]),
.mem_req_byteen (per_bank_mem_req_byteen[i]),
.mem_req_data (per_bank_mem_req_data[i]),
.mem_req_id (per_bank_mem_req_id[i]),
.mem_req_ready (per_bank_mem_req_ready[i]),
// Memory response
.mem_rsp_valid (curr_bank_mem_rsp_valid),
.mem_rsp_data (mem_rsp_data_s),
.mem_rsp_id (`CS_MEM_TAG_TO_REQ_ID(mem_rsp_tag_s)),
.mem_rsp_ready (per_bank_mem_rsp_ready[i]),
// initialization
.init_enable (init_enable),
.init_line_sel (init_line_sel)
);
if (NUM_BANKS == 1) begin
assign per_bank_mem_req_addr[i] = curr_bank_mem_req_addr;
end else begin
assign per_bank_mem_req_addr[i] = `CS_LINE_TO_MEM_ADDR(curr_bank_mem_req_addr, i);
end
end
// Bank responses gather
wire [NUM_BANKS-1:0][CORE_RSP_DATAW-1:0] core_rsp_data_in;
wire [NUM_REQS-1:0][CORE_RSP_DATAW-1:0] core_rsp_data_out;
for (genvar i = 0; i < NUM_BANKS; ++i) begin
assign core_rsp_data_in[i] = {per_bank_core_rsp_data[i], per_bank_core_rsp_tag[i]};
end
`RESET_RELAY (rsp_xbar_reset, reset);
VX_stream_xbar #(
.NUM_INPUTS (NUM_BANKS),
.NUM_OUTPUTS (NUM_REQS),
.DATAW (CORE_RSP_DATAW)
) rsp_xbar (
.clk (clk),
.reset (rsp_xbar_reset),
`UNUSED_PIN (collisions),
.valid_in (per_bank_core_rsp_valid),
.data_in (core_rsp_data_in),
.sel_in (per_bank_core_rsp_idx),
.ready_in (per_bank_core_rsp_ready),
.valid_out (core_rsp_valid_s),
.data_out (core_rsp_data_out),
.ready_out (core_rsp_ready_s),
`UNUSED_PIN (sel_out)
);
for (genvar i = 0; i < NUM_REQS; ++i) begin
assign {core_rsp_data_s[i], core_rsp_tag_s[i]} = core_rsp_data_out[i];
end
///
wire mem_req_valid_p;
wire [`CS_MEM_ADDR_WIDTH-1:0] mem_req_addr_p;
wire mem_req_rw_p;
wire [WORD_SEL_WIDTH-1:0] mem_req_wsel_p;
wire [WORD_SIZE-1:0] mem_req_byteen_p;
wire [`CS_WORD_WIDTH-1:0] mem_req_data_p;
wire [MEM_TAG_WIDTH-1:0] mem_req_tag_p;
wire [MSHR_ADDR_WIDTH-1:0] mem_req_id_p;
wire mem_req_ready_p;
// Memory request arbitration
wire [NUM_BANKS-1:0][(`CS_MEM_ADDR_WIDTH + MSHR_ADDR_WIDTH + 1 + WORD_SIZE + WORD_SEL_WIDTH + `CS_WORD_WIDTH)-1:0] data_in;
for (genvar i = 0; i < NUM_BANKS; ++i) begin
assign data_in[i] = {per_bank_mem_req_addr[i],
per_bank_mem_req_rw[i],
per_bank_mem_req_wsel[i],
per_bank_mem_req_byteen[i],
per_bank_mem_req_data[i],
per_bank_mem_req_id[i]};
end
VX_stream_arb #(
.NUM_INPUTS (NUM_BANKS),
.DATAW (`CS_MEM_ADDR_WIDTH + 1 + WORD_SEL_WIDTH + WORD_SIZE + `CS_WORD_WIDTH + MSHR_ADDR_WIDTH),
.ARBITER ("R")
) mem_req_arb (
.clk (clk),
.reset (reset),
.valid_in (per_bank_mem_req_valid),
.ready_in (per_bank_mem_req_ready),
.data_in (data_in),
.data_out ({mem_req_addr_p, mem_req_rw_p, mem_req_wsel_p, mem_req_byteen_p, mem_req_data_p, mem_req_id_p}),
.valid_out (mem_req_valid_p),
.ready_out (mem_req_ready_p),
`UNUSED_PIN (sel_out)
);
if (NUM_BANKS > 1) begin
wire [`CS_BANK_SEL_BITS-1:0] mem_req_bank_id = `CS_MEM_ADDR_TO_BANK_ID(mem_req_addr_p);
assign mem_req_tag_p = MEM_TAG_WIDTH'({mem_req_bank_id, mem_req_id_p});
end else begin
assign mem_req_tag_p = MEM_TAG_WIDTH'(mem_req_id_p);
end
// Memory request multi-port handling
assign mem_req_valid_s = mem_req_valid_p;
assign mem_req_addr_s = mem_req_addr_p;
assign mem_req_tag_s = mem_req_tag_p;
assign mem_req_ready_p = mem_req_ready_s;
if (WRITE_ENABLE != 0) begin
if (`CS_WORDS_PER_LINE > 1) begin
reg [LINE_SIZE-1:0] mem_req_byteen_r;
reg [`CS_LINE_WIDTH-1:0] mem_req_data_r;
always @(*) begin
mem_req_byteen_r = '0;
mem_req_data_r = 'x;
mem_req_byteen_r[mem_req_wsel_p * WORD_SIZE +: WORD_SIZE] = mem_req_byteen_p;
mem_req_data_r[mem_req_wsel_p * `CS_WORD_WIDTH +: `CS_WORD_WIDTH] = mem_req_data_p;
end
assign mem_req_rw_s = mem_req_rw_p;
assign mem_req_byteen_s = mem_req_byteen_r;
assign mem_req_data_s = mem_req_data_r;
end else begin
`UNUSED_VAR (mem_req_wsel_p)
assign mem_req_rw_s = mem_req_rw_p;
assign mem_req_byteen_s = mem_req_byteen_p;
assign mem_req_data_s = mem_req_data_p;
end
end else begin
`UNUSED_VAR (mem_req_byteen_p)
`UNUSED_VAR (mem_req_wsel_p)
`UNUSED_VAR (mem_req_data_p)
`UNUSED_VAR (mem_req_rw_p)
assign mem_req_rw_s = 0;
assign mem_req_byteen_s = {LINE_SIZE{1'b1}};
assign mem_req_data_s = '0;
end
endmodule
2.1 VX_cache_define.vh的代码总结
首先代码通过include "VX_cache_define.vh"
来载入已经定义好的变量,内容并不多:
`ifndef VX_CACHE_DEFINE_VH
`define VX_CACHE_DEFINE_VH
`include "VX_define.vh"
`define CS_REQ_SEL_BITS `CLOG2(NUM_REQS)
`define CS_WORD_WIDTH (8 * WORD_SIZE)
`define CS_LINE_WIDTH (8 * LINE_SIZE)
`define CS_BANK_SIZE (CACHE_SIZE / NUM_BANKS)
`define CS_WAY_SEL_BITS `CLOG2(NUM_WAYS)
`define CS_LINES_PER_BANK (`CS_BANK_SIZE / (LINE_SIZE * NUM_WAYS))
`define CS_WORDS_PER_LINE (LINE_SIZE / WORD_SIZE)
`define CS_WORD_ADDR_WIDTH (`MEM_ADDR_WIDTH-`CLOG2(WORD_SIZE))
`define CS_MEM_ADDR_WIDTH (`MEM_ADDR_WIDTH-`CLOG2(LINE_SIZE))
`define CS_LINE_ADDR_WIDTH (`CS_MEM_ADDR_WIDTH-`CLOG2(NUM_BANKS))
// Word select
`define CS_WORD_SEL_BITS `CLOG2(`CS_WORDS_PER_LINE)
`define CS_WORD_SEL_ADDR_START 0
`define CS_WORD_SEL_ADDR_END (`CS_WORD_SEL_ADDR_START+`CS_WORD_SEL_BITS-1)
// Bank select
`define CS_BANK_SEL_BITS `CLOG2(NUM_BANKS)
`define CS_BANK_SEL_ADDR_START (1+`CS_WORD_SEL_ADDR_END)
`define CS_BANK_SEL_ADDR_END (`CS_BANK_SEL_ADDR_START+`CS_BANK_SEL_BITS-1)
// Line select
`define CS_LINE_SEL_BITS `CLOG2(`CS_LINES_PER_BANK)
`define CS_LINE_SEL_ADDR_START (1+`CS_BANK_SEL_ADDR_END)
`define CS_LINE_SEL_ADDR_END (`CS_LINE_SEL_ADDR_START+`CS_LINE_SEL_BITS-1)
// Tag select
`define CS_TAG_SEL_BITS (`CS_WORD_ADDR_WIDTH-1-`CS_LINE_SEL_ADDR_END)
`define CS_TAG_SEL_ADDR_START (1+`CS_LINE_SEL_ADDR_END)
`define CS_TAG_SEL_ADDR_END (`CS_WORD_ADDR_WIDTH-1)
`define CS_LINE_TAG_ADDR(x) x[`CS_LINE_ADDR_WIDTH-1 : `CS_LINE_SEL_BITS]
///
`define CS_LINE_TO_MEM_ADDR(x, i) {x, `CS_BANK_SEL_BITS'(i)}
`define CS_MEM_ADDR_TO_BANK_ID(x) x[0 +: `CS_BANK_SEL_BITS]
`define CS_MEM_TAG_TO_REQ_ID(x) x[MSHR_ADDR_WIDTH-1:0]
`define CS_MEM_TAG_TO_BANK_ID(x) x[MSHR_ADDR_WIDTH +: `CS_BANK_SEL_BITS]
`define CS_LINE_TO_FULL_ADDR(x, i) {x, (`XLEN-$bits(x))'(i << (`XLEN-$bits(x)-`CS_BANK_SEL_BITS))}
`define CS_MEM_TO_FULL_ADDR(x) {x, (`XLEN-$bits(x))'(0)}
///
`define PERF_CACHE_ADD(dst, src, dcount, scount) \
`PERF_COUNTER_ADD (dst, src, reads, `PERF_CTR_BITS, dcount, scount, (`CDIV(scount, dcount) > 1)) \
`PERF_COUNTER_ADD (dst, src, writes, `PERF_CTR_BITS, dcount, scount, (`CDIV(scount, dcount) > 1)) \
`PERF_COUNTER_ADD (dst, src, read_misses, `PERF_CTR_BITS, dcount, scount, (`CDIV(scount, dcount) > 1)) \
`PERF_COUNTER_ADD (dst, src, write_misses, `PERF_CTR_BITS, dcount, scount, (`CDIV(scount, dcount) > 1)) \
`PERF_COUNTER_ADD (dst, src, bank_stalls, `PERF_CTR_BITS, dcount, scount, (`CDIV(scount, dcount) > 1)) \
`PERF_COUNTER_ADD (dst, src, mshr_stalls, `PERF_CTR_BITS, dcount, scount, (`CDIV(scount, dcount) > 1)) \
`PERF_COUNTER_ADD (dst, src, mem_stalls, `PERF_CTR_BITS, dcount, scount, (`CDIV(scount, dcount) > 1)) \
`PERF_COUNTER_ADD (dst, src, crsp_stalls, `PERF_CTR_BITS, dcount, scount, (`CDIV(scount, dcount) > 1))
`endif // VX_CACHE_DEFINE_VH
具体来说包括四个部分:宏定义及参数计算部分
、地址选择部分
、其他地址转换宏
和性能计数器宏
。
2.1.1 宏定义及其参数计算部分
首先是宏定义及参数计算部分
(以下的line
其实就是cache line
,数字8
是为了计算bit
数):
宏变量 | 解释 |
---|---|
CS_REQ_SEL_BITS | 计算请求的选择位数,使用 CLOG2(NUM_REQS) 宏 |
CS_WORD_WIDTH | 定义cache中的字宽,8 * WORD_SIZE |
CS_LINE_WIDTH | 定义cache的line 宽度,8 * LINE_SIZE |
CS_BANK_SIZE | 定义cache内bank 的容量,CACHE_SIZE / NUM_BANKS |
CS_WAY_SEL_BITS | 定义多路组相联中的路数 所需要的bit 数,用于选择某路 ,CLOG2(NUM_WAYS) |
CS_LINES_PER_BANK | 计算每个bank 中的line 数,CS_BANK_SIZE / (LINE_SIZE * NUM_WAYS) |
CS_WORDS_PER_LINE | 计算每个line 中的words 数,LINE_SIZE / WORD_SIZE |
CS_WORD_ADDR_WIDTH | 计算字word 地址宽度,MEM_ADDR_WIDTH-CLOG2(WORD_SIZE) |
CS_MEM_ADDR_WIDTH | 计算mem 地址宽度,MEM_ADDR_WIDTH-CLOG2(LINE_SIZE) |
CS_LINE_ADDR_WIDTH | 计算line 地址宽度,CS_MEM_ADDR_WIDTH-CLOG2(NUM_BANKS) |
CS_WORD_SEL_BITS | 计算用于word 选择,CLOG2(CS_WORDS_PER_LINE) |
CS_BANK_SEL_BITS | 计算用于bank 选择,CLOG2(NUM_BANKS) |
CS_LINE_SEL_BITS | 计算用于line 选择,CLOG2(CS_LINES_PER_BANK) |
CS_TAG_SEL_BITS | 计算用于tag 选择,CS_WORD_ADDR_WIDTH-1-CS_LINE_SEL_ADDR_END |
其中CS_WORD_ADDR_WIDTH
、CS_MEM_ADDR_WIDTH
和CS_LINE_ADDR_WIDTH
是虚拟地址中的重要组成部分。
CS_WORD_ADDR_WIDTH
就是减去了cache line
内的block offset
后剩余的地址长度。
CS_MEM_ADDR_WIDTH
就是减去了单路cache line
内的行数表示bit数
后剩余的地址长度,其中CLOG2(LINE_SIZE)
就是cache
内的index
。
CS_LINE_ADDR_WIDTH
就是在减去了单路cache line
内的行数表示bit数
后剩余的地址长度的基础上,继续减去bank 数量表示bit数
后剩余的地址长度。注意这里的cache
大概率还是采用多bank
设计,所以CLOG2(NUM_BANKS)
可以用来索引bank
。
2.1.2 基于cache的存储地址划分
其次是地址选择部分
宏变量 | 解释 |
---|---|
CS_WORD_SEL_ADDR_START | 定义了字选择位在地址中的起始位置,0 |
CS_WORD_SEL_ADDR_END | 定义了字选择位在地址中的结束位置,CS_WORD_SEL_ADDR_START+CS_WORD_SEL_BITS-1 |
CS_BANK_SEL_ADDR_START | 定义了bank选择位在地址中的起始位置,1+CS_WORD_SEL_ADDR_END |
CS_BANK_SEL_ADDR_END | 定义了bank选择位在地址中的结束位置,CS_BANK_SEL_ADDR_START+CS_BANK_SEL_BITS-1 |
CS_LINE_SEL_ADDR_START | 定义了line选择位在地址中的起始位置,1+CS_BANK_SEL_ADDR_END |
CS_LINE_SEL_ADDR_END | 定义了line选择位在地址中的结束位置,CS_LINE_SEL_ADDR_START+CS_LINE_SEL_BITS-1 |
CS_TAG_SEL_ADDR_START | 定义了tag选择位在地址中的起始位置,1+CS_LINE_SEL_ADDR_END |
CS_TAG_SEL_ADDR_END | 定义了tag选择位在地址中的结束位置,CS_WORD_ADDR_WIDTH-1 |
所以地址各要素,从高位到低位:
| ------------1---------- | ------------2---------- | -----------3----------- | ------------4---------- |
1表示tag
;2表示line
索引,其实就是cache
里提到的index
;3表示bank
,用于选择特定的cache bank
;4表示word
的block offset
。
对比之前提到的存储地址
格式,无非多了个bank
索引。
2.1.3 其他地址转换宏
之后是其他地址转换宏
,如下:
宏变量 | 解释 |
---|---|
CS_LINE_TAG_ADDR(x) | 宏函数定义是:x[CS_LINE_ADDR_WIDTH-1 : CS_LINE_SEL_BITS] |
CS_LINE_TO_MEM_ADDR(x, i) | 宏函数定义是:{x, CS_BANK_SEL_BITS(i)} ,将cache line 地址x 和索引i 组合成完整的地址,用于从存储器中读取或写入数据。 |
CS_MEM_ADDR_TO_BANK_ID(x) | 宏函数定义是:x[0 +: CS_BANK_SEL_BITS] ,从地址x 中提取出用于选择cache 中不同bank 的标识。 |
CS_MEM_TAG_TO_REQ_ID(x) | 宏函数定义是:x[MSHR_ADDR_WIDTH-1:0] ,将地址tag x 转换为请求 标识符,用于管理和跟踪请求的状态。 |
CS_MEM_TAG_TO_BANK_ID(x) | 宏函数定义是:x[MSHR_ADDR_WIDTH +: CS_BANK_SEL_BITS] |
CS_LINE_TO_FULL_ADDR(x, i) | 宏函数定义是:{x, (XLEN-$bits(x))(i << (XLEN-$bits(x)-CS_BANK_SEL_BITS))} |
CS_MEM_TO_FULL_ADDR(x) | 宏函数定义是:{x, (XLEN-$bits(x))'(0)} |
先挑几个来讲讲,比如这里的MSHR
,这个其实是为了非阻塞cache
准备的,也就是允许不满足一个地址请求的时候接着响应之后的请求。说点正经话,MSHR
就是"Miss-status Handling Register"
,用来记录每一项未完成的事务,包括失效地址
、关键字信息
以及重命名寄存器
信息 。在cache
中,当发生cache miss
时,MSHR
用于存储之前要访问但未在cache中的请求
。
具体操作如下:当cache
未命中时,首先搜索MSHR
看是否有相同的Block
也是处于缺失
状态,如果找到了,就将当前请求合并
到之前的请求中,一起解决历史和此次缺失;如果没有找到,且MSHR
还有余位,则分配一个位置;如果没有余位,则发生资源冲突 。
所以MSHR
的作用是提高缓存系统
的效率,允许在处理一个缓存未命中请求
的同时,继续响应其他的存储访问请求
。当缓存
未命中被满足后,MSHR
会被释放
,从而可以处理新的缓存未命中请求 。在现代缓存设计中,MSHR
的存在使得缓存系统
可以是非阻塞
或者无锁
的,提高了处理器的性能 。
其他还没做解释的宏函数
得先等等!
2.1.4 性能计数器宏
`define PERF_CACHE_ADD(dst, src, dcount, scount) \
`PERF_COUNTER_ADD (dst, src, reads, `PERF_CTR_BITS, dcount, scount, (`CDIV(scount, dcount) > 1)) \
`PERF_COUNTER_ADD (dst, src, writes, `PERF_CTR_BITS, dcount, scount, (`CDIV(scount, dcount) > 1)) \
`PERF_COUNTER_ADD (dst, src, read_misses, `PERF_CTR_BITS, dcount, scount, (`CDIV(scount, dcount) > 1)) \
`PERF_COUNTER_ADD (dst, src, write_misses, `PERF_CTR_BITS, dcount, scount, (`CDIV(scount, dcount) > 1)) \
`PERF_COUNTER_ADD (dst, src, bank_stalls, `PERF_CTR_BITS, dcount, scount, (`CDIV(scount, dcount) > 1)) \
`PERF_COUNTER_ADD (dst, src, mshr_stalls, `PERF_CTR_BITS, dcount, scount, (`CDIV(scount, dcount) > 1)) \
`PERF_COUNTER_ADD (dst, src, mem_stalls, `PERF_CTR_BITS, dcount, scount, (`CDIV(scount, dcount) > 1)) \
`PERF_COUNTER_ADD (dst, src, crsp_stalls, `PERF_CTR_BITS, dcount, scount, (`CDIV(scount, dcount) > 1))
这里涉及两个宏:
宏变量 | 表达式 | 文件 |
---|---|---|
PERF_CTR_BITS | 44 | VX_define.vh |
CDIV | `define CDIV(n,d) ((n + d - 1) / (d)) | VX_platform.vh |
关于性能参数观测reads
、writes
、read misses
、write misses
、bank stalls
、mshr stalls
、memory stalls
都容易看明白,crsp stalls
则指的是当L1 cache
没有命中,需要等待来自更高级别cache
(如L2或L3 cache
)的数据时,就可能发生CRSP stalls
,用来宏观衡量缺失代价和缺失率。CDIV
按照缩写和表达式大概意思就是对count
的除法。
2.1.5 世界线收束1——回归VX_cache.sv的端口介绍
2.1.5.1 参数介绍和整理
首先是:
module VX_cache import VX_gpu_pkg::*;
其中的import VX_gpu_pkg::*;
是导入语句,它告诉编译器
或解释器
去找到名为VX_gpu_pkg
的模块或包,并将该模块或包中的所有内容(使用*
表示所有)导入到当前模块VX_cache
的命名空间中。这种写法的好处在于不用每次都使用VX_gpu_pkg.
的前缀来访问变量或者函数。
其中
// Number of Word requests per cycle
parameter NUM_REQS = 4,
// Size of cache in bytes
parameter CACHE_SIZE = 4096,
// Size of line inside a bank in bytes
parameter LINE_SIZE = 64,
// Number of banks
parameter NUM_BANKS = 1,
// Number of associative ways
parameter NUM_WAYS = 1,
// Size of a word in bytes
parameter WORD_SIZE = `XLEN/8,
// Core Response Queue Size
parameter CRSQ_SIZE = 2,
// Miss Reserv Queue Knob
parameter MSHR_SIZE = 8,
// Memory Response Queue Size
parameter MRSQ_SIZE = 0,
// Memory Request Queue Size
parameter MREQ_SIZE = 4,
// Enable cache writeable
parameter WRITE_ENABLE = 1,
// Request debug identifier
parameter UUID_WIDTH = 0,
// core request tag size
parameter TAG_WIDTH = UUID_WIDTH + 1,
// Core response output register
parameter CORE_OUT_BUF = 0,
// Memory request output register
parameter MEM_OUT_BUF = 0
可以获知如下:
参数名及其值 | 含义 |
---|---|
NUM_REQS=4 | 表示每个周期出现4个word 请求 |
CACHE_SIZE=4096 | 表示cache大小是4KB,注意这里的cache大小不包括valid 、dirty 、替换 、一致性 等辅助性的功能bit 位 |
LINE_SIZE=64 | 表示一个bank 内部有64行,因此对于索引只需要6bit |
NUM_BANKS=1 | 表示只有一个bank |
NUM_WAYS=1 | 表示cache 采用直接映射 |
WORD_SIZE=XLEN/8 | 我们可以在README 文件中找到 $ ../configure --xlen=32 --tooldir=$HOME/tools ,对于WORD 来说是4字节 挺符合常理的。 |
CRSQ_SIZE=2 | Core Response Queue Size ,定义了核心 响应队列的大小。核心 通常在处理请求时会生成响应,并将这些响应 放入一个队列 中等待处理或发送给其他部件 。CRSQ_SIZE 就是指定这个队列 可以容纳的响应数量 。默认为2 ,意味着核心响应队列最多可以存放2个响应。 |
MSHR_SIZE=8 | Miss Reserve Queue Knob ,定义了miss 保留队列的大小。MSHR 在计算机系统中是用来缓存处理器发出的cache miss 请求的数据结构。当处理器发出的请求 未能在缓存 中找到所需的数据,就会生成一个未命中请求,并放入MSHR 中,等待响应。默认为8 ,表示可以同时缓存和处理最多8个未命中请求。 |
MRSQ_SIZE=0 | Memory Response Queue Size ,定义了内存响应队列 的大小。在处理器和内存交互时,内存通常会返回请求的数据或响应,这些响应被放置在内存响应队列中,等待处理器接收和处理。此处不接收来自内存应答的响应 。 |
MREQ_SIZE=4 | Memory Request Queue Size ,定义了内存请求队列 的大小。当处理器发出请求访问内存时,这些请求可能需要先放入内存请求队列中排队,等待内存控制器的处理。默认为4 ,表示可以同时缓存和处理最多 4 个内存请求 |
WRITE_ENABLE=1 | Enable cache writeable |
UUID_WIDTH=0 | Request debug identifier |
TAG_WIDTH=UUID_WIDTH + 1 | core request tag size |
CORE_OUT_BUF=0 | Core response output register ,定义了核心响应输出缓冲区的大小 。在处理器内部,当核心生成响应时,通常会将响应存储在一个输出缓冲区中,以便稍后发送到其他部件或处理器。此处表示不使用 额外的缓冲区来存储核心的响应输出。 |
MEM_OUT_BUF=0 | Memory request output register ,定义了内存请求输出寄存器的大小 。当处理器向内存控制器发送请求时,通常会将请求存储在一个输出寄存器中,以便稍后发送到内存控制器或内存模块。此处表示不使用 额外的寄存器来存储内存请求的输出。 |
2.1.5.2 直接映射VIPT L1 Cache设计合理性的探讨
值得一说的是,NUM_WAYS=1
这个数值是合理的。这是因为,对于现代CPU
而言,为了适应Virtual Address
和VIPT L1 Cache
,往往考虑地址低12bit
当作Virtual Tag
,因此一般的L1 ICache
都是4KB
,和页
大小一样,与此同时一般VIPT L1 ICache 和 DCahe
都采用32KB
,由于存在比较复杂的aliasing
问题(就是有可能出现多个虚拟地址
映射到同一个物理地址
上而出现cache一致性问题
,至于解决方案就是在PIPT L2 Cache
中查找到虚拟页号低位或者低2位
来定位VIPT L1 DCache
中映射向同一物理地址
的其他虚拟地址
),因此会将32KB
划分为若干个4KB
,所以在Intel Core i7
中的L1 DCache
都会采用8路组相联
。
接下来是输入/输出端口:
input wire clk,
input wire reset,
VX_mem_bus_if.slave core_bus_if [NUM_REQS],
VX_mem_bus_if.master mem_bus_if
我们先看看后面两个是怎么回事儿!看下一节!
2.1.6 世界线发散1——讲解VX_mem_bus_if.sv的端口介绍定义
这里的VX_mem_bus_if
可以见/mem/VX_mem_bus_if.sv
,具体代码如下:
`include "VX_define.vh"
interface VX_mem_bus_if #(
parameter DATA_SIZE = 1,
parameter ATYPE_WIDTH= `ADDR_TYPE_WIDTH,
parameter TAG_WIDTH = 1,
parameter MEM_ADDR_WIDTH = `MEM_ADDR_WIDTH,
parameter ADDR_WIDTH = MEM_ADDR_WIDTH - `CLOG2(DATA_SIZE)
) ();
typedef struct packed {
logic rw;
logic [DATA_SIZE-1:0] byteen;
logic [ADDR_WIDTH-1:0] addr;
logic [ATYPE_WIDTH-1:0] atype;
logic [DATA_SIZE*8-1:0] data;
logic [TAG_WIDTH-1:0] tag;
} req_data_t;
typedef struct packed {
logic [DATA_SIZE*8-1:0] data;
logic [TAG_WIDTH-1:0] tag;
} rsp_data_t;
logic req_valid;
req_data_t req_data;
logic req_ready;
logic rsp_valid;
rsp_data_t rsp_data;
logic rsp_ready;
modport master (
output req_valid,
output req_data,
input req_ready,
input rsp_valid,
input rsp_data,
output rsp_ready
);
modport slave (
input req_valid,
input req_data,
output req_ready,
output rsp_valid,
output rsp_data,
input rsp_ready
);
endinterface
开头的interface VX_mem_bus_if #(...):
:定义了一个interface
命名为 VX_mem_bus_if
。#(...)
表示这个接口可以接受一系列的参数。在这个例子中,定义了五个参数:
DATA_SIZE: 数据大小,默认为 1
ATYPE_WIDTH: 地址类型宽度,从头文件中引用了一个宏定义 ADDR_TYPE_WIDTH
TAG_WIDTH: 标签宽度,默认为 1
MEM_ADDR_WIDTH: 内存地址宽度,从头文件中引用了一个宏定义 MEM_ADDR_WIDTH
ADDR_WIDTH: 地址宽度,根据 MEM_ADDR_WIDTH 和 DATA_SIZE 计算得出
中间的typedef struct packed { ... } req_data_t;
:定义了一个名为req_data_t
的packed
结构体,包含了请求
的数据字段
:
读写信号 rw
字节使能 byteen
地址 addr
地址类型 atype
数据 data
标签 tag
中间的typedef struct packed { ... } rsp_data_t;
:定义了一个名为rsp_data_t
的packed
结构体,包含了响应
的数据字段
:数据data和标签tag。
随后就是信号声明:
req_valid, req_data, req_ready: 定义了请求端的有效信号、数据结构和就绪信号。
rsp_valid, rsp_data, rsp_ready: 定义了响应端的有效信号、数据结构和就绪信号。
最后就是modport
定义了接口
的端口映射
,分为master
和slave
两个模式:
master模式定义了接口从主设备(如 CPU 或者主控制器)到从设备(如内存控制器)的信号传输方向。
slave模式定义了接口从从设备到主设备的信号传输方向。
2.1.7 世界线收束2——回归VX_cache.sv端口内master/slave的介绍
input wire clk,
input wire reset,
VX_mem_bus_if.slave core_bus_if [NUM_REQS],
VX_mem_bus_if.master mem_bus_if
结合2.1.6
的内容,有如下:
将
VX_mem_bus_if
接口的slave模式
实例化为名为core_bus_if的数组
,数组大小为NUM_REQS
。这表示有NUM_REQS
个从设备接口实例
,每个接口都遵循VX_mem_bus_if
接口的从设备定义
,即接收来自主设备(如 CPU)的请求信号和发送响应信号。
将
VX_mem_bus_if
接口的master模式
实例化为名为mem_bus_if
的信号集合。这个实例用于连接到主设备(如内存控制器),接收来自主设备的请求信号并发送响应信号。
2.2 VX_cache.sv内的常量和局部变量解释
`STATIC_ASSERT(NUM_BANKS == (1 << `CLOG2(NUM_BANKS)), ("invalid parameter"))
localparam REQ_SEL_WIDTH = `UP(`CS_REQ_SEL_BITS);
localparam WORD_SEL_WIDTH = `UP(`CS_WORD_SEL_BITS);
localparam MSHR_ADDR_WIDTH = `LOG2UP(MSHR_SIZE);
localparam MEM_TAG_WIDTH = MSHR_ADDR_WIDTH + `CS_BANK_SEL_BITS;
localparam WORDS_PER_LINE = LINE_SIZE / WORD_SIZE;
localparam WORD_WIDTH = WORD_SIZE * 8;
localparam WORD_SEL_BITS = `CLOG2(WORDS_PER_LINE);
localparam BANK_SEL_BITS = `CLOG2(NUM_BANKS);
localparam BANK_SEL_WIDTH = `UP(BANK_SEL_BITS);
localparam LINE_ADDR_WIDTH = (`CS_WORD_ADDR_WIDTH - BANK_SEL_BITS - WORD_SEL_BITS);
localparam CORE_REQ_DATAW = LINE_ADDR_WIDTH + 1 + WORD_SEL_WIDTH + WORD_SIZE + WORD_WIDTH + TAG_WIDTH;
localparam CORE_RSP_DATAW = WORD_WIDTH + TAG_WIDTH;
localparam CORE_REQ_BUF_ENABLE = (NUM_BANKS != 1) || (NUM_REQS != 1);
localparam MEM_REQ_BUF_ENABLE = (NUM_BANKS != 1);
关于UP
的介绍在hw/rtl/VX_config.vh
中,其中UP
的定义如下:
`ifndef UP
`define UP(x) (((x) != 0) ? (x) : 1)
`endif
也就是说UP
的结果至少为1
。
关于LOG2UP
的介绍在hw/rtl/VX_platform.vh
中,其定义如下:
`define LOG2UP(x) (((x) > 1) ? $clog2(x) : 1)
关于地址CS_WORD_ADDR_WIDTH
,按照之前的描述,从高位到低位:
| ------------1---------- | ------------2---------- | -----------3----------- | ------------4---------- |
1表示tag
;2表示line
索引,其实就是cache
里提到的index
;3表示bank
,用于选择特定的cache bank
;4表示word
的block offset
。(以上均带有CS_
前缀)
然后地址MEM_ADDR_WIDTH
为:
| ------------1---------- | ------------2---------- | -----------3----------- | ------------4---------- | ------------5---------- |
1表示tag
;2表示line
索引,其实就是cache
里提到的index
;3表示bank
,用于选择特定的cache bank
;4表示word
的block offset
;5表示word size
,也就是偏移的2个bit
来表示4字节取址
。(以上均带有CS_
前缀)
作为对比,MEM_TAG_WIDTH
这里的TAG
表示为:
MEM_TAG_WIDTH = MSHR_ADDR_WIDTH + `CS_BANK_SEL_BITS;
其他还有几个对比项,分别是CORE_REQ_DATAW
、CORE_RSP_DATAW
。
CORE_REQ_DATAW = LINE_ADDR_WIDTH + 1 + WORD_SEL_WIDTH + WORD_SIZE + WORD_WIDTH + TAG_WIDTH;
CORE_RSP_DATAW = WORD_WIDTH + TAG_WIDTH;
这里几个核心常数的数值如下表:
常数 | 表达式 | 数值 |
---|---|---|
LINE_ADDR_WIDTH | LINE_ADDR_WIDTH = (CS_WORD_ADDR_WIDTH - BANK_SEL_BITS - WORD_SEL_BITS) ; define CS_WORD_ADDR_WIDTH (MEM_ADDR_WIDTH-CLOG2(WORD_SIZE)) | CS_WORD_ADDR_WIDTH=32-log2(4)=30 ; BANK_SEL_BITS=log2(1)=0 ; WORD_SEL_BITS=log2(64/4)=4 ; 因此LINE_ADDR_WIDTH = 26 |
MEM_ADDR_WIDTH | - | 32 |
WORD_SEL_WIDTH | UP(CS_WORD_SEL_BITS) ; define CS_WORD_SEL_BITS CLOG2(CS_WORDS_PER_LINE) ; define CS_WORDS_PER_LINE (LINE_SIZE / WORD_SIZE) | WORD_SEL_WIDTH = log2(64/4)=4 |
WORD_SIZE | - | WORD_SIZE=4 |
WORD_WIDTH | WORD_WIDTH = WORD_SIZE * 8 | WORD_WIDTH=4*8=32 |
TAG_WIDTH | TAG_WIDTH = UUID_WIDTH + 1 | TAG_WIDTH=0+1=1 |
所以上述CORE_REQ_DATAW
、CORE_RSP_DATAW
分别为26+1+4+4+32+1=68
、32+1=33
。
ps1:关于MEM_ADDR_WIDTH
的定义见hw/rtl/VX_config.vh
,其表达式如下:
`ifndef MEM_ADDR_WIDTH
`ifdef XLEN_64
`define MEM_ADDR_WIDTH 48
`else
`define MEM_ADDR_WIDTH 32
`endif
`endif
我们在前文提到定义XLEN
为32
,因此MEM_ADDR_WIDTH=32
。
ps2:关于CS_WORDS_PER_LINE
的定义见hw/rtl/cache/VX_cache_define.vh
,其表达式如下:
`define CS_WORDS_PER_LINE (LINE_SIZE / WORD_SIZE)
之后是关于接口定义部分:
wire [NUM_REQS-1:0] core_req_valid;
wire [NUM_REQS-1:0][`CS_WORD_ADDR_WIDTH-1:0] core_req_addr;
wire [NUM_REQS-1:0] core_req_rw;
wire [NUM_REQS-1:0][WORD_SIZE-1:0] core_req_byteen;
wire [NUM_REQS-1:0][`CS_WORD_WIDTH-1:0] core_req_data;
wire [NUM_REQS-1:0][TAG_WIDTH-1:0] core_req_tag;
wire [NUM_REQS-1:0] core_req_ready;
for (genvar i = 0; i < NUM_REQS; ++i) begin
assign core_req_valid[i] = core_bus_if[i].req_valid;
assign core_req_rw[i] = core_bus_if[i].req_data.rw;
assign core_req_byteen[i] = core_bus_if[i].req_data.byteen;
assign core_req_addr[i] = core_bus_if[i].req_data.addr;
assign core_req_data[i] = core_bus_if[i].req_data.data;
assign core_req_tag[i] = core_bus_if[i].req_data.tag;
assign core_bus_if[i].req_ready = core_req_ready[i];
`UNUSED_VAR (core_bus_if[i].req_data.atype)
end
还是老样子,给出相关地址长度/宽度:
常数 | 表达式 | 数值 |
---|---|---|
CS_WORD_ADDR_WIDTH | define CS_WORD_ADDR_WIDTH (MEM_ADDR_WIDTH-CLOG2(WORD_SIZE)) | CS_WORD_ADDR_WIDTH=32-log2(4)=30 |
WORD_SIZE | ` | WORD_SIZE=4 |
CS_WORD_WIDTH | define CS_WORD_WIDTH (8 * WORD_SIZE) | CS_WORD_WIDTH=8*4=32 |
TAG_WIDTH | TAG_WIDTH = UUID_WIDTH + 1 | TAG_WIDTH=0+1=1 |
ps1:关于CS_WORD_ADDR_WIDTH
的定义见hw/rtl/cache/VX_cache_define.vh
。表达式如下:
`define CS_WORD_ADDR_WIDTH (`MEM_ADDR_WIDTH-`CLOG2(WORD_SIZE))
ps2:关于CS_WORD_WIDTH
的定义见hw/rtl/cache/VX_cache_define.vh
。表达式如下:
`define CS_WORD_WIDTH (8 * WORD_SIZE)
先到这里为止。
总结
本文主要针对VX_cache.sv
分析常见常量、推导常量数值、接口定义。做好例化模块的前置工作,接下来打算逐个分析模块功能。