`timescale 1ns / 1ps

//////////////////////////////////////////////////////////////////////////////////
//
// This file is part of the Next186 Soc PC project
// http://opencores.org/project,next186
//
// Filename: DSP32.v
// Description: Part of the Next186 SoC PC project, DSP coprocessor
// Version 1.0
// Creation date: Jan2015
//
// Author: Nicolae Dumitrache 
// e-mail: ndumitrache@opencores.org
//
/////////////////////////////////////////////////////////////////////////////////
// 
// Copyright (C) 2015 Nicolae Dumitrache
// 
// This source file may be used and distributed without 
// restriction provided that this copyright statement is not 
// removed from the file and that any derivative work contains 
// the original copyright notice and the associated disclaimer.
// 
// This source file is free software; you can redistribute it 
// and/or modify it under the terms of the GNU Lesser General 
// Public License as published by the Free Software Foundation;
// either version 2.1 of the License, or (at your option) any 
// later version. 
// 
// This source is distributed in the hope that it will be 
// useful, but WITHOUT ANY WARRANTY; without even the implied 
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
// PURPOSE. See the GNU Lesser General Public License for more 
// details. 
// 
// You should have received a copy of the GNU Lesser General 
// Public License along with this source; if not, download it 
// from http://www.opencores.org/lgpl.shtml 
// 
///////////////////////////////////////////////////////////////////////////////////
// Additional Comments: 
//
// 8 x 64integers overlapping data windows, over 256 integers 
// command: 16'b0c000vvvvvvvvvvv = set r/w pointer - 256 32bit integers, 2048 instructions. c=1 for code write, 0 for data read/write
// command: 16'b10wwwvvvvvvvvvvv = run ip - 2048 instructions, 3 bit data window offset
//
// Instructions:
// D=dest(0..63), S=src(0..63)
//	0 - MOV D,S [mov 0,0 = HALT, mov 8,8..mov 15,15 = set data window, other mov x,x = NOP, CF unaffected]
// 1 - SAR D,n	[n[2:0] = shift arythmetic right 1..8, CF <- D[0]]
//	2 - ADD D,S
//	3 - ADC D,S
// 4 - SUB D,S
// 5 - SBB D,S
// 6 - MULH D,S	[{D, CF} <- D*S >> 32]
// 7 - MULL D,S	[{D, CF} <- D*S]
// 8 - MULM	 D,S	[{D, CF} <- D*S >> 16]
// 9 - TOWORD D,S [D <- {WORD(D), WORD(S)}, CF unaffected] 
// 10- SHR D,n [n[2:0] = shift logic right 1..8, CF enters through left, CF <- D[0]]
// 11- AND D, S [CF <- 1]
// 12- OR D, S	[CF <- (D | S) != 0]
// 13- XOR D, S [CF <- !CF]
//////////////////////////////////////////////////////////////////////////////////
// Oct2016 - DSP is no more CPU synchronous, it may have a different clock
// The DSP clock may be no less than 1/2 CPU bus clock
// Sep2017 - Improved the pipeline by using WriteBeforeRead in module <regs>
//////////////////////////////////////////////////////////////////////////////////

module DSP32(
		input clkcpu,
		input clkdsp,
		input cmd,
		input ce,
		input wr,
		input [15:0]din,
		output [15:0]dout,
		output halt
);

	reg [10:0]wp = 0;	// write pointer
	reg [8:0]rp = 0;	// read pointer
	reg [10:0]ip = 0;  // instruction pointer
	reg [2:0]dwin = 0; // data window
	reg [2:0]dwin1 = 0;
	reg hi = 0;
	reg [15:0]lodata;
	reg [31:0]res;
	reg wcode = 0;
	reg [2:0]op;
	reg [2:0]rop;
	wire [15:0]instr;
	reg [15:0]instr1;
	reg [1:0]mulshift;
	reg run = 1'b0;
	wire signed [31:0]D;
	wire signed [31:0]S;
	reg signed [63:0]mul;// = D * S;
	wire [15:0]S16 = (~|S[31:15] | &S[31:15]) ? S[15:0] : {S[31], {15{!S[31]}}};
	wire [15:0]D16 = (~|D[31:15] | &D[31:15]) ? D[15:0] : {D[31], {15{!D[31]}}};
   wire [32:0]resmc = {mul, 1'b0} >> (mulshift[1] ? 16 : mulshift[0] ? 0 : 32);
	wire [16:0]cpuwdata;
	wire qbusempty;
	reg cpuw = 1'b0;
	wire extwrite = cpuw && !cpuwdata[16] && hi && !wcode;
   reg rhalt = 1'b0;
   reg [1:0]cpu_hcount = 2'b00;
   reg [1:0]dsp_hcount = 2'b00;
   reg [1:0]ishalt = 2'b00;
	reg rCF;
	wire CF = rop == 3 ? resmc[0] : rop == 6 ? |res : rCF;	// carry flag
	wire cin = (instr1[14] ^ (CF & instr1[12]));
	wire [32:0]sumdiff = D + ({32{instr1[14]}} ^ S) + cin;

	assign ihalt = ~|instr;
   assign halt = &ishalt;
	
	qdsp qbus
	(
	  .WrClock(clkcpu), // input wr_clk
	  .RdClock(clkdsp), // input rd_clk
	  .Data({cmd, din}), // input [16 : 0] din
	  .WrEn(ce && wr), // input wr_en
	  .RdEn(!qbusempty), // input rd_en
	  .Q(cpuwdata), // output [16 : 0] dout
	  .Full(), // output full
	  .Empty(qbusempty), // output empty	
	  .Reset(1'b0),
	  .RPReset(1'b0)
	);
	
	instrmem Code
	(
	  .WrClock(clkdsp), // input clka
	  .WrClockEn(1'b1),
	  .WE(cpuw && !cpuwdata[16] && wcode), // input [0 : 0] wea
	  .WrAddress(wp), // input [10 : 0] addra
	  .Data(cpuwdata[15:0]), // input [15 : 0] dina
	  .RdClock(clkdsp), // input clkb
	  .RdClockEn(run || (!ihalt && !extwrite)),
	  .RdAddress(ip), // input [10 : 0] addrb
	  .Q(instr), // output [15 : 0] doutb
	  .Reset(1'b0)
	);
	
	regs DSRegs
	(
		.clkcpu(clkcpu),
		.clkdsp(clkdsp),
		.we(!rhalt || extwrite),
		.rd(!extwrite),
		.wa(extwrite ? wp[7:0] : {dwin1 + instr1[11], instr1[10:6]}),
		.din_cpu({cpuwdata[15:0], lodata}), // valid if extwrite
		.din_alu(rop == 3'b011 ? resmc[32:1] : res),	// delayed 1T, used if !extwrite
		.rda({dwin + instr[11], instr[10:6]}),
		.D(D),
		.rsa({dwin + instr[5], instr[4:0]}),
		.S(S),
		.rra(rp),
		.dout(dout)
	);
	
	always @(posedge clkcpu) begin
		if(ce)
			if(cmd) begin
				if(wr) 
               if(din[15]) cpu_hcount <= cpu_hcount + 1'b1; // run
               else rp <= {din[7:0], 1'b0};	// set rp
			end else if(!wr) rp <= rp + 1'b1;
      ishalt <= {ishalt[0], cpu_hcount == dsp_hcount && ihalt};
	end


	always @(posedge clkdsp) begin
		cpuw <= !qbusempty;
		rop <= op;
		mul <= D * S;
		mulshift <= {instr1[15], instr1[12]};
		
		case(op)
			0: res <= S;
			1: res <=  $signed({instr1[12] ? D[31] : CF, D[30:0]}) >>> (instr1[2:0] + 1);
			2: res <= sumdiff[31:0];
//			3: {res, mc} = {mul, 1'b0} >> (instr1[15] ? 16 : instr1[12] ? 0 : 32);
			4: res <= {D16, S16};
			5: res <= S & D;
			6: res <= S | D;
			7: res <= S ^ D;
		endcase
		
		if(cpuw)
			if(cpuwdata[16]) begin // cmd
				if(!cpuwdata[15]) {hi, wcode, wp} <= {1'b0, cpuwdata[14], cpuwdata[10:0]};	// set rwp
			end else begin
				hi <= !hi;
				lodata <= cpuwdata[15:0];
				if(wcode || hi) wp <= wp + 1'b1;
			end
			
		if(cpuw && cpuwdata[16] && cpuwdata[15]) {run, dwin, ip} <= {1'b1, cpuwdata[13:0]}; // run
		else begin
			run <= 1'b0;
			if(!extwrite) begin
				ip <= ip + 1'b1;
				if({instr[15:9], instr[5:3]} == 10'b0000001001) dwin <= instr[2:0];
				dwin1 <= dwin;
			end
		end

      if(run) dsp_hcount <= dsp_hcount + 1'b1;
		
		if(!extwrite) begin // if not write external data
			rhalt <= ihalt;
			instr1 <= instr;
			
			case(instr[15:12])
				0: op <= 3'b000; // S
				1,10: op <= 3'b001; // SAR, SHR
				2,3,4,5: op <= 3'b010; // adder
				6,7,8: op <= 3'b011; // MULHI, MULLO, MULM
				9: op <= 3'b100; // TOWORD
				11: op <= 3'b101; // AND
				12: op <= 3'b110; // OR
				13: op <= 3'b111; // XOR
				default: op <= 3'bxxx;
			endcase

			case(op)
				1: rCF <= S[0];
				2: rCF <= sumdiff[32] ^ instr1[14];
//				3: rCF <= resmc[0];
				5: rCF <= 1'b1;	// and
//				6: rCF <= |res;	// or
				7: rCF <= !CF;	// xor
			endcase

		end
	end

endmodule


module regs(
	input clkcpu,
	input clkdsp,
	input we,
	input rd,
	input [7:0]wa,
	input [31:0]din_cpu, // valid if rd == 0
	input [31:0]din_alu, // delayed 1T, used if rd == 1
	input [7:0]rda,
	output reg [31:0]D,
	input [7:0]rsa,
	output reg [31:0]S,
	input [8:0]rra,
	output [15:0]dout
);
	
	reg [31:0]r[255:0];
	reg [31:0]rdin_cpu;
	reg [7:0]rwa;
	reg rwe = 1'b0;
	reg rrd = 1'b0;
	wire [31:0]wdata = rrd ? din_alu : rdin_cpu;

	datamem16 RdRegs
	(
	  .WrClock(clkdsp), // input clka
	  .WrClockEn(1'b1),
	  .WE(rwe), // input [0 : 0] wea
	  .WrAddress(rwa), // input [7 : 0] addra
	  .Data(wdata), // input [31 : 0] dina
	  .RdClock(clkcpu), // input clkb
	  .RdClockEn(1'b1),
	  .RdAddress(rra), // input [8 : 0] addrb
	  .Q(dout), // output [15 : 0] doutb);
	  .Reset(1'b0)
	);

	always @(posedge clkdsp) begin
		rdin_cpu <= din_cpu;
		rwa <= wa;
		rwe <= we;
		rrd <= rd;
		if(rwe) r[rwa] <= wdata; // write before read!!!
		if(rd) D <= /*(we && rda == wa) ? din :*/ r[rda];
		if(rd) S <= /*(we && rsa == wa) ? din :*/ r[rsa];
	end

endmodule

