-- EMACS settings: -*-  tab-width: 2; indent-tabs-mode: t -*-
-- vim: tabstop=2:shiftwidth=2:noexpandtab
-- kate: tab-width 2; replace-tabs off; indent-width 2;
-- =============================================================================
-- Authors:				 	Martin Zabel
--
-- Entity:				 	Simulation model for true dual-port memory.
--
-- Description:
-- 
-- Simulation model for true dual-port memory, with:
--
-- * dual clock, clock enable,
-- * 2 read/write ports.
--
-- The interface matches that of the IP core PoC.mem.ocram.tdp.
-- But the implementation there is restricted to the description supported by
-- various synthesis compilers. The implementation here also simulates the
-- correct Mixed-Port Read-During-Write Behavior and handles X propagation.
--
-- License:
-- =============================================================================
-- Copyright 2016-2016 Technische Universitaet Dresden - Germany
--										 Chair for VLSI-Design, Diagnostics and Architecture
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--		http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
-- =============================================================================


library	IEEE;
use			IEEE.std_logic_1164.all;
use			IEEE.numeric_std.all;

library PoC;
use			PoC.utils.all;
use			PoC.strings.all;
use			PoC.vectors.all;
use			PoC.mem.all;


entity [docs]ocram_tdp_sim is
	generic (
		A_BITS		: positive;															-- number of address bits
		D_BITS		: positive;															-- number of data bits
		FILENAME	: string		:= ""												-- file-name for RAM initialization
	);
	port (
		clk1 : in	std_logic;															-- clock for 1st port
		clk2 : in	std_logic;															-- clock for 2nd port
		ce1	: in	std_logic;															-- clock-enable for 1st port
		ce2	: in	std_logic;															-- clock-enable for 2nd port
		we1	: in	std_logic;															-- write-enable for 1st port
		we2	: in	std_logic;															-- write-enable for 2nd port
		a1	 : in	unsigned(A_BITS-1 downto 0);						-- address for 1st port
		a2	 : in	unsigned(A_BITS-1 downto 0);						-- address for 2nd port
		d1	 : in	std_logic_vector(D_BITS-1 downto 0);		-- write-data for 1st port
		d2	 : in	std_logic_vector(D_BITS-1 downto 0);		-- write-data for 2nd port
		q1	 : out std_logic_vector(D_BITS-1 downto 0);		-- read-data from 1st port
		q2	 : out std_logic_vector(D_BITS-1 downto 0) 		-- read-data from 2nd port
	);
end entity;


