1
0
mirror of https://github.com/Gehstock/Mist_FPGA.git synced 2026-02-13 11:24:06 +00:00
Files
Gehstock.Mist_FPGA/common/CPU/68000/FX68k/excUnit.sv
2019-07-22 23:42:05 +02:00

554 lines
14 KiB
Systemverilog

/*
Execution unit
Executes register transfers set by the microcode. Originally through a set of bidirectional buses.
Most sources are available at T3, but DBIN only at T4! CCR also might be updated at T4, but it is not connected to these buses.
We mux at T1 and T2, then transfer to the destination at T3. The exception is AOB that need to be updated earlier.
*/
module excUnit( input s_clks Clks,
input enT1, enT2, enT3, enT4,
input s_nanod Nanod, input s_irdecod Irdecod,
input [15:0] Ird, // ALU row (and others) decoder needs it
input pswS,
input [15:0] ftu,
input [15:0] iEdb,
output logic [7:0] ccr,
output [15:0] alue,
output prenEmpty, au05z,
output logic dcr4, ze,
output logic aob0,
output [15:0] AblOut,
output logic [15:0] Irc,
output logic [15:0] oEdb,
output logic [23:1] eab);
localparam REG_USP = 15;
localparam REG_SSP = 16;
localparam REG_DT = 17;
// Register file
reg [15:0] regs68L[ 18];
reg [15:0] regs68H[ 18];
// synthesis translate off
/*
It is bad practice to initialize simulation registers that the hardware doesn't.
There is risk that simulation would be different than the real hardware. But in this case is the other way around.
Some ROM uses something like sub.l An,An at powerup which clears the register
Simulator power ups the registers with 'X, as they are really undetermined at the real hardware.
But the simulator doesn't realize (it can't) that the same value is substracting from itself,
and that the result should be zero even when it's 'X - 'X.
*/
initial begin
for( int i = 0; i < 18; i++) begin
regs68L[i] <= '0;
regs68H[i] <= '0;
end
end
// For simulation display only
wire [31:0] SSP = { regs68H[REG_SSP], regs68L[REG_SSP]};
// synthesis translate on
wire [15:0] aluOut;
wire [15:0] dbin;
logic [15:0] dcrOutput;
reg [15:0] PcL, PcH;
reg [31:0] auReg, aob;
reg [15:0] Ath, Atl;
// Bus execution
reg [15:0] Dbl, Dbh;
reg [15:0] Abh, Abl;
reg [15:0] Abd, Dbd;
assign AblOut = Abl;
assign au05z = (~| auReg[5:0]);
logic [15:0] dblMux, dbhMux;
logic [15:0] abhMux, ablMux;
logic [15:0] abdMux, dbdMux;
logic abdIsByte;
logic Pcl2Dbl, Pch2Dbh;
logic Pcl2Abl, Pch2Abh;
// RX RY muxes
// RX and RY actual registers
logic [4:0] actualRx, actualRy;
logic [3:0] movemRx;
logic byteNotSpAlign; // Byte instruction and no sp word align
// IRD decoded signals must be latched. See comments on decoder
// But nanostore decoding can't be latched before T4.
//
// If we need this earlier we can register IRD decode on T3 and use nano async
logic [4:0] rxMux, ryMux;
logic [3:0] rxReg, ryReg;
logic rxIsSp, ryIsSp;
logic rxIsAreg, ryIsAreg;
always_comb begin
// Unique IF !!
if( Nanod.ssp) begin
rxMux = REG_SSP;
rxIsSp = 1'b1;
rxReg = 1'bX;
end
else if( Irdecod.rxIsUsp) begin
rxMux = REG_USP;
rxIsSp = 1'b1;
rxReg = 1'bX;
end
else if( Irdecod.rxIsDt & !Irdecod.implicitSp) begin
rxMux = REG_DT;
rxIsSp = 1'b0;
rxReg = 1'bX;
end
else begin
if( Irdecod.implicitSp)
rxReg = 15;
else if( Irdecod.rxIsMovem)
rxReg = movemRx;
else
rxReg = { Irdecod.rxIsAreg, Irdecod.rx};
if( (& rxReg)) begin
rxMux = pswS ? REG_SSP : 15;
rxIsSp = 1'b1;
end
else begin
rxMux = { 1'b0, rxReg};
rxIsSp = 1'b0;
end
end
// RZ has higher priority!
if( Irdecod.ryIsDt & !Nanod.rz) begin
ryMux = REG_DT;
ryIsSp = 1'b0;
ryReg = 'X;
end
else begin
ryReg = Nanod.rz ? Irc[15:12] : {Irdecod.ryIsAreg, Irdecod.ry};
ryIsSp = (& ryReg);
if( ryIsSp & pswS) // No implicit SP on RY
ryMux = REG_SSP;
else
ryMux = { 1'b0, ryReg};
end
end
always_ff @( posedge Clks.clk) begin
if( enT4) begin
byteNotSpAlign <= Irdecod.isByte & ~(Nanod.rxlDbl ? rxIsSp : ryIsSp);
actualRx <= rxMux;
actualRy <= ryMux;
rxIsAreg <= rxIsSp | rxMux[3];
ryIsAreg <= ryIsSp | ryMux[3];
end
if( enT4)
abdIsByte <= Nanod.abdIsByte & Irdecod.isByte;
end
// Set RX/RY low word to which bus segment is connected.
wire ryl2Abl = Nanod.ryl2ab & (ryIsAreg | Nanod.ablAbd);
wire ryl2Abd = Nanod.ryl2ab & (~ryIsAreg | Nanod.ablAbd);
wire ryl2Dbl = Nanod.ryl2db & (ryIsAreg | Nanod.dblDbd);
wire ryl2Dbd = Nanod.ryl2db & (~ryIsAreg | Nanod.dblDbd);
wire rxl2Abl = Nanod.rxl2ab & (rxIsAreg | Nanod.ablAbd);
wire rxl2Abd = Nanod.rxl2ab & (~rxIsAreg | Nanod.ablAbd);
wire rxl2Dbl = Nanod.rxl2db & (rxIsAreg | Nanod.dblDbd);
wire rxl2Dbd = Nanod.rxl2db & (~rxIsAreg | Nanod.dblDbd);
// Buses. Main mux
logic abhIdle, ablIdle, abdIdle;
logic dbhIdle, dblIdle, dbdIdle;
always_comb begin
{abhIdle, ablIdle, abdIdle} = '0;
{dbhIdle, dblIdle, dbdIdle} = '0;
unique case( 1'b1)
ryl2Dbd: dbdMux = regs68L[ actualRy];
rxl2Dbd: dbdMux = regs68L[ actualRx];
Nanod.alue2Dbd: dbdMux = alue;
Nanod.dbin2Dbd: dbdMux = dbin;
Nanod.alu2Dbd: dbdMux = aluOut;
Nanod.dcr2Dbd: dbdMux = dcrOutput;
default: begin dbdMux = 'X; dbdIdle = 1'b1; end
endcase
unique case( 1'b1)
rxl2Dbl: dblMux = regs68L[ actualRx];
ryl2Dbl: dblMux = regs68L[ actualRy];
Nanod.ftu2Dbl: dblMux = ftu;
Nanod.au2Db: dblMux = auReg[15:0];
Nanod.atl2Dbl: dblMux = Atl;
Pcl2Dbl: dblMux = PcL;
default: begin dblMux = 'X; dblIdle = 1'b1; end
endcase
unique case( 1'b1)
Nanod.rxh2dbh: dbhMux = regs68H[ actualRx];
Nanod.ryh2dbh: dbhMux = regs68H[ actualRy];
Nanod.au2Db: dbhMux = auReg[31:16];
Nanod.ath2Dbh: dbhMux = Ath;
Pch2Dbh: dbhMux = PcH;
default: begin dbhMux = 'X; dbhIdle = 1'b1; end
endcase
unique case( 1'b1)
ryl2Abd: abdMux = regs68L[ actualRy];
rxl2Abd: abdMux = regs68L[ actualRx];
Nanod.dbin2Abd: abdMux = dbin;
Nanod.alu2Abd: abdMux = aluOut;
default: begin abdMux = 'X; abdIdle = 1'b1; end
endcase
unique case( 1'b1)
Pcl2Abl: ablMux = PcL;
rxl2Abl: ablMux = regs68L[ actualRx];
ryl2Abl: ablMux = regs68L[ actualRy];
Nanod.ftu2Abl: ablMux = ftu;
Nanod.au2Ab: ablMux = auReg[15:0];
Nanod.aob2Ab: ablMux = aob[15:0];
Nanod.atl2Abl: ablMux = Atl;
default: begin ablMux = 'X; ablIdle = 1'b1; end
endcase
unique case( 1'b1)
Pch2Abh: abhMux = PcH;
Nanod.rxh2abh: abhMux = regs68H[ actualRx];
Nanod.ryh2abh: abhMux = regs68H[ actualRy];
Nanod.au2Ab: abhMux = auReg[31:16];
Nanod.aob2Ab: abhMux = aob[31:16];
Nanod.ath2Abh: abhMux = Ath;
default: begin abhMux = 'X; abhIdle = 1'b1; end
endcase
end
// Source starts driving the bus on T1. Bus holds data until end of T3. Destination latches at T3.
// These registers store the first level mux, without bus interconnections.
// Even when this uses almost to 100 registers, it saves a lot of comb muxing and it is much faster.
reg [15:0] preAbh, preAbl, preAbd;
reg [15:0] preDbh, preDbl, preDbd;
always_ff @( posedge Clks.clk) begin
// Register first level mux at T1
if( enT1) begin
{preAbh, preAbl, preAbd} <= { abhMux, ablMux, abdMux};
{preDbh, preDbl, preDbd} <= { dbhMux, dblMux, dbdMux};
end
// Process bus interconnection at T2. Many combinations only used on DIV
// We use a simple method. If a specific bus segment is not driven we know that it should get data from a neighbour segment.
// In some cases this is not true and the segment is really idle without any destination. But then it doesn't matter.
if( enT2) begin
if( Nanod.extAbh)
Abh <= { 16{ ablIdle ? preAbd[ 15] : preAbl[ 15] }};
else if( abhIdle)
Abh <= ablIdle ? preAbd : preAbl;
else
Abh <= preAbh;
if( ~ablIdle)
Abl <= preAbl;
else
Abl <= Nanod.ablAbh ? preAbh : preAbd;
Abd <= ~abdIdle ? preAbd : ablIdle ? preAbh : preAbl;
if( Nanod.extDbh)
Dbh <= { 16{ dblIdle ? preDbd[ 15] : preDbl[ 15] }};
else if( dbhIdle)
Dbh <= dblIdle ? preDbd : preDbl;
else
Dbh <= preDbh;
if( ~dblIdle)
Dbl <= preDbl;
else
Dbl <= Nanod.dblDbh ? preDbh : preDbd;
Dbd <= ~dbdIdle ? preDbd: dblIdle ? preDbh : preDbl;
/*
Dbl <= dblMux; Dbh <= dbhMux;
Abd <= abdMux; Dbd <= dbdMux;
Abh <= abhMux; Abl <= ablMux; */
end
end
// AOB
//
// Originally change on T1. We do on T2, only then the output is enabled anyway.
//
// AOB[0] is used for address error. But even when raises on T1, seems not actually used until T2 or possibly T3.
// It is used on T1 when deasserted at the BSER exception ucode. Probably deassertion timing is not critical.
// But in that case (at BSER), AOB is loaded from AU, so we can safely transfer on T1.
// We need to take directly from first level muxes that are updated and T1
wire au2Aob = Nanod.au2Aob | (Nanod.au2Db & Nanod.db2Aob);
always_ff @( posedge Clks.clk) begin
// UNIQUE IF !
if( enT1 & au2Aob) // From AU we do can on T1
aob <= auReg;
else if( enT2) begin
if( Nanod.db2Aob)
aob <= { preDbh, ~dblIdle ? preDbl : preDbd};
else if( Nanod.ab2Aob)
aob <= { preAbh, ~ablIdle ? preAbl : preAbd};
end
end
assign eab = aob[23:1];
assign aob0 = aob[0];
// AU
logic [31:0] auInpMux;
// `ifdef ALW_COMB_BUG
// Old Modelsim bug. Doesn't update ouput always. Need excplicit sensitivity list !?
// always @( Nanod.auCntrl) begin
always_comb begin
unique case( Nanod.auCntrl)
3'b000: auInpMux = 0;
3'b001: auInpMux = byteNotSpAlign | Nanod.noSpAlign ? 1 : 2; // +1/+2
3'b010: auInpMux = -4;
3'b011: auInpMux = { Abh, Abl};
3'b100: auInpMux = 2;
3'b101: auInpMux = 4;
3'b110: auInpMux = -2;
3'b111: auInpMux = byteNotSpAlign | Nanod.noSpAlign ? -1 : -2; // -1/-2
default: auInpMux = 'X;
endcase
end
// Simulation problem
// Sometimes (like in MULM1) DBH is not set. AU is used in these cases just as a 6 bits counter testing if bits 5-0 are zero.
// But when adding something like 32'hXXXX0000, the simulator (incorrectly) will set *all the 32 bits* of the result as X.
// synthesis translate_off
`define SIMULBUGX32 1
wire [16:0] aulow = Dbl + auInpMux[15:0];
wire [31:0] auResult = {Dbh + auInpMux[31:16] + aulow[16], aulow[15:0]};
// synthesis translate_on
always_ff @( posedge Clks.clk) begin
if( Clks.pwrUp)
auReg <= '0;
else if( enT3 & Nanod.auClkEn)
`ifdef SIMULBUGX32
auReg <= auResult;
`else
auReg <= { Dbh, Dbl } + auInpMux;
`endif
end
// Main A/D registers
always_ff @( posedge Clks.clk) begin
if( enT3) begin
if( Nanod.dbl2rxl | Nanod.abl2rxl) begin
if( ~rxIsAreg) begin
if( Nanod.dbl2rxl) regs68L[ actualRx] <= Dbd;
else if( abdIsByte) regs68L[ actualRx][7:0] <= Abd[7:0];
else regs68L[ actualRx] <= Abd;
end
else
regs68L[ actualRx] <= Nanod.dbl2rxl ? Dbl : Abl;
end
if( Nanod.dbl2ryl | Nanod.abl2ryl) begin
if( ~ryIsAreg) begin
if( Nanod.dbl2ryl) regs68L[ actualRy] <= Dbd;
else if( abdIsByte) regs68L[ actualRy][7:0] <= Abd[7:0];
else regs68L[ actualRy] <= Abd;
end
else
regs68L[ actualRy] <= Nanod.dbl2ryl ? Dbl : Abl;
end
// High registers are easier. Both A & D on the same buses, and not byte ops.
if( Nanod.dbh2rxh | Nanod.abh2rxh)
regs68H[ actualRx] <= Nanod.dbh2rxh ? Dbh : Abh;
if( Nanod.dbh2ryh | Nanod.abh2ryh)
regs68H[ actualRy] <= Nanod.dbh2ryh ? Dbh : Abh;
end
end
// PC & AT
reg dbl2Pcl, dbh2Pch, abh2Pch, abl2Pcl;
always_ff @( posedge Clks.clk) begin
if( Clks.extReset) begin
{ dbl2Pcl, dbh2Pch, abh2Pch, abl2Pcl } <= '0;
Pcl2Dbl <= 1'b0;
Pch2Dbh <= 1'b0;
Pcl2Abl <= 1'b0;
Pch2Abh <= 1'b0;
end
else if( enT4) begin // Must latch on T4 !
dbl2Pcl <= Nanod.dbl2reg & Nanod.pcldbl;
dbh2Pch <= Nanod.dbh2reg & Nanod.pchdbh;
abh2Pch <= Nanod.abh2reg & Nanod.pchabh;
abl2Pcl <= Nanod.abl2reg & Nanod.pclabl;
Pcl2Dbl <= Nanod.reg2dbl & Nanod.pcldbl;
Pch2Dbh <= Nanod.reg2dbh & Nanod.pchdbh;
Pcl2Abl <= Nanod.reg2abl & Nanod.pclabl;
Pch2Abh <= Nanod.reg2abh & Nanod.pchabh;
end
// Unique IF !!!
if( enT1 & Nanod.au2Pc)
PcL <= auReg[15:0];
else if( enT3) begin
if( dbl2Pcl)
PcL <= Dbl;
else if( abl2Pcl)
PcL <= Abl;
end
// Unique IF !!!
if( enT1 & Nanod.au2Pc)
PcH <= auReg[31:16];
else if( enT3) begin
if( dbh2Pch)
PcH <= Dbh;
else if( abh2Pch)
PcH <= Abh;
end
// Unique IF !!!
if( enT3) begin
if( Nanod.dbl2Atl)
Atl <= Dbl;
else if( Nanod.abl2Atl)
Atl <= Abl;
end
// Unique IF !!!
if( enT3) begin
if( Nanod.abh2Ath)
Ath <= Abh;
else if( Nanod.dbh2Ath)
Ath <= Dbh;
end
end
// Movem reg mask priority encoder
wire rmIdle;
logic [3:0] prHbit;
logic [15:0] prenLatch;
// Invert reg order for predecrement mode
assign prenEmpty = (~| prenLatch);
pren rmPren( .mask( prenLatch), .hbit (prHbit));
always_ff @( posedge Clks.clk) begin
// Cheating: PREN always loaded from DBIN
// Must be on T1 to branch earlier if reg mask is empty!
if( enT1 & Nanod.abl2Pren)
prenLatch <= dbin;
else if( enT3 & Nanod.updPren) begin
prenLatch [prHbit] <= 1'b0;
movemRx <= Irdecod.movemPreDecr ? ~prHbit : prHbit;
end
end
// DCR
wire [15:0] dcrCode;
wire [3:0] dcrInput = abdIsByte ? { 1'b0, Abd[ 2:0]} : Abd[ 3:0];
onehotEncoder4 dcrDecoder( .bin( dcrInput), .bitMap( dcrCode));
always_ff @( posedge Clks.clk) begin
if( Clks.pwrUp)
dcr4 <= '0;
else if( enT3 & Nanod.abd2Dcr) begin
dcrOutput <= dcrCode;
dcr4 <= Abd[4];
end
end
// ALUB
reg [15:0] alub;
always_ff @( posedge Clks.clk) begin
if( enT3) begin
// UNIQUE IF !!
if( Nanod.dbd2Alub)
alub <= Dbd;
else if( Nanod.abd2Alub)
alub <= Abd; // abdIsByte affects this !!??
end
end
wire alueClkEn = enT3 & Nanod.dbd2Alue;
// DOB/DBIN/IRC
logic [15:0] dobInput;
wire dobIdle = (~| Nanod.dobCtrl);
always_comb begin
unique case (Nanod.dobCtrl)
NANO_DOB_ADB: dobInput = Abd;
NANO_DOB_DBD: dobInput = Dbd;
NANO_DOB_ALU: dobInput = aluOut;
default: dobInput = 'X;
endcase
end
dataIo dataIo( .Clks, .enT1, .enT2, .enT3, .enT4, .Nanod, .Irdecod,
.iEdb, .dobIdle, .dobInput, .aob0,
.Irc, .dbin, .oEdb);
fx68kAlu alu(
.clk( Clks.clk), .pwrUp( Clks.pwrUp), .enT1, .enT3, .enT4,
.ird( Ird),
.aluColumn( Nanod.aluColumn), .aluAddrCtrl( Nanod.aluActrl),
.init( Nanod.aluInit), .finish( Nanod.aluFinish), .aluIsByte( Irdecod.isByte),
.ftu2Ccr( Nanod.ftu2Ccr),
.alub, .ftu, .alueClkEn, .alue,
.aluDataCtrl( Nanod.aluDctrl), .iDataBus( Dbd), .iAddrBus(Abd),
.ze, .aluOut, .ccr);
endmodule