// Copyright 2020-2022 F4PGA Authors
//
// 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.
//
// SPDX-License-Identifier: Apache-2.0

`timescale 1ns/1ps

`define STRINGIFY(x) `"x`"

module TB;
	localparam PERIOD = 50;
	localparam ADDR_INCR = 1;

	reg clk_a;
	reg rce_a;
	reg [`ADDR_WIDTH-1:0] ra_a;
	wire [`DATA_WIDTH-1:0] rq_a;
	reg wce_a;
	reg [`ADDR_WIDTH-1:0] wa_a;
	reg [`DATA_WIDTH-1:0] wd_a;

	reg clk_b;
	reg rce_b;
	reg [`ADDR_WIDTH-1:0] ra_b;
	wire [`DATA_WIDTH-1:0] rq_b;
	reg wce_b;
	reg [`ADDR_WIDTH-1:0] wa_b;
	reg [`DATA_WIDTH-1:0] wd_b;


	initial clk_a = 0;
	initial clk_b = 0;
	initial ra_a = 0;
	initial ra_b = 0;
	initial rce_a = 0;
	initial rce_b = 0;
	initial forever #(PERIOD / 2.0) clk_a = ~clk_a;
	initial begin
		#(PERIOD / 4.0);
		forever #(PERIOD / 2.0) clk_b = ~clk_b;
	end
	initial begin
		$dumpfile(`STRINGIFY(`VCD));
		$dumpvars;
	end

	integer a;
	integer b;

	reg done_a;
	reg done_b;
	initial done_a = 1'b0;
	initial done_b = 1'b0;
	wire done_sim = done_a & done_b;

	reg [`DATA_WIDTH-1:0] expected_a;
	reg [`DATA_WIDTH-1:0] expected_b;

	always @(posedge clk_a) begin
		expected_a <= (a | (a << 20) | 20'h55000) & {`DATA_WIDTH{1'b1}};
	end
	always @(posedge clk_b) begin
		expected_b <= (b | (b << 20) | 20'h55000) & {`DATA_WIDTH{1'b1}};
	end

	wire error_a = a != 0 ? rq_a !== expected_a : 0;
	wire error_b = b != (1<<`ADDR_WIDTH) / 2 ? rq_b !== expected_b : 0;

	integer error_a_cnt = 0;
	integer error_b_cnt = 0;

	always @ (posedge clk_a)
	begin
		if (error_a)
			error_a_cnt <= error_a_cnt + 1'b1;
	end
	always @ (posedge clk_b)
	begin
		if (error_b)
			error_b_cnt <= error_b_cnt + 1'b1;
	end
	// PORT A
	initial #(1) begin
		// Write data
		for (a = 0; a < (1<<`ADDR_WIDTH) / 2; a = a + ADDR_INCR) begin
			@(negedge clk_a) begin
				wa_a = a;
				wd_a = a | (a << 20) | 20'h55000;
				wce_a = 1;
			end
			@(posedge clk_a) begin
				#(PERIOD/10) wce_a = 0;
			end
		end
		// Read data
		for (a = 0; a < (1<<`ADDR_WIDTH) / 2; a = a + ADDR_INCR) begin
			@(negedge clk_a) begin
				ra_a = a;
				rce_a = 1;
			end
			@(posedge clk_a) begin
				#(PERIOD/10) rce_a = 0;
				if ( rq_a !== expected_a) begin
					$display("%d: PORT A: FAIL: mismatch act=%x exp=%x at %x", $time, rq_a, expected_a, a);
				end else begin
					$display("%d: PORT A: OK: act=%x exp=%x at %x", $time, rq_a, expected_a, a);
				end
			end
		end
		done_a = 1'b1;
	end

	// PORT B
	initial #(1) begin
		// Write data
		for (b = (1<<`ADDR_WIDTH) / 2; b < (1<<`ADDR_WIDTH); b = b + ADDR_INCR) begin
			@(negedge clk_b) begin
				wa_b = b;
				wd_b = b | (b << 20) | 20'h55000;
				wce_b = 1;
			end
			@(posedge clk_b) begin
				#(PERIOD/10) wce_b = 0;
			end
		end
		// Read data
		for (b = (1<<`ADDR_WIDTH) / 2; b < (1<<`ADDR_WIDTH); b = b + ADDR_INCR) begin
			@(negedge clk_b) begin
				ra_b = b;
				rce_b = 1;
			end
			@(posedge clk_b) begin
				#(PERIOD/10) rce_b = 0;
				if ( rq_b !== expected_b) begin
					$display("%d: PORT B: FAIL: mismatch act=%x exp=%x at %x", $time, rq_b, expected_b, b);
				end else begin
					$display("%d: PORT B: OK: act=%x exp=%x at %x", $time, rq_b, expected_b, b);
				end
			end
		end
		done_b = 1'b1;
	end

	// Scan for simulation finish
	always @(posedge clk_a, posedge clk_b) begin
		if (done_sim)
			$finish_and_return( (error_a_cnt == 0 & error_b_cnt == 0) ? 0 : -1 );
	end

	case (`STRINGIFY(`TOP))
		"BRAM_TDP_32x512": begin
			BRAM_TDP_32x512 #() bram (
				.clk_a(clk_a),
				.rce_a(rce_a),
				.ra_a(ra_a),
				.rq_a(rq_a),
				.wce_a(wce_a),
				.wa_a(wa_a),
				.wd_a(wd_a),
				.clk_b(clk_b),
				.rce_b(rce_b),
				.ra_b(ra_b),
				.rq_b(rq_b),
				.wce_b(wce_b),
				.wa_b(wa_b),
				.wd_b(wd_b)
			);
		end
		"BRAM_TDP_16x1024": begin
			BRAM_TDP_16x1024 #() bram (
				.clk_a(clk_a),
				.rce_a(rce_a),
				.ra_a(ra_a),
				.rq_a(rq_a),
				.wce_a(wce_a),
				.wa_a(wa_a),
				.wd_a(wd_a),
				.clk_b(clk_b),
				.rce_b(rce_b),
				.ra_b(ra_b),
				.rq_b(rq_b),
				.wce_b(wce_b),
				.wa_b(wa_b),
				.wd_b(wd_b)
			);
		end
		"BRAM_TDP_8x2048": begin
			BRAM_TDP_8x2048 #() bram (
				.clk_a(clk_a),
				.rce_a(rce_a),
				.ra_a(ra_a),
				.rq_a(rq_a),
				.wce_a(wce_a),
				.wa_a(wa_a),
				.wd_a(wd_a),
				.clk_b(clk_b),
				.rce_b(rce_b),
				.ra_b(ra_b),
				.rq_b(rq_b),
				.wce_b(wce_b),
				.wa_b(wa_b),
				.wd_b(wd_b)
			);
		end
		"BRAM_TDP_4x4096": begin
			BRAM_TDP_4x4096 #() bram (
				.clk_a(clk_a),
				.rce_a(rce_a),
				.ra_a(ra_a),
				.rq_a(rq_a),
				.wce_a(wce_a),
				.wa_a(wa_a),
				.wd_a(wd_a),
				.clk_b(clk_b),
				.rce_b(rce_b),
				.ra_b(ra_b),
				.rq_b(rq_b),
				.wce_b(wce_b),
				.wa_b(wa_b),
				.wd_b(wd_b)
			);
		end
	endcase
endmodule