architecture [docs]sim of ocram_tdp_sim is
	constant DEPTH : positive := 2**A_BITS;
		subtype word_t	is std_logic_vector(D_BITS - 1 downto 0);
		type		ram_t		is array(0 to DEPTH - 1) of word_t;

	impure function [docs]ocram_InitMemory(FilePath : string) return ram_t is
		variable Memory		: T_SLM(DEPTH - 1 downto 0, word_t'range);
		variable res			: ram_t;
	begin
		if str_length(FilePath) = 0 then
			-- shortcut required by Vivado
			return (others => (others => ite(SIMULATION, 'U', '0')));
		elsif mem_FileExtension(FilePath) = "mem" then
			Memory	:= mem_ReadMemoryFile(FilePath, DEPTH, word_t'length, MEM_FILEFORMAT_XILINX_MEM, MEM_CONTENT_HEX);
		else
			Memory	:= mem_ReadMemoryFile(FilePath, DEPTH, word_t'length, MEM_FILEFORMAT_INTEL_HEX, MEM_CONTENT_HEX);
		end if;

		for i in Memory'range(1) loop
			for j in word_t'range loop
				res(i)(j)		:= Memory(i, j);
			end loop;
		end loop;
		return  res;
	end function;

	signal ram			: ram_t		:= ocram_InitMemory(FILENAME);

	-- write to memory, 'X' means maybe write
	signal write1 : X01;
	signal write2 : X01;

	-- read only from memory, 'X' means maybe read
	signal read1  : X01;
	signal read2  : X01;
begin
	assert SIMULATION report "This model is only for simulation." severity error;

	-- handle 'U' as 'X'
  write1 <= to_x01(ce1 and we1);
  read1  <= to_x01(ce1 and not we1);
  write2 <= to_x01(ce2 and we2);
  read2  <= to_x01(ce2 and not we2);

  process (clk1, clk2)
    -- Flag and address indicating whether a write occurs in the current clock
		-- cycle. Set and cleared at the rising_edge of the port's clock.
		-- The write address is set to don't care when the write location is
		-- undefined, to match all addresses in collision checks from other port.
    variable writing1 : boolean;
    variable writing2 : boolean;
    variable waddr1   : unsigned(A_BITS-1 downto 0);
    variable waddr2   : unsigned(A_BITS-1 downto 0);

		-- Check for write-collision check on port 1. Only set during one execution
		-- of the process.
		variable check_wr1 : boolean;

    -- Flag and address indicating whether a read occurs in the current clock
		-- cycle. Set and cleared at the rising_edge of the port's clock.
		-- In opposition to the writing flag, the reading flag is only set if the
		-- address is well known and the read succeeded at the rising clock edge.
		-- A read fails afterwards if a write happens during the read clock cycle.
    variable reading1 : boolean;
    variable reading2 : boolean;
    variable raddr1   : unsigned(A_BITS-1 downto 0);
    variable raddr2   : unsigned(A_BITS-1 downto 0);

	begin	-- process
		check_wr1 := false;

		-- Writing to Memory
		-- =========================================================================
		if rising_edge(clk1) then
			writing1 := false;
			waddr1   := (others => '-');

			if write1 = '1' then
				-- RAM is definitely written ...
				writing1 := true;
				if is_x(std_logic_vector(a1)) then
					-- ... but address is unknown
					ram <= (others => (others => 'X'));
				else
					--- ... and address is well known
					waddr1 := a1;
					ram(to_integer(a1)) <= to_ux01(d1);
					-- writing2 and waddr2 are not yet up-to-date, check for
					-- write-collision below
					check_wr1 := true;
				end if;
				-- same-port read during write: return new data
				q1 <= to_ux01(d1);

			elsif write1 = 'X' then
				-- RAM may be written ...
				writing1 := true;
				if is_x(std_logic_vector(a1)) then
					-- ... but address is unknown
					ram <= (others => (others => 'X'));
				else
					--- ... and address is well known
					waddr1 := a1;
					ram(to_integer(a1)) <= (others => 'X');
				end if;
				-- same-port read during write: unknown data
				q1 <= (others => 'X');
			end if;
		end if;

		-- Must be executed after write to port 1 due to write-collsion check
		if rising_edge(clk2) then
			writing2 := false;
			waddr2   := (others => '-');

			if write2 = '1' then
				-- RAM is definitely written ...
				writing2 := true;
				if is_x(std_logic_vector(a2)) then
					-- ... but address is unknown
					ram <= (others => (others => 'X'));
				else
					--- ... and address is well known
					waddr2 := a2;
					-- writing1 and waddr1 are up-to-date, check for write-collision
					if writing1 and std_match(waddr1, a2) then
						ram(to_integer(a2)) <= (others => 'X');
					else
						ram(to_integer(a2)) <= to_ux01(d2);
					end if;
				end if;
				-- same-port read during write: return new data
				q2 <= to_ux01(d2);

			elsif write2 = 'X' then
				-- RAM may be written ...
				writing2 := true;
				if is_x(std_logic_vector(a2)) then
					-- ... but address is unknown
					ram <= (others => (others => 'X'));
				else
					--- ... and address is well known
					waddr2 := a2;
					ram(to_integer(a2)) <= (others => 'X');
				end if;
				-- same-port read during write: unknown data
				q1 <= (others => 'X');
			end if;
		end if;

		-- writing1 and waddr1 are up-to-date, check for write-collision
		if check_wr1 then
			if writing2 and std_match(waddr2, a1) then
				ram(to_integer(a1)) <= (others => 'X');
			end if;
		end if;

		-- Reading (only) from Memory
		-- =========================================================================
		if rising_edge(clk1) then
			reading1 := false;
			raddr1   := (others => '-');

			if read1 = '1' then
				-- Definitely read only from RAM ...
				if is_x(std_logic_vector(a1)) then
					-- ... but address is unknown
					q1 <= (others => 'X');
				else
					-- check for mixed-port read-during-write
					if writing2 and std_match(a1,waddr2) then
						q1 <= (others => 'X');
					else
						-- further checks are only required if address is well known
						reading1 := true;
						raddr1   := a1;
						q1 <= ram(to_integer(a1));
					end if;
				end if;
			elsif read1 = 'X' then
				-- Maybe read only from RAM
				q1 <= (others => 'X');
			end if;
		end if;

		if rising_edge(clk2) then
			reading2 := false;
			raddr2   := (others => '-');

			if read2 = '1' then
				-- Definitely read only from RAM ...
				if is_x(std_logic_vector(a2)) then
					-- ... but address is unknown
					q2 <= (others => 'X');
				else
					-- check for mixed-port read-during-write
					if writing1 and std_match(a2,waddr1) then
						q2 <= (others => 'X');
					else
						-- further checks are only required if address is well known
						reading2 := true;
						raddr2   := a2;
						q2 <= ram(to_integer(a2));
					end if;
				end if;
			elsif read2 = 'X' then
				-- Maybe read only from RAM
				q2 <= (others => 'X');
			end if;
		end if;

    -- Write-during-read check
    -- =========================================================================
		-- cannot be included in read part above, because check is performed on a
		-- following rising edge of the write clock (not read clock!).
		if rising_edge(clk1) and writing1 then
			if reading2 and std_match(raddr2, waddr1) then
				-- read is disturbed by a write during the read clock cycle
				q2 <= (others => 'X');
			end if;
		end if;

		if rising_edge(clk2) and writing2 then
			if reading1 and std_match(raddr1, waddr2) then
				-- read is disturbed by a write during the read clock cycle
				q1 <= (others => 'X');
			end if;
		end if;
	end process;

end architecture;