xilinx: Add simulation model for DSP48 (Virtex 4).

This commit is contained in:
Marcin Kościelnicki 2019-11-21 13:05:30 +01:00 committed by Marcelina Kościelnicka
parent 7939727d14
commit 7e0e42f907
6 changed files with 534 additions and 45 deletions

View File

@ -2155,7 +2155,235 @@ assign PCOUT = P;
endmodule endmodule
// TODO: DSP48 (Virtex 4). module DSP48 (
input signed [17:0] A,
input signed [17:0] B,
input signed [47:0] C,
input signed [17:0] BCIN,
input signed [47:0] PCIN,
input CARRYIN,
input [6:0] OPMODE,
input SUBTRACT,
input [1:0] CARRYINSEL,
output signed [47:0] P,
output signed [17:0] BCOUT,
output signed [47:0] PCOUT,
(* clkbuf_sink *)
input CLK,
input CEA,
input CEB,
input CEC,
input CEM,
input CECARRYIN,
input CECINSUB,
input CECTRL,
input CEP,
input RSTA,
input RSTB,
input RSTC,
input RSTM,
input RSTCARRYIN,
input RSTCTRL,
input RSTP
);
parameter integer AREG = 1;
parameter integer BREG = 1;
parameter integer CREG = 1;
parameter integer MREG = 1;
parameter integer PREG = 1;
parameter integer CARRYINREG = 1;
parameter integer CARRYINSELREG = 1;
parameter integer OPMODEREG = 1;
parameter integer SUBTRACTREG = 1;
parameter B_INPUT = "DIRECT";
parameter LEGACY_MODE = "MULT18X18S";
wire signed [17:0] A_OUT;
wire signed [17:0] B_OUT;
wire signed [47:0] C_OUT;
wire signed [35:0] M_MULT;
wire signed [35:0] M_OUT;
wire signed [47:0] P_IN;
wire [6:0] OPMODE_OUT;
wire [1:0] CARRYINSEL_OUT;
wire CARRYIN_OUT;
wire SUBTRACT_OUT;
reg INT_CARRYIN_XY;
reg INT_CARRYIN_Z;
reg signed [47:0] XMUX;
reg signed [47:0] YMUX;
wire signed [47:0] XYMUX;
reg signed [47:0] ZMUX;
reg CIN;
// The B input multiplexer.
wire signed [17:0] B_MUX;
assign B_MUX = (B_INPUT == "DIRECT") ? B : BCIN;
// The cascade output.
assign BCOUT = B_OUT;
assign PCOUT = P;
// The registers.
reg signed [17:0] A0_REG;
reg signed [17:0] A1_REG;
reg signed [17:0] B0_REG;
reg signed [17:0] B1_REG;
reg signed [47:0] C_REG;
reg signed [35:0] M_REG;
reg signed [47:0] P_REG;
reg [6:0] OPMODE_REG;
reg [1:0] CARRYINSEL_REG;
reg SUBTRACT_REG;
reg CARRYIN_REG;
reg INT_CARRYIN_XY_REG;
initial begin
A0_REG = 0;
A1_REG = 0;
B0_REG = 0;
B1_REG = 0;
C_REG = 0;
M_REG = 0;
P_REG = 0;
OPMODE_REG = 0;
CARRYINSEL_REG = 0;
SUBTRACT_REG = 0;
CARRYIN_REG = 0;
INT_CARRYIN_XY_REG = 0;
end
always @(posedge CLK) begin
if (RSTA) begin
A0_REG <= 0;
A1_REG <= 0;
end else if (CEA) begin
A0_REG <= A;
A1_REG <= A0_REG;
end
if (RSTB) begin
B0_REG <= 0;
B1_REG <= 0;
end else if (CEB) begin
B0_REG <= B_MUX;
B1_REG <= B0_REG;
end
if (RSTC) begin
C_REG <= 0;
end else if (CEC) begin
C_REG <= C;
end
if (RSTM) begin
M_REG <= 0;
end else if (CEM) begin
M_REG <= M_MULT;
end
if (RSTP) begin
P_REG <= 0;
end else if (CEP) begin
P_REG <= P_IN;
end
if (RSTCTRL) begin
OPMODE_REG <= 0;
CARRYINSEL_REG <= 0;
SUBTRACT_REG <= 0;
end else begin
if (CECTRL) begin
OPMODE_REG <= OPMODE;
CARRYINSEL_REG <= CARRYINSEL;
end
if (CECINSUB)
SUBTRACT_REG <= SUBTRACT;
end
if (RSTCARRYIN) begin
CARRYIN_REG <= 0;
INT_CARRYIN_XY_REG <= 0;
end else begin
if (CECINSUB)
CARRYIN_REG <= CARRYIN;
if (CECARRYIN)
INT_CARRYIN_XY_REG <= INT_CARRYIN_XY;
end
end
// The register enables.
assign A_OUT = (AREG == 2) ? A1_REG : (AREG == 1) ? A0_REG : A;
assign B_OUT = (BREG == 2) ? B1_REG : (BREG == 1) ? B0_REG : B_MUX;
assign C_OUT = (CREG == 1) ? C_REG : C;
assign M_OUT = (MREG == 1) ? M_REG : M_MULT;
assign P = (PREG == 1) ? P_REG : P_IN;
assign OPMODE_OUT = (OPMODEREG == 1) ? OPMODE_REG : OPMODE;
assign SUBTRACT_OUT = (SUBTRACTREG == 1) ? SUBTRACT_REG : SUBTRACT;
assign CARRYINSEL_OUT = (CARRYINSELREG == 1) ? CARRYINSEL_REG : CARRYINSEL;
assign CARRYIN_OUT = (CARRYINREG == 1) ? CARRYIN_REG : CARRYIN;
// The multiplier.
assign M_MULT = A_OUT * B_OUT;
// The post-adder inputs.
always @* begin
case (OPMODE_OUT[1:0])
2'b00: XMUX <= 0;
2'b10: XMUX <= P;
2'b11: XMUX <= {{12{A_OUT[17]}}, A_OUT, B_OUT};
default: XMUX <= 48'hxxxxxxxxxxxx;
endcase
case (OPMODE_OUT[1:0])
2'b01: INT_CARRYIN_XY <= A_OUT[17] ~^ B_OUT[17];
2'b11: INT_CARRYIN_XY <= ~A_OUT[17];
// TODO: not tested in hardware.
default: INT_CARRYIN_XY <= A_OUT[17] ~^ B_OUT[17];
endcase
end
always @* begin
case (OPMODE_OUT[3:2])
2'b00: YMUX <= 0;
2'b11: YMUX <= C_OUT;
default: YMUX <= 48'hxxxxxxxxxxxx;
endcase
end
assign XYMUX = (OPMODE_OUT[3:0] == 4'b0101) ? M_OUT : (XMUX + YMUX);
always @* begin
case (OPMODE_OUT[6:4])
3'b000: ZMUX <= 0;
3'b001: ZMUX <= PCIN;
3'b010: ZMUX <= P;
3'b011: ZMUX <= C_OUT;
3'b101: ZMUX <= {{17{PCIN[47]}}, PCIN[47:17]};
3'b110: ZMUX <= {{17{P[47]}}, P[47:17]};
default: ZMUX <= 48'hxxxxxxxxxxxx;
endcase
// TODO: check how all this works on actual hw.
if (OPMODE_OUT[1:0] == 2'b10)
INT_CARRYIN_Z <= ~P[47];
else
case (OPMODE_OUT[6:4])
3'b001: INT_CARRYIN_Z <= ~PCIN[47];
3'b010: INT_CARRYIN_Z <= ~P[47];
3'b101: INT_CARRYIN_Z <= ~PCIN[47];
3'b110: INT_CARRYIN_Z <= ~P[47];
default: INT_CARRYIN_Z <= 1'bx;
endcase
end
always @* begin
case (CARRYINSEL_OUT)
2'b00: CIN <= CARRYIN_OUT;
2'b01: CIN <= INT_CARRYIN_Z;
2'b10: CIN <= INT_CARRYIN_XY;
2'b11: CIN <= INT_CARRYIN_XY_REG;
default: CIN <= 1'bx;
endcase
end
// The post-adder.
assign P_IN = SUBTRACT_OUT ? (ZMUX - (XYMUX + CIN)) : (ZMUX + XYMUX + CIN);
endmodule
// TODO: DSP48E (Virtex 5). // TODO: DSP48E (Virtex 5).

View File

@ -209,7 +209,7 @@ CELLS = [
# Cell('MULT18X18SIO', port_attrs={'CLK': ['clkbuf_sink']}), # Spartan 3E # Cell('MULT18X18SIO', port_attrs={'CLK': ['clkbuf_sink']}), # Spartan 3E
# Cell('DSP48A', port_attrs={'CLK': ['clkbuf_sink']}), # Spartan 3A DSP # Cell('DSP48A', port_attrs={'CLK': ['clkbuf_sink']}), # Spartan 3A DSP
# Cell('DSP48A1', port_attrs={'CLK': ['clkbuf_sink']}), # Spartan 6 # Cell('DSP48A1', port_attrs={'CLK': ['clkbuf_sink']}), # Spartan 6
Cell('DSP48', port_attrs={'CLK': ['clkbuf_sink']}), # Virtex 4 # Cell('DSP48', port_attrs={'CLK': ['clkbuf_sink']}), # Virtex 4
Cell('DSP48E', port_attrs={'CLK': ['clkbuf_sink']}), # Virtex 5 Cell('DSP48E', port_attrs={'CLK': ['clkbuf_sink']}), # Virtex 5
#Cell('DSP48E1', port_attrs={'CLK': ['clkbuf_sink']}), # Virtex 6 / Series 7 #Cell('DSP48E1', port_attrs={'CLK': ['clkbuf_sink']}), # Virtex 6 / Series 7
Cell('DSP48E2', port_attrs={'CLK': ['clkbuf_sink']}), # Ultrascale Cell('DSP48E2', port_attrs={'CLK': ['clkbuf_sink']}), # Ultrascale

View File

@ -5476,49 +5476,6 @@ module URAM288_BASE (...);
input SLEEP; input SLEEP;
endmodule endmodule
module DSP48 (...);
parameter integer AREG = 1;
parameter integer BREG = 1;
parameter B_INPUT = "DIRECT";
parameter integer CARRYINREG = 1;
parameter integer CARRYINSELREG = 1;
parameter integer CREG = 1;
parameter LEGACY_MODE = "MULT18X18S";
parameter integer MREG = 1;
parameter integer OPMODEREG = 1;
parameter integer PREG = 1;
parameter integer SUBTRACTREG = 1;
output [17:0] BCOUT;
output [47:0] P;
output [47:0] PCOUT;
input [17:0] A;
input [17:0] B;
input [17:0] BCIN;
input [47:0] C;
input CARRYIN;
input [1:0] CARRYINSEL;
input CEA;
input CEB;
input CEC;
input CECARRYIN;
input CECINSUB;
input CECTRL;
input CEM;
input CEP;
(* clkbuf_sink *)
input CLK;
input [6:0] OPMODE;
input [47:0] PCIN;
input RSTA;
input RSTB;
input RSTC;
input RSTCARRYIN;
input RSTCTRL;
input RSTM;
input RSTP;
input SUBTRACT;
endmodule
module DSP48E (...); module DSP48E (...);
parameter SIM_MODE = "SAFE"; parameter SIM_MODE = "SAFE";
parameter integer ACASCREG = 1; parameter integer ACASCREG = 1;

View File

@ -12,4 +12,7 @@ test_dsp48a_model_ref.v
test_dsp48a1_model_ref.v test_dsp48a1_model_ref.v
test_dsp48a1_model_uut.v test_dsp48a1_model_uut.v
test_dsp48a1_model test_dsp48a1_model
test_dsp48_model_ref.v
test_dsp48_model_uut.v
test_dsp48_model
*.vcd *.vcd

View File

@ -0,0 +1,14 @@
#!/bin/bash
set -ex
if [ -z $ISE_DIR ]; then
ISE_DIR=/opt/Xilinx/ISE/14.7
fi
sed 's/DSP48 /DSP48_UUT /; /DSP48_UUT/,/endmodule/ p; d;' < ../cells_sim.v > test_dsp48_model_uut.v
if [ ! -f "test_dsp48_model_ref.v" ]; then
cp $ISE_DIR/ISE_DS/ISE/verilog/src/unisims/DSP48.v test_dsp48_model_ref.v
fi
for tb in mult_allreg mult_noreg mult_inreg
do
iverilog -s $tb -s glbl -o test_dsp48_model test_dsp48_model.v test_dsp48_model_uut.v test_dsp48_model_ref.v $ISE_DIR/ISE_DS/ISE/verilog/src/glbl.v
vvp -N ./test_dsp48_model
done

View File

@ -0,0 +1,287 @@
`timescale 1ns / 1ps
module testbench;
parameter integer AREG = 1;
parameter integer BREG = 1;
parameter integer CREG = 1;
parameter integer MREG = 1;
parameter integer PREG = 1;
parameter integer CARRYINREG = 1;
parameter integer CARRYINSELREG = 1;
parameter integer OPMODEREG = 1;
parameter integer SUBTRACTREG = 1;
parameter B_INPUT = "DIRECT";
parameter LEGACY_MODE = "NONE";
reg CLK;
reg CEA, CEB, CEC, CEM, CEP, CECARRYIN, CECINSUB, CECTRL;
reg RSTA, RSTB, RSTC, RSTM, RSTP, RSTCARRYIN, RSTCTRL;
reg [17:0] A;
reg [17:0] B;
reg [47:0] C;
reg [17:0] BCIN;
reg [47:0] PCIN;
reg CARRYIN;
reg [6:0] OPMODE;
reg SUBTRACT;
reg [1:0] CARRYINSEL;
output [47:0] P, REF_P;
output [17:0] BCOUT, REF_BCOUT;
output [47:0] PCOUT, REF_PCOUT;
integer errcount = 0;
reg ERROR_FLAG = 0;
task clkcycle;
begin
#5;
CLK = ~CLK;
#10;
CLK = ~CLK;
#2;
ERROR_FLAG = 0;
if (REF_BCOUT !== BCOUT) begin
$display("ERROR at %1t: REF_BCOUT=%b UUT_BCOUT=%b DIFF=%b", $time, REF_BCOUT, BCOUT, REF_BCOUT ^ BCOUT);
errcount = errcount + 1;
ERROR_FLAG = 1;
end
if (REF_P !== P) begin
$display("ERROR at %1t: REF_P=%b UUT_P=%b DIFF=%b", $time, REF_P, P, REF_P ^ P);
errcount = errcount + 1;
ERROR_FLAG = 1;
end
if (REF_PCOUT !== PCOUT) begin
$display("ERROR at %1t: REF_PCOUT=%b UUT_PCOUT=%b DIFF=%b", $time, REF_PCOUT, PCOUT, REF_PCOUT ^ PCOUT);
errcount = errcount + 1;
ERROR_FLAG = 1;
end
#3;
end
endtask
reg config_valid = 0;
task drc;
begin
config_valid = 1;
if (OPMODE[1:0] == 2'b10 && PREG != 1) config_valid = 0;
if (OPMODE[1:0] == 2'b00 && CARRYINSEL == 2'b10) config_valid = 0;
if (OPMODE[1:0] == 2'b10 && CARRYINSEL == 2'b10) config_valid = 0;
if (OPMODE[1:0] == 2'b00 && CARRYINSEL == 2'b11) config_valid = 0;
if (OPMODE[1:0] == 2'b10 && CARRYINSEL == 2'b11) config_valid = 0;
if (OPMODE[3:2] == 2'b10) config_valid = 0;
if ((OPMODE[3:2] == 2'b01) ^ (OPMODE[1:0] == 2'b01) == 1'b1) config_valid = 0;
if ((OPMODE[6:4] == 3'b010 || OPMODE[6:4] == 3'b110) && PREG != 1) config_valid = 0;
if (OPMODE[6:4] == 3'b100) config_valid = 0;
if (OPMODE[6:4] == 3'b111) config_valid = 0;
if (OPMODE[6:4] == 3'b000 && CARRYINSEL == 2'b01) config_valid = 0;
if (OPMODE[6:4] == 3'b011 && CARRYINSEL == 2'b01) config_valid = 0;
// Xilinx models consider these combinations invalid for an unknown reason.
if (CARRYINSEL == 2'b01 && OPMODE[3:2] == 2'b00) config_valid = 0;
if (CARRYINSEL == 2'b10 && OPMODE == 7'b0000011) config_valid = 0;
if (CARRYINSEL == 2'b10 && OPMODE == 7'b0000101) config_valid = 0;
if (CARRYINSEL == 2'b10 && OPMODE == 7'b0100011) config_valid = 0;
if (CARRYINSEL == 2'b10 && OPMODE == 7'b0111111) config_valid = 0;
if (CARRYINSEL == 2'b10 && OPMODE == 7'b1100011) config_valid = 0;
if (CARRYINSEL == 2'b11 && OPMODE == 7'b0000011) config_valid = 0;
if (CARRYINSEL == 2'b11 && OPMODE == 7'b0000101) config_valid = 0;
if (CARRYINSEL == 2'b11 && OPMODE == 7'b0011111) config_valid = 0;
if (CARRYINSEL == 2'b11 && OPMODE == 7'b0010011) config_valid = 0;
if (CARRYINSEL == 2'b11 && OPMODE == 7'b0100011) config_valid = 0;
if (CARRYINSEL == 2'b11 && OPMODE == 7'b0100101) config_valid = 0;
if (CARRYINSEL == 2'b11 && OPMODE == 7'b0101111) config_valid = 0;
if (CARRYINSEL == 2'b11 && OPMODE == 7'b0110011) config_valid = 0;
if (CARRYINSEL == 2'b11 && OPMODE == 7'b0111111) config_valid = 0;
if (CARRYINSEL == 2'b11 && OPMODE == 7'b1010011) config_valid = 0;
if (CARRYINSEL == 2'b11 && OPMODE == 7'b1011111) config_valid = 0;
if (CARRYINSEL == 2'b11 && OPMODE == 7'b1100011) config_valid = 0;
if (CARRYINSEL == 2'b11 && OPMODE == 7'b1100101) config_valid = 0;
if (CARRYINSEL == 2'b11 && OPMODE == 7'b1101111) config_valid = 0;
if (CARRYINSEL == 2'b10 && OPMODE[3:0] == 4'b0101 && MREG == 1) config_valid = 0;
if (CARRYINSEL == 2'b11 && OPMODE[3:0] == 4'b0101 && MREG == 0) config_valid = 0;
end
endtask
initial begin
$dumpfile("test_dsp48_model.vcd");
$dumpvars(0, testbench);
#2;
CLK = 1'b0;
{CEA, CEB, CEC, CEM, CEP, CECARRYIN, CECINSUB, CECTRL} = 8'b11111111;
{A, B, C, PCIN, OPMODE, SUBTRACT, CARRYIN, CARRYINSEL} = 0;
{RSTA, RSTB, RSTC, RSTM, RSTP, RSTCARRYIN, RSTCTRL} = 7'b1111111;
repeat (10) begin
#10;
CLK = 1'b1;
#10;
CLK = 1'b0;
#10;
CLK = 1'b1;
#10;
CLK = 1'b0;
end
{RSTA, RSTB, RSTC, RSTM, RSTP, RSTCARRYIN, RSTCTRL} = 0;
repeat (100000) begin
clkcycle;
config_valid = 0;
while (!config_valid) begin
A = $urandom;
B = $urandom;
C = {$urandom, $urandom};
BCIN = $urandom;
PCIN = {$urandom, $urandom};
{CEA, CEB, CEC, CEM, CEP, CECARRYIN, CECINSUB, CECTRL} = $urandom | $urandom | $urandom;
{RSTA, RSTB, RSTC, RSTM, RSTP, RSTCARRYIN, RSTCTRL} = $urandom & $urandom & $urandom & $urandom & $urandom & $urandom;
{CARRYIN, CARRYINSEL, OPMODE, SUBTRACT} = $urandom;
drc;
end
end
if (errcount == 0) begin
$display("All tests passed.");
$finish;
end else begin
$display("Caught %1d errors.", errcount);
$stop;
end
end
DSP48 #(
.AREG (AREG),
.BREG (BREG),
.CREG (CREG),
.MREG (MREG),
.PREG (PREG),
.CARRYINREG (CARRYINREG),
.CARRYINSELREG (CARRYINSELREG),
.OPMODEREG (OPMODEREG),
.SUBTRACTREG (SUBTRACTREG),
.B_INPUT (B_INPUT),
.LEGACY_MODE (LEGACY_MODE)
) ref (
.A (A),
.B (B),
.C (C),
.BCIN (BCIN),
.PCIN (PCIN),
.CARRYIN (CARRYIN),
.OPMODE (OPMODE),
.SUBTRACT (SUBTRACT),
.CARRYINSEL (CARRYINSEL),
.BCOUT (REF_BCOUT),
.P (REF_P),
.PCOUT (REF_PCOUT),
.CEA (CEA),
.CEB (CEB),
.CEC (CEC),
.CEM (CEM),
.CEP (CEP),
.CECARRYIN (CECARRYIN),
.CECINSUB (CECINSUB),
.CECTRL (CECTRL),
.CLK (CLK),
.RSTA (RSTA),
.RSTB (RSTB),
.RSTC (RSTC),
.RSTM (RSTM),
.RSTP (RSTP),
.RSTCARRYIN (RSTCARRYIN),
.RSTCTRL (RSTCTRL)
);
DSP48_UUT #(
.AREG (AREG),
.BREG (BREG),
.CREG (CREG),
.MREG (MREG),
.PREG (PREG),
.CARRYINREG (CARRYINREG),
.CARRYINSELREG (CARRYINSELREG),
.OPMODEREG (OPMODEREG),
.SUBTRACTREG (SUBTRACTREG),
.B_INPUT (B_INPUT),
.LEGACY_MODE (LEGACY_MODE)
) uut (
.A (A),
.B (B),
.C (C),
.BCIN (BCIN),
.PCIN (PCIN),
.CARRYIN (CARRYIN),
.OPMODE (OPMODE),
.SUBTRACT (SUBTRACT),
.CARRYINSEL (CARRYINSEL),
.BCOUT (BCOUT),
.P (P),
.PCOUT (PCOUT),
.CEA (CEA),
.CEB (CEB),
.CEC (CEC),
.CEM (CEM),
.CEP (CEP),
.CECARRYIN (CECARRYIN),
.CECINSUB (CECINSUB),
.CECTRL (CECTRL),
.CLK (CLK),
.RSTA (RSTA),
.RSTB (RSTB),
.RSTC (RSTC),
.RSTM (RSTM),
.RSTP (RSTP),
.RSTCARRYIN (RSTCARRYIN),
.RSTCTRL (RSTCTRL)
);
endmodule
module mult_noreg;
testbench #(
.AREG (0),
.BREG (0),
.CREG (0),
.MREG (0),
.PREG (0),
.CARRYINREG (0),
.CARRYINSELREG (0),
.OPMODEREG (0),
.SUBTRACTREG (0),
.B_INPUT ("DIRECT")
) testbench ();
endmodule
module mult_allreg;
testbench #(
.AREG (1),
.BREG (1),
.CREG (1),
.MREG (1),
.PREG (1),
.CARRYINREG (1),
.CARRYINSELREG (1),
.OPMODEREG (1),
.SUBTRACTREG (1),
.B_INPUT ("CASCADE")
) testbench ();
endmodule
module mult_inreg;
testbench #(
.AREG (1),
.BREG (1),
.CREG (1),
.MREG (0),
.PREG (0),
.CARRYINREG (1),
.CARRYINSELREG (0),
.OPMODEREG (0),
.SUBTRACTREG (0),
.B_INPUT ("DIRECT")
) testbench ();
endmodule