Browse Source

cores/mem_cache: Initial import of iCE40 memory cache

Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
Sylvain Munaut 5 years ago
parent
commit
3bd5ca1a5f

+ 3 - 0
cores/mem_cache/Makefile

@@ -0,0 +1,3 @@
+CORE := mem_cache
+
+include ../../build/core-rules.mk

+ 22 - 0
cores/mem_cache/core.mk

@@ -0,0 +1,22 @@
+CORE := mem_cache
+
+DEPS_mem_cache := misc ice40
+
+RTL_SRCS_mem_cache := $(addprefix rtl/, \
+	mc_bus_wb.v \
+	mc_core.v \
+	mc_tag_match.v \
+	mc_tag_ram.v \
+)
+
+SIM_SRCS_mem_cache := $(addprefix sim/, \
+	mem_sim.v \
+)
+
+TESTBENCHES_mem_cache := \
+	mc_core_tb \
+	mc_wb_tb \
+	mem_sim_tb \
+	$(NULL)
+
+include $(ROOT)/build/core-magic.mk

+ 95 - 0
cores/mem_cache/rtl/mc_bus_wb.v

@@ -0,0 +1,95 @@
+/*
+ * mc_bus_wb.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2020  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * BSD 3-clause, see LICENSE.bsd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+`default_nettype none
+
+module mc_bus_wb #(
+	parameter integer ADDR_WIDTH = 24,
+
+	// auto
+	parameter integer BL = ADDR_WIDTH - 1
+)(
+	// Wishbone bus
+	input  wire [BL:0] wb_addr,
+	input  wire [31:0] wb_wdata,
+	input  wire [ 3:0] wb_wmask,
+	output wire [31:0] wb_rdata,
+	input  wire        wb_cyc,
+	input  wire        wb_we,
+	output wire        wb_ack,
+
+	// Request output
+	output wire [BL:0] req_addr_pre,	// 1 cycle early
+
+	output wire        req_valid,
+
+	output wire        req_write,
+	output wire [31:0] req_wdata,
+	output wire [ 3:0] req_wmask,
+
+	// Response input
+	input  wire        resp_ack,
+	input  wire        resp_nak,
+	input  wire [31:0] resp_rdata,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+	// Control path
+	reg pending;
+	reg new;
+
+	always @(posedge clk or posedge rst)
+		if (rst)
+			pending <= 1'b0;
+		else
+			pending <= (pending | wb_cyc) & ~resp_ack;
+
+	always @(posedge clk)
+		new <= wb_cyc & ~pending;
+
+	assign req_addr_pre = wb_addr;
+	assign req_valid = resp_nak | new;
+
+	assign wb_ack = resp_ack;
+
+	// Write path
+	assign req_write = wb_we;
+	assign req_wdata = wb_wdata;
+	assign req_wmask = wb_wmask;
+
+	// Read path
+	assign wb_rdata  = resp_rdata;
+
+endmodule

+ 536 - 0
cores/mem_cache/rtl/mc_core.v

@@ -0,0 +1,536 @@
+/*
+ * mc_core.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2020  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * BSD 3-clause, see LICENSE.bsd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+`default_nettype none
+
+module mc_core #(
+	parameter integer N_WAYS     =  4,
+	parameter integer ADDR_WIDTH = 24,	/* Word address, 64 Mbytes */
+	parameter integer CACHE_LINE = 64,
+	parameter integer CACHE_SIZE = 64,	/* 64k or 128k */
+
+	// auto
+	parameter integer BL = ADDR_WIDTH - 1
+)(
+	// Request input
+	input  wire [BL:0] req_addr_pre,	// 1 cycle early
+
+	input  wire        req_valid,
+
+	input  wire        req_write,
+	input  wire [31:0] req_wdata,
+	input  wire [ 3:0] req_wmask,
+
+	// Response output (1 cycle later)
+	output reg         resp_ack,
+	output reg         resp_nak,
+	output wire [31:0] resp_rdata,
+
+	// Memory controller interface
+	output wire [BL:0] mi_addr,
+	output wire [ 6:0] mi_len,
+	output wire        mi_rw,
+	output wire        mi_valid,
+	input  wire        mi_ready,
+
+	output wire [31:0] mi_wdata,
+	input  wire        mi_wack,
+	input  wire        mi_wlast,
+
+	input  wire [31:0] mi_rdata,
+	input  wire        mi_rstb,
+	input  wire        mi_rlast,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+	genvar i;
+
+	// Constants
+	// ---------
+
+	localparam integer SPRAM_ADDR_WIDTH = (CACHE_SIZE == 128) ? 15 : 14;
+	localparam integer SL = SPRAM_ADDR_WIDTH - 1;
+
+	localparam integer OFS_WIDTH = $clog2(CACHE_LINE) - 2;
+	localparam integer IDX_WIDTH = SPRAM_ADDR_WIDTH - $clog2(N_WAYS) - OFS_WIDTH;
+	localparam integer TAG_WIDTH = ADDR_WIDTH - (IDX_WIDTH + OFS_WIDTH);
+	localparam integer AGE_WIDTH = $clog2(N_WAYS);
+	localparam integer WAY_WIDTH = $clog2(N_WAYS);
+
+	localparam integer OL = OFS_WIDTH - 1;
+	localparam integer IL = IDX_WIDTH - 1;
+	localparam integer TL = TAG_WIDTH - 1;
+	localparam integer AL = AGE_WIDTH - 1;
+	localparam integer WL = WAY_WIDTH - 1;
+
+	initial begin
+		$display("Memory cache config :");
+		$display(" - %d ways", N_WAYS);
+		$display(" - %d kbytes cache", CACHE_SIZE);
+		$display(" - %d bytes cache lines", CACHE_LINE);
+		$display(" - %d Mbytes address space", 1 << (ADDR_WIDTH - 18));
+		$display(" - %d/%d/%d address split", TAG_WIDTH, IDX_WIDTH, OFS_WIDTH);
+	end
+
+
+	localparam [1:0]
+		ST_BUS_MODE = 0,
+		ST_MEMIF_ISSUE_WRITE = 1,
+		ST_MEMIF_ISSUE_READ = 2,
+		ST_MEMIF_WAIT = 3;
+
+
+	// Signals
+	// -------
+
+	// Control
+	reg  [1:0]  ctrl_state;
+	reg  [1:0]  ctrl_state_nxt;
+
+	wire        ctrl_bus_mode;
+	wire        ctrl_tagram_we;
+
+	// Offset counter
+	reg  [OL:0] cnt_ofs;
+	wire        cnt_ofs_rst;
+	wire        cnt_ofs_inc;
+
+	// Requests
+	wire [IL:0] req_addr_pre_idx;
+
+	reg  [BL:0] req_addr;
+	wire [TL:0] req_addr_tag;
+	wire [IL:0] req_addr_idx;
+	wire [OL:0] req_addr_ofs;
+
+	// Tag Memory
+	wire        way_valid[0:N_WAYS-1];
+	wire        way_dirty[0:N_WAYS-1];
+	wire [AL:0] way_age[0:N_WAYS-1];
+	wire [TL:0] way_tag[0:N_WAYS-1];
+
+	reg         way_valid_nxt[0:N_WAYS-1];
+	reg         way_valid_we[0:N_WAYS-1];
+
+	reg         way_dirty_nxt[0:N_WAYS-1];
+	reg         way_dirty_we[0:N_WAYS-1];
+
+	reg  [AL:0] way_age_nxt[0:N_WAYS-1];
+	wire        way_age_we;
+
+	wire [TL:0] way_tag_nxt;
+	reg         way_tag_we[0:N_WAYS-1];
+
+	// Pre-compute on tag mem data
+	wire [N_WAYS-1:0] way_match;	// Needs to be vector to use unary OR
+	wire [AL:0] way_match_age[0:N_WAYS-1];
+
+	// Lookup
+	wire        lu_miss;
+	wire        lu_hit;
+	reg  [WL:0] lu_hit_way;
+	reg  [AL:0] lu_hit_age;
+
+	// Eviction
+	reg  [WL:0] ev_way;
+	wire        ev_valid;
+	wire        ev_dirty;
+	wire [TL:0] ev_tag;
+
+	reg  [WL:0] ev_way_r;
+	reg         ev_valid_r;
+	reg  [TL:0] ev_tag_r;
+
+	// Data memory
+	reg  [SL:0] dm_addr;
+	wire [31:0] dm_rdata;
+	reg         dm_re;
+	reg  [31:0] dm_wdata;
+	wire [ 7:0] dm_wmask_nibble;
+	reg  [ 3:0] dm_wmask;
+	reg         dm_we;
+
+
+	// Control
+	// -------
+
+	// FSM state register
+	always @(posedge clk or posedge rst)
+		if (rst)
+			ctrl_state <= ST_BUS_MODE;
+		else
+			ctrl_state <= ctrl_state_nxt;
+
+	// FSM next-state logic
+	always @(*)
+	begin
+		// Default is not to move
+		ctrl_state_nxt = ctrl_state;
+
+		// State change logic
+		case (ctrl_state)
+			ST_BUS_MODE:
+				if (lu_miss)
+					ctrl_state_nxt = ev_dirty ? ST_MEMIF_ISSUE_WRITE : ST_MEMIF_ISSUE_READ;
+
+			ST_MEMIF_ISSUE_WRITE:
+				if (mi_ready)
+					ctrl_state_nxt = ST_MEMIF_ISSUE_READ;
+
+			ST_MEMIF_ISSUE_READ:
+				if (mi_ready)
+					ctrl_state_nxt = ST_MEMIF_WAIT;
+
+			ST_MEMIF_WAIT:
+				if (mi_rstb && mi_rlast)
+					ctrl_state_nxt = ST_BUS_MODE;
+		endcase
+	end
+
+	// State conditions
+	assign ctrl_bus_mode = ctrl_state == ST_BUS_MODE;
+
+
+	// Memory interface
+	// ----------------
+
+	// Issue commands
+	assign mi_addr  = {
+		(ctrl_state == ST_MEMIF_ISSUE_WRITE) ? ev_tag_r : req_addr_tag,
+		req_addr_idx,
+		{OFS_WIDTH{1'b0}}
+	};
+	assign mi_len   = (CACHE_LINE / 4) - 1;
+	assign mi_rw    = (ctrl_state == ST_MEMIF_ISSUE_READ);
+	assign mi_valid = (ctrl_state == ST_MEMIF_ISSUE_WRITE) || (ctrl_state == ST_MEMIF_ISSUE_READ);
+
+	// Read data path
+	assign mi_wdata = dm_rdata;
+
+	// Offset counter
+	always @(posedge clk)
+		if (cnt_ofs_rst)
+			cnt_ofs <= 0;
+		else if (cnt_ofs_inc)
+			cnt_ofs <= cnt_ofs + 1;
+
+	assign cnt_ofs_rst = ctrl_bus_mode | (mi_wack & mi_wlast);
+	assign cnt_ofs_inc = mi_rstb | mi_wack | (mi_ready & (ctrl_state == ST_MEMIF_ISSUE_WRITE));
+
+
+	// Request
+	// -------
+
+	// Extract index from pre-address for tag memory lookup
+	assign req_addr_pre_idx = req_addr_pre[IDX_WIDTH+OFS_WIDTH-1:OFS_WIDTH];
+
+	// Register the pre-address, _only_ if in bus mode for next cycle
+	always @(posedge clk)
+		if (ctrl_state_nxt == ST_BUS_MODE)
+			req_addr <= req_addr_pre;
+
+	// Split address
+	assign { req_addr_tag, req_addr_idx, req_addr_ofs } = req_addr;
+
+
+	// Tag Memory
+	// ----------
+
+	// Blocks
+	generate
+		for (i=0; i<N_WAYS; i=i+1)
+			mc_tag_ram #(
+				.IDX_WIDTH(IDX_WIDTH),
+				.TAG_WIDTH(TAG_WIDTH),
+				.AGE_WIDTH(AGE_WIDTH)
+			) tag_ram_I (
+				.w_idx     (req_addr_idx),
+				.w_ena     (ctrl_tagram_we),
+				.w_valid_we(way_valid_we[i]),
+				.w_valid   (way_valid_nxt[i]),
+				.w_dirty_we(way_dirty_we[i]),
+				.w_dirty   (way_dirty_nxt[i]),
+				.w_age_we  (way_age_we),
+				.w_age     (way_age_nxt[i]),
+				.w_tag_we  (way_tag_we[i]),
+				.w_tag     (way_tag_nxt),
+				.r_ena     (ctrl_state_nxt == ST_BUS_MODE),
+				.r_idx     (req_addr_pre_idx),
+				.r_valid   (way_valid[i]),
+				.r_dirty   (way_dirty[i]),
+				.r_age     (way_age[i]),
+				.r_tag     (way_tag[i]),
+				.clk       (clk)
+			);
+	endgenerate
+
+
+	// Lookup logic
+	// ------------
+
+	// Per-way precompute
+	generate
+		for (i=0; i<N_WAYS; i=i+1)
+		begin
+//`define GENERIC
+`ifdef GENERIC
+			assign way_match[i] = way_valid[i] & (way_tag[i] == req_addr_tag);
+`else
+			// Comparator
+			mc_tag_match #(
+				.TAG_WIDTH(TAG_WIDTH)
+			) tag_match_I (
+				.ref(req_addr_tag),
+				.tag(way_tag[i]),
+				.valid(way_valid[i]),
+				.match(way_match[i])
+			);
+`endif
+
+			// Age
+			assign way_match_age[i] = way_match[i] ? way_age[i] : 2'b00;
+		end
+	endgenerate
+
+	// Hit / Miss
+	assign lu_miss = ctrl_bus_mode & req_valid & ~|way_match;
+	assign lu_hit  = ctrl_bus_mode & req_valid &  |way_match;
+
+	// Hit way and age
+	always @(*)
+	begin : hit
+		integer w;
+
+		// Any way that's a match (should be only one !)
+		lu_hit_way = 0;
+		for (w=1; w<N_WAYS; w=w+1)
+			if (way_match[w])
+				lu_hit_way = w;
+
+		// Or all the pre-masked values
+		lu_hit_age = 0;
+		for (w=0; w<N_WAYS; w=w+1)
+			lu_hit_age = lu_hit_age | way_match_age[w];
+	end
+
+
+	// Eviction logic
+	// --------------
+
+	// Select way to evict
+	always @(*)
+	begin : evict
+		integer w;
+
+		// Find a way that's either invalid or "oldest"
+		ev_way = 0;
+		for (w=1; w<N_WAYS; w=w+1)
+			if (!way_valid[w] || (way_age[w] == (N_WAYS-1)))
+				ev_way = w;
+	end
+
+	// Muxes for tag and dirty flags
+	assign ev_valid = way_valid[ev_way];
+	assign ev_dirty = way_dirty[ev_way];
+	assign ev_tag   = way_tag[ev_way];
+
+	// Save them for mem mode
+	always @(posedge clk)
+	begin
+		if (ctrl_bus_mode) begin
+			ev_way_r   <= ev_way;
+			ev_valid_r <= ev_valid;
+			ev_tag_r   <= ev_tag;
+		end
+	end
+
+
+	// Tag Memory update logic
+	// -----------------------
+
+	// Global write enable
+	assign ctrl_tagram_we = lu_hit | ((ctrl_state == ST_MEMIF_ISSUE_READ) & mi_ready);
+
+	// Flag update
+	always @(*)
+	begin : dirty_next
+		integer w;
+
+		if (ctrl_bus_mode)
+			// Bus Mode
+			for (w=0; w<N_WAYS; w=w+1) begin
+				// Valid
+				way_valid_nxt[w] = 1'b0;
+				way_valid_we[w]  = 1'b0;
+
+				// Dirty: Set on write
+				way_dirty_nxt[w] = 1'b1;
+				way_dirty_we[w]  = req_valid & req_write & way_match[w];
+			end
+
+		else
+			// Cache line load
+			for (w=0; w<N_WAYS; w=w+1) begin
+				// Valid
+				way_valid_nxt[w] = 1'b1;
+				way_valid_we[w]  = (w == ev_way_r);
+
+				// Dirty: Set on write
+				way_dirty_nxt[w] = 1'b0;
+				way_dirty_we[w]  = (w == ev_way_r);
+			end
+	end
+
+	// Age update (on hit)
+	assign way_age_we = 1'b1; // ctrl_bus_mode;
+
+	always @(*)
+	begin : age_next
+		integer w;
+
+		if (ctrl_bus_mode)
+		begin
+			// Next age is 0 for the hit, max for invalid and increment if current
+			// age is lower than the age of the hit way
+			for (w=0; w<N_WAYS; w=w+1)
+				if (!way_valid[w])
+					way_age_nxt[w] = N_WAYS - 1;
+				else if (way_match[w])
+					way_age_nxt[w] = 0;
+				else if (way_age[w] < lu_hit_age)
+					way_age_nxt[w] = way_age[w] + 1;
+				else
+					way_age_nxt[w] = way_age[w];
+		end else begin
+			for (w=0; w<N_WAYS; w=w+1)
+				if (!way_valid[w])
+					way_age_nxt[w] = N_WAYS - 1;
+				else if (w == ev_way_r)
+					way_age_nxt[w] = 0;
+				else
+					way_age_nxt[w] = way_age[w] + ev_valid_r;
+		end
+
+/*
+		// Next age is 0 for the hit, max for invalid and increment if current
+		// age is lower than the age of the hit way
+		for (w=0; w<N_WAYS; w=w+1)
+			if (!way_valid[w])
+				way_age_nxt[w] = N_WAYS - 1;
+			else if (way_match[w])
+				way_age_nxt[w] = 0;
+			else if (way_age[w] < lu_hit_age)
+				way_age_nxt[w] = way_age[w] + 1;
+			else
+				way_age_nxt[w] = way_age[w];
+*/
+	end
+
+	// Tag update
+	assign way_tag_nxt = req_addr_tag;
+
+	always @(*)
+	begin : tag_next
+		integer w;
+
+		for (w=0; w<N_WAYS; w=w+1)
+			way_tag_we[w] = (ctrl_state == ST_MEMIF_ISSUE_READ) && (w == ev_way_r);
+	end
+
+
+	// Data Memory
+	// -----------
+
+	// Mem-block
+	ice40_spram_gen #(
+		.ADDR_WIDTH(SPRAM_ADDR_WIDTH),
+		.DATA_WIDTH(32)
+	) data_ram_I (
+		.addr(dm_addr),
+		.rd_data(dm_rdata),
+		.rd_ena(dm_re),
+		.wr_data(dm_wdata),
+		.wr_mask(dm_wmask_nibble),
+		.wr_ena(dm_we),
+		.clk(clk)
+	);
+
+	// Extend mask to nibbles
+	assign dm_wmask_nibble = {
+		dm_wmask[3], dm_wmask[3],
+		dm_wmask[2], dm_wmask[2],
+		dm_wmask[1], dm_wmask[1],
+		dm_wmask[0], dm_wmask[0]
+	};
+
+	// Muxing
+	always @(*)
+	begin
+		if (ctrl_bus_mode) begin
+			// Bus Access
+			dm_addr  = { lu_hit_way, req_addr_idx, req_addr_ofs };
+			dm_re    = 1'b1;
+			dm_wdata = req_wdata;
+			dm_wmask = req_wmask;
+			dm_we    = req_write & lu_hit;
+		end else begin
+			// Read or Write access to/from memory interface
+			dm_addr  = { ev_way_r, req_addr_idx, cnt_ofs };
+			dm_re    = cnt_ofs_inc;
+			dm_wdata = mi_rdata;
+			dm_wmask = 4'h0;
+			dm_we    = mi_rstb;
+		end
+	end
+
+
+	// Responses
+	// ---------
+
+	// Data is direct from the data memory
+	assign resp_rdata = dm_rdata;
+
+	// ACK / NAK
+	always @(posedge clk or posedge rst)
+		if (rst) begin
+			resp_ack <= 1'b0;
+			resp_nak <= 1'b0;
+		end else begin
+			resp_ack <= lu_hit;
+			resp_nak <= lu_miss | (req_valid & ~ctrl_bus_mode);
+		end
+
+endmodule

+ 112 - 0
cores/mem_cache/rtl/mc_tag_match.v

@@ -0,0 +1,112 @@
+/*
+ * mc_tag_match.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2020  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * BSD 3-clause, see LICENSE.bsd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+`default_nettype none
+
+module mc_tag_match #(
+	parameter integer TAG_WIDTH = 12
+)(
+	input  wire [TAG_WIDTH-1:0] ref,
+	input  wire [TAG_WIDTH-1:0] tag,
+	input  wire valid,
+	output wire match
+);
+
+	genvar i;
+
+	// Constants
+	// ---------
+
+	localparam integer CW = (TAG_WIDTH + 1) / 2;
+	localparam integer AW = ((CW + 1)  + 3) / 4;
+
+
+	// Signals
+	// -------
+
+	wire [(2*CW)-1:0] cmp_in0;
+	wire [(2*CW)-1:0] cmp_in1;
+	wire [   CW -1:0] cmp_out;
+
+	wire [(4*AW)-1:0] agg_in;
+	wire [   AW -1:0] agg_out;
+
+
+	// Comparator stage
+	// ----------------
+
+	// Map input to even number, pad with 0
+	assign cmp_in0 = { {(TAG_WIDTH & 1){1'b0}}, ref };
+	assign cmp_in1 = { {(TAG_WIDTH & 1){1'b0}}, tag };
+
+	// Comparator, 2 bits at a time
+	generate
+		for (i=0; i<CW; i=i+1)
+			SB_LUT4 #(
+				.LUT_INIT(16'h9009)
+			) lut_cmp_I (
+				.I0(cmp_in0[2*i+0]),
+				.I1(cmp_in1[2*i+0]),
+				.I2(cmp_in0[2*i+1]),
+				.I3(cmp_in1[2*i+1]),
+				.O(cmp_out[i])
+			);
+	endgenerate
+
+
+	// Aggregation stage
+	// -----------------
+
+	// Map aggregator input
+	assign agg_in = { {((4*AW)-CW-1){1'b1}}, valid, cmp_out };
+
+	// Aggregate 4 bits at a time
+	generate
+		for (i=0; i<AW; i=i+1)
+			SB_LUT4 #(
+				.LUT_INIT(16'h8000)
+			) lut_cmp_I (
+				.I0(agg_in[4*i+3]),
+				.I1(agg_in[4*i+2]),
+				.I2(agg_in[4*i+1]),
+				.I3(agg_in[4*i+0]),
+				.O(agg_out[i])
+			);
+	endgenerate
+
+	// Final OR
+		// This is not manually done because we want the optimizer to merge it
+		// with other logic
+	assign match = &agg_out;
+
+endmodule

+ 165 - 0
cores/mem_cache/rtl/mc_tag_ram.v

@@ -0,0 +1,165 @@
+/*
+ * mc_tag_ram.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2020  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * BSD 3-clause, see LICENSE.bsd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+`default_nettype none
+
+module mc_tag_ram #(
+	parameter integer IDX_WIDTH =  8,
+	parameter integer TAG_WIDTH = 12,
+	parameter integer AGE_WIDTH =  2,
+
+	// auto
+	parameter integer IL = IDX_WIDTH-1,
+	parameter integer TL = TAG_WIDTH-1,
+	parameter integer AL = AGE_WIDTH-1
+)(
+	// Write
+	input  wire [IL:0] w_idx,
+	input  wire        w_ena,
+
+	input  wire        w_valid_we,
+	input  wire        w_valid,
+
+	input  wire        w_dirty_we,
+	input  wire        w_dirty,
+
+	input  wire        w_age_we,
+	input  wire [AL:0] w_age,
+
+	input  wire        w_tag_we,
+	input  wire [TL:0] w_tag,
+
+	// Read
+	input  wire [IL:0] r_idx,
+	input  wire        r_ena,
+
+	output wire        r_valid,
+	output wire        r_dirty,
+	output wire [AL:0] r_age,
+	output wire [TL:0] r_tag,
+
+	// Common
+	input  wire        clk
+);
+
+	// Configuration
+	// -------------
+
+	initial
+		if (IDX_WIDTH > 11) begin
+			$display("Maximum supported number of cache lines is 2048");
+			$finish;
+		end
+
+
+	localparam integer REQ_DWIDTH = TAG_WIDTH + AGE_WIDTH + 2;
+
+	localparam integer RAM_MODE   = (IDX_WIDTH <= 8) ? 0 : (IDX_WIDTH - 8);
+	localparam integer RAM_DWIDTH = 16 >> RAM_MODE;
+	localparam integer RAM_AWIDTH = 8 + RAM_MODE;
+	localparam integer RAM_COUNT  = (REQ_DWIDTH + RAM_DWIDTH - 1) / RAM_DWIDTH;
+
+	localparam integer MEM_DWIDTH = RAM_COUNT * RAM_DWIDTH;
+	localparam integer FILL = MEM_DWIDTH - REQ_DWIDTH;
+
+	initial
+		$display("Cache tag memory config, %d x %d x %d", RAM_COUNT, 1 << RAM_AWIDTH, RAM_DWIDTH);
+
+
+	// Signals
+	// -------
+
+	wire [RAM_AWIDTH-1:0] w_addr;
+	reg  [RAM_AWIDTH-1:0] w_addr_r;
+	wire [RAM_AWIDTH-1:0] r_addr;
+
+	wire [MEM_DWIDTH-1:0] r_val;
+	wire [MEM_DWIDTH-1:0] w_val;
+	reg  [MEM_DWIDTH-1:0] w_val_r;
+	wire [MEM_DWIDTH-1:0] w_msk;
+	reg  [MEM_DWIDTH-1:0] w_msk_r;
+
+	reg  w_ena_r;
+
+
+	// Mapping
+	// -------
+
+	assign w_addr = { {(RAM_AWIDTH-IDX_WIDTH){1'b0}}, w_idx };
+	assign r_addr = { {(RAM_AWIDTH-IDX_WIDTH){1'b0}}, r_idx };
+
+	assign { r_valid, r_dirty, r_age, r_tag } = r_val[REQ_DWIDTH-1:0];
+	assign w_val = { {FILL{1'b0}},  w_valid,     w_dirty,                w_age,                  w_tag };
+	assign w_msk = { {FILL{1'b1}}, ~w_valid_we, ~w_dirty_we, {AGE_WIDTH{~w_age_we}}, {TAG_WIDTH{~w_tag_we}} };
+
+
+	// Write side reg
+	// --------------
+
+	always @(posedge clk)
+	begin
+		w_addr_r <= w_addr;
+		w_val_r  <= w_val;
+		w_msk_r  <= w_msk;
+		w_ena_r  <= w_ena;
+	end
+
+
+	// Storage elements
+	// ----------------
+
+	genvar i;
+
+	generate
+
+		for (i=0; i<RAM_COUNT; i=i+1)
+			ice40_ebr #(
+				.READ_MODE(RAM_MODE),
+				.WRITE_MODE(RAM_MODE),
+				.MASK_WORKAROUND(1),
+				.NEG_WR_CLK(1)
+			) ram_I (
+				.wr_addr(w_addr_r),
+				.wr_data(w_val_r[i*RAM_DWIDTH+:RAM_DWIDTH]),
+				.wr_mask(w_msk_r[i*RAM_DWIDTH+:RAM_DWIDTH]),
+				.wr_ena(w_ena_r),
+				.wr_clk(clk),
+				.rd_addr(r_addr),
+				.rd_data(r_val[i*RAM_DWIDTH+:RAM_DWIDTH]),
+				.rd_ena(r_ena),
+				.rd_clk(clk)
+			);
+
+	endgenerate
+
+endmodule

+ 269 - 0
cores/mem_cache/sim/mc_core_tb.v

@@ -0,0 +1,269 @@
+/*
+ * mc_core_tb.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2020  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * BSD 3-clause, see LICENSE.bsd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+`default_nettype none
+`timescale 1ns / 100ps
+
+module mc_core_tb;
+
+	// Signals
+	// -------
+
+	// Cache request/response
+	reg  [19:0] req_addr_pre;
+	reg         req_valid_pre;
+	reg         req_write_pre;
+	reg  [31:0] req_wdata_pre;
+	reg  [ 3:0] req_wmask_pre;
+
+	reg         req_valid;
+	reg         req_write;
+	reg  [31:0] req_wdata;
+	reg  [ 3:0] req_wmask;
+
+	wire        resp_ack;
+	wire        resp_nak;
+	wire [31:0] resp_rdata;
+
+	// Memory interface
+	wire [19:0] mi_addr;
+	wire [ 6:0] mi_len;
+	wire        mi_rw;
+	wire        mi_valid;
+	wire        mi_ready;
+
+	wire [31:0] mi_wdata;
+	wire        mi_wack;
+	wire        mi_wlast;
+
+	wire [31:0] mi_rdata;
+	wire        mi_rstb;
+	wire        mi_rlast;
+
+	// Clocks / Sync
+	wire [3:0] clk_read_delay;
+
+	reg  pll_lock = 1'b0;
+	reg  clk = 1'b0;
+	wire rst;
+
+	reg  [3:0] rst_cnt = 4'h8;
+
+
+	// Recording setup
+	// ---------------
+
+	initial begin : dump
+		integer i;
+
+		$dumpfile("mc_core_tb.vcd");
+		$dumpvars(0,mc_core_tb);
+		$dumpvars(0,mc_core_tb);
+
+		for (i=0; i<4; i=i+1) begin
+			$dumpvars(0, mc_core_tb.dut_I.way_valid[i]);
+			$dumpvars(0, mc_core_tb.dut_I.way_dirty[i]);
+			$dumpvars(0, mc_core_tb.dut_I.way_age[i]);
+			$dumpvars(0, mc_core_tb.dut_I.way_tag[i]);
+		end
+	end
+
+
+	// DUT
+	// ---
+
+	mc_core #(
+		.N_WAYS(4),
+		.ADDR_WIDTH(20),
+		.CACHE_LINE(64),
+		.CACHE_SIZE(64)
+	) dut_I (
+		.req_addr_pre(req_addr_pre),
+		.req_valid(req_valid),
+		.req_write(req_write),
+		.req_wdata(req_wdata),
+		.req_wmask(req_wmask),
+		.resp_ack(resp_ack),
+		.resp_nak(resp_nak),
+		.resp_rdata(resp_rdata),
+		.mi_addr(mi_addr),
+		.mi_len(mi_len),
+		.mi_rw(mi_rw),
+		.mi_valid(mi_valid),
+		.mi_ready(mi_ready),
+		.mi_wdata(mi_wdata),
+		.mi_wack(mi_wack),
+		.mi_wlast(mi_wlast),
+		.mi_rdata(mi_rdata),
+		.mi_rstb(mi_rstb),
+		.mi_rlast(mi_rlast),
+		.clk(clk),
+		.rst(rst)
+	);
+
+
+
+	// Simulated memory
+	// ----------------
+
+	mem_sim mem_I (
+		.mi_addr(mi_addr),
+		.mi_len(mi_len),
+		.mi_rw(mi_rw),
+		.mi_valid(mi_valid),
+		.mi_ready(mi_ready),
+		.mi_wdata(mi_wdata),
+		.mi_wack(mi_wack),
+		.mi_wlast(mi_wlast),
+		.mi_rdata(mi_rdata),
+		.mi_rstb(mi_rstb),
+		.mi_rlast(mi_rlast),
+		.clk(clk),
+		.rst(rst)
+	);
+
+
+	// Stimulus
+	// --------
+
+	task mc_req_write;
+		input [20:0] addr;
+		input [31:0] data;
+		input [ 3:0] mask;
+		begin
+			req_addr_pre <= addr;
+			req_valid_pre <= 1'b1;
+			req_write_pre <= 1'b1;
+			req_wdata_pre <= data;
+			req_wmask_pre <= mask;
+			@(posedge clk);
+
+			req_addr_pre  <= 20'hxxxxx;
+			req_valid_pre <= 1'b0;
+			req_write_pre <= 1'bx;
+			req_wdata_pre <= 32'hxxxxxxxx;
+			req_wmask_pre <=  4'hx;
+		end
+	endtask
+
+	task mc_req_read;
+		input [31:0] addr;
+		begin
+			req_addr_pre <= addr;
+			req_valid_pre <= 1'b1;
+			req_write_pre <= 1'b0;
+			@(posedge clk);
+
+			req_addr_pre  <= 20'hxxxxx;
+			req_valid_pre <= 1'b0;
+			req_write_pre <= 1'bx;
+		end
+	endtask
+
+	initial begin
+		// Defaults
+		req_addr_pre  <= 20'hxxxxx;
+		req_valid_pre <= 1'b0;
+		req_write_pre <= 1'bx;
+		req_wdata_pre <= 32'hxxxxxxxx;
+		req_wmask_pre <=  4'hx;
+
+		@(negedge rst);
+		@(posedge clk);
+
+		#200 @(posedge clk);
+
+		// Execute 32 byte burst
+		mc_req_read(20'h00010);
+		#200 @(posedge clk);
+		mc_req_read(20'h00010);
+		mc_req_write(20'h0001f, 32'h01234567, 4'h0);
+		mc_req_write(20'h00010, 32'h600dbabe, 4'h0);
+		mc_req_read(20'h0001f);
+
+		mc_req_read(20'h10010);
+		#200 @(posedge clk);
+		mc_req_read(20'h10010);
+		@(posedge clk);
+
+		mc_req_read(20'h20010);
+		#200 @(posedge clk);
+		mc_req_read(20'h20010);
+		@(posedge clk);
+
+		mc_req_read(20'h30010);
+		#200 @(posedge clk);
+		mc_req_read(20'h30010);
+		@(posedge clk);
+
+		mc_req_read(20'h40010);
+		#400 @(posedge clk);
+		mc_req_read(20'h40010);
+		@(posedge clk);
+
+		mc_req_read(20'h20010);
+		@(posedge clk);
+		mc_req_read(20'h20010);
+		@(posedge clk);
+	end
+
+	always @(posedge clk)
+	begin
+		req_valid <= req_valid_pre;
+		req_write <= req_write_pre;
+		req_wdata <= req_wdata_pre;
+		req_wmask <= req_wmask_pre;
+	end
+
+
+	// Clock / Reset
+	// -------------
+
+	// Native clocks
+	initial begin
+		# 200 pll_lock = 1'b1;
+		# 100000 $finish;
+	end
+
+	always #4 clk = ~clk;
+
+	// Reset
+	always @(posedge clk or negedge pll_lock)
+		if (~pll_lock)
+			rst_cnt <= 4'h8;
+		else if (rst_cnt[3])
+			rst_cnt <= rst_cnt + 1;
+
+	assign rst = rst_cnt[3];
+
+endmodule

+ 264 - 0
cores/mem_cache/sim/mc_wb_tb.v

@@ -0,0 +1,264 @@
+/*
+ * mc_wb_tb.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2020  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * BSD 3-clause, see LICENSE.bsd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+`default_nettype none
+`timescale 1ns / 100ps
+
+module mc_wb_tb;
+
+	// Signals
+	// -------
+
+	// Wishbone bus
+	reg  [19:0] wb_addr;
+	reg  [31:0] wb_wdata;
+	reg  [ 3:0] wb_wmask;
+	wire [31:0] wb_rdata;
+	reg         wb_cyc;
+	reg         wb_we;
+	wire        wb_ack;
+
+	// Cache request/response
+	wire [19:0] req_addr_pre;
+
+	wire        req_valid;
+	wire        req_write;
+	wire [31:0] req_wdata;
+	wire [ 3:0] req_wmask;
+
+	wire        resp_ack;
+	wire        resp_nak;
+	wire [31:0] resp_rdata;
+
+	// Memory interface
+	wire [19:0] mi_addr;
+	wire [ 6:0] mi_len;
+	wire        mi_rw;
+	wire        mi_valid;
+	wire        mi_ready;
+
+	wire [31:0] mi_wdata;
+	wire        mi_wack;
+	wire        mi_wlast;
+
+	wire [31:0] mi_rdata;
+	wire        mi_rstb;
+	wire        mi_rlast;
+
+	// Clocks / Sync
+	wire [3:0] clk_read_delay;
+
+	reg  pll_lock = 1'b0;
+	reg  clk = 1'b0;
+	wire rst;
+
+	reg  [3:0] rst_cnt = 4'h8;
+
+
+	// Recording setup
+	// ---------------
+
+	initial begin : dump
+		integer i;
+
+		$dumpfile("mc_wb_tb.vcd");
+		$dumpvars(0,mc_wb_tb);
+		$dumpvars(0,mc_wb_tb);
+
+		for (i=0; i<4; i=i+1) begin
+			$dumpvars(0, mc_wb_tb.dut_I.way_valid[i]);
+			$dumpvars(0, mc_wb_tb.dut_I.way_dirty[i]);
+			$dumpvars(0, mc_wb_tb.dut_I.way_age[i]);
+			$dumpvars(0, mc_wb_tb.dut_I.way_tag[i]);
+		end
+	end
+
+
+	// DUT
+	// ---
+
+	mc_core #(
+		.N_WAYS(4),
+		.ADDR_WIDTH(20),
+		.CACHE_LINE(64),
+		.CACHE_SIZE(64)
+	) dut_I (
+		.req_addr_pre(req_addr_pre),
+		.req_valid(req_valid),
+		.req_write(req_write),
+		.req_wdata(req_wdata),
+		.req_wmask(req_wmask),
+		.resp_ack(resp_ack),
+		.resp_nak(resp_nak),
+		.resp_rdata(resp_rdata),
+		.mi_addr(mi_addr),
+		.mi_len(mi_len),
+		.mi_rw(mi_rw),
+		.mi_valid(mi_valid),
+		.mi_ready(mi_ready),
+		.mi_wdata(mi_wdata),
+		.mi_wack(mi_wack),
+		.mi_wlast(mi_wlast),
+		.mi_rdata(mi_rdata),
+		.mi_rstb(mi_rstb),
+		.mi_rlast(mi_rlast),
+		.clk(clk),
+		.rst(rst)
+	);
+
+	mc_bus_wb #(
+		.ADDR_WIDTH(20)
+	) bus_adapt_I (
+		.wb_addr(wb_addr),
+		.wb_wdata(wb_wdata),
+		.wb_wmask(wb_wmask),
+		.wb_rdata(wb_rdata),
+		.wb_cyc(wb_cyc),
+		.wb_we(wb_we),
+		.wb_ack(wb_ack),
+		.req_addr_pre(req_addr_pre),
+		.req_valid(req_valid),
+		.req_write(req_write),
+		.req_wdata(req_wdata),
+		.req_wmask(req_wmask),
+		.resp_ack(resp_ack),
+		.resp_nak(resp_nak),
+		.resp_rdata(resp_rdata),
+		.clk(clk),
+		.rst(rst)
+	);
+
+
+	// Simulated memory
+	// ----------------
+
+	mem_sim mem_I (
+		.mi_addr(mi_addr),
+		.mi_len(mi_len),
+		.mi_rw(mi_rw),
+		.mi_valid(mi_valid),
+		.mi_ready(mi_ready),
+		.mi_wdata(mi_wdata),
+		.mi_wack(mi_wack),
+		.mi_wlast(mi_wlast),
+		.mi_rdata(mi_rdata),
+		.mi_rstb(mi_rstb),
+		.mi_rlast(mi_rlast),
+		.clk(clk),
+		.rst(rst)
+	);
+
+
+	// Stimulus
+	// --------
+
+	task wb_write;
+		input [19:0] addr;
+		input [31:0] data;
+		begin
+			wb_addr  <= addr;
+			wb_wdata <= data;
+			wb_wmask <= 4'h0;
+			wb_we    <= 1'b1;
+			wb_cyc   <= 1'b1;
+
+			@(posedge clk);
+			while (~wb_ack)
+				@(posedge clk);
+
+			wb_addr  <= 4'hx;
+			wb_wdata <= 32'hxxxxxxxx;
+			wb_wmask <= 4'hx;
+			wb_we    <= 1'bx;
+			wb_cyc   <= 1'b0;
+		end
+	endtask
+
+	task wb_read;
+		input [19:0] addr;
+		begin
+			wb_addr  <= addr;
+			wb_we    <= 1'b0;
+			wb_cyc   <= 1'b1;
+
+			@(posedge clk);
+			while (~wb_ack)
+				@(posedge clk);
+
+			wb_addr  <= 4'hx;
+			wb_we    <= 1'bx;
+			wb_cyc   <= 1'b0;
+		end
+	endtask
+
+	initial begin
+		// Defaults
+		wb_addr  <= 4'hx;
+		wb_wdata <= 32'hxxxxxxxx;
+		wb_wmask <= 4'hx;
+		wb_we    <= 1'bx;
+		wb_cyc   <= 1'b0;
+
+		@(negedge rst);
+		@(posedge clk);
+
+		#200 @(posedge clk);
+
+		wb_write(20'h00010, 32'hcafedead);
+		wb_read (20'h00010);
+		wb_read (20'h00011);
+
+	end
+
+
+	// Clock / Reset
+	// -------------
+
+	// Native clocks
+	initial begin
+		# 200 pll_lock = 1'b1;
+		# 100000 $finish;
+	end
+
+	always #4 clk = ~clk;
+
+	// Reset
+	always @(posedge clk or negedge pll_lock)
+		if (~pll_lock)
+			rst_cnt <= 4'h8;
+		else if (rst_cnt[3])
+			rst_cnt <= rst_cnt + 1;
+
+	assign rst = rst_cnt[3];
+
+endmodule

+ 180 - 0
cores/mem_cache/sim/mem_sim.v

@@ -0,0 +1,180 @@
+/*
+ * mem_sim.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2020  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * BSD 3-clause, see LICENSE.bsd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+`default_nettype none
+
+module mem_sim (
+	// Memory controller interface
+	input  wire [19:0] mi_addr,
+	input  wire [ 6:0] mi_len,
+	input  wire        mi_rw,
+	input  wire        mi_valid,
+	output wire        mi_ready,
+
+	input  wire [31:0] mi_wdata,
+	output wire        mi_wack,
+	output wire        mi_wlast,
+
+	output wire [31:0] mi_rdata,
+	output wire        mi_rstb,
+	output wire        mi_rlast,
+
+	// Common
+	input  wire clk,
+	input  wire rst
+);
+
+	localparam [1:0]
+		ST_IDLE		= 0,
+		ST_WRITE	= 2,
+		ST_READ		= 3;
+
+
+	// Signals
+	// -------
+
+	// Memory array
+	reg [31:0] mem[0:(1<<20)-1];
+
+	wire [19:0] mem_addr;
+	wire [31:0] mem_wdata;
+	reg  [31:0] mem_rdata;
+	wire        mem_we;
+
+	// FSM
+	reg  [1:0] state_cur;
+	reg  [1:0] state_nxt;
+
+	// Command counters
+	reg  [19:0] cmd_addr;
+	reg  [ 7:0] cmd_len;
+	wire        cmd_last;
+
+
+	// Memory
+	// ------
+
+	initial
+	begin : mem_init
+		integer a;
+
+		for (a=0; a<(1<<20)-1; a=a+1)
+			mem[a] = a;
+	end
+
+	always @(posedge clk)
+	begin
+		if (mem_we)
+			mem[mem_addr] <= mem_wdata;
+
+		mem_rdata <= mem[mem_addr];
+	end
+
+
+	// Main FSM
+	// --------
+
+	always @(posedge clk)
+		if (rst)
+			state_cur <= ST_IDLE;
+		else
+			state_cur <= state_nxt;
+
+	always @(*)
+	begin
+		// Default is to stay put
+		state_nxt = state_cur;
+
+		// ... or not
+		case (state_cur)
+			ST_IDLE:
+				if (mi_valid)
+					state_nxt = mi_rw ? ST_READ : ST_WRITE;
+
+			ST_READ:
+				if (cmd_last)
+					state_nxt = ST_IDLE;
+
+			ST_WRITE:
+				if (cmd_last)
+					state_nxt = ST_IDLE;
+		endcase
+	end
+
+
+	// Command channel
+	// ---------------
+
+	// Register command
+	always @(posedge clk)
+	begin
+		if (state_cur == ST_IDLE) begin
+			cmd_addr <= mi_addr;
+			cmd_len  <= { 1'b0, mi_len } - 1;
+		end else begin
+			cmd_addr <= cmd_addr + 1;
+			cmd_len  <= cmd_len - 1;
+		end
+	end
+
+	assign cmd_last = cmd_len[7];
+
+	// Ready ?
+	assign mi_ready = (state_cur == ST_IDLE);
+
+	// Mem access
+	assign mem_addr = cmd_addr;
+
+
+	// Write data channel
+	// ------------------
+
+	assign mem_wdata = mi_wdata;
+	delay_bit #(2) dly_we( (state_cur == ST_WRITE), mem_we, clk );
+
+	assign mi_wack  = mem_we;
+	delay_bit #(2) dly_wlast( cmd_last, mi_wlast, clk );
+
+
+	// Read data channel
+	// -----------------
+
+	wire [31:0] mi_rdata_i;
+
+	delay_bus #(5, 32) dly_rdata (mem_rdata, mi_rdata_i, clk);
+	delay_bit #(6)     dly_rstb  ((state_cur == ST_READ), mi_rstb, clk);
+	delay_bit #(6)     dly_rlast ((state_cur == ST_READ) ? cmd_last : 1'bx, mi_rlast, clk);
+
+	assign mi_rdata = mi_rstb ? mi_rdata_i : 32'hxxxxxxxx;
+
+endmodule

+ 187 - 0
cores/mem_cache/sim/mem_sim_tb.v

@@ -0,0 +1,187 @@
+/*
+ * mem_sim_tb.v
+ *
+ * vim: ts=4 sw=4
+ *
+ * Copyright (C) 2020  Sylvain Munaut <tnt@246tNt.com>
+ * All rights reserved.
+ *
+ * BSD 3-clause, see LICENSE.bsd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the <organization> nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+`default_nettype none
+`timescale 1ns / 100ps
+
+module mem_sim_tb;
+
+	// Signals
+	// -------
+
+	// Memory interface
+	reg  [19:0] mi_addr;
+	reg  [ 6:0] mi_len;
+	reg         mi_rw;
+	reg         mi_valid;
+	wire        mi_ready;
+
+	reg  [31:0] mi_wdata;
+	wire        mi_wack;
+	wire        mi_wlast;
+
+	wire [31:0] mi_rdata;
+	wire        mi_rstb;
+	wire        mi_rlast;
+
+	// Clocks / Sync
+	wire [3:0] clk_read_delay;
+
+	reg  pll_lock = 1'b0;
+	reg  clk = 1'b0;
+	wire rst;
+
+	reg  [3:0] rst_cnt = 4'h8;
+
+
+	// Recording setup
+	// ---------------
+
+	initial begin
+		$dumpfile("mem_sim_tb.vcd");
+		$dumpvars(0,mem_sim_tb);
+	end
+
+
+	// DUT
+	// ---
+
+	mem_sim dut_I (
+		.mi_addr(mi_addr),
+		.mi_len(mi_len),
+		.mi_rw(mi_rw),
+		.mi_valid(mi_valid),
+		.mi_ready(mi_ready),
+		.mi_wdata(mi_wdata),
+		.mi_wack(mi_wack),
+		.mi_wlast(mi_wlast),
+		.mi_rdata(mi_rdata),
+		.mi_rstb(mi_rstb),
+		.mi_rlast(mi_rlast),
+		.clk(clk),
+		.rst(rst)
+	);
+
+
+	// Mem interface
+	// -------------
+
+	always @(posedge clk)
+		if (rst)
+			mi_wdata <= 32'h00010203;
+		else if (mi_wack)
+			mi_wdata <= mi_wdata + 32'h04040404;
+
+
+	// Stimulus
+	// --------
+
+	task mi_burst_write;
+		input [31:0] addr;
+		input [ 6:0] len;
+		begin
+			mi_addr  <= addr;
+			mi_len   <= len;
+			mi_rw    <= 1'b0;
+			mi_valid <= 1'b1;
+
+			@(posedge clk);
+			while (~mi_ready)
+				@(posedge clk);
+
+			mi_valid <= 1'b0;
+
+			@(posedge clk);
+		end
+	endtask
+
+	task mi_burst_read;
+		input [31:0] addr;
+		input [ 6:0] len;
+		begin
+			mi_addr  <= addr;
+			mi_len   <= len;
+			mi_rw    <= 1'b1;
+			mi_valid <= 1'b1;
+
+			@(posedge clk);
+			while (~mi_ready)
+				@(posedge clk);
+
+			mi_valid <= 1'b0;
+
+			@(posedge clk);
+		end
+	endtask
+
+	initial begin
+		// Defaults
+		mi_addr  <= 32'hxxxxxxxx;
+		mi_len   <= 7'hx;
+		mi_rw    <= 1'bx;
+		mi_valid <= 1'b0;
+
+		@(negedge rst);
+		@(posedge clk);
+
+		#200 @(posedge clk);
+
+		// Execute 32 byte burst
+		mi_burst_read (32'h00002000, 7'd15);
+		mi_burst_write(32'h00002000, 7'd31);
+		mi_burst_read (32'h00002000, 7'd15);
+		mi_burst_write(32'h00003000, 7'd31);
+	end
+
+
+	// Clock / Reset
+	// -------------
+
+	// Native clocks
+	initial begin
+		# 200 pll_lock = 1'b1;
+		# 100000 $finish;
+	end
+
+	always #4 clk = ~clk;
+
+	// Reset
+	always @(posedge clk or negedge pll_lock)
+		if (~pll_lock)
+			rst_cnt <= 4'h8;
+		else if (rst_cnt[3])
+			rst_cnt <= rst_cnt + 1;
+
+	assign rst = rst_cnt[3];
+
+endmodule