9086/system/biu.v

371 lines
14 KiB
Verilog

/* processor.v - implementation of the 9086 bus interface unit. The logic that
controls all external bus functions
This file is part of the 9086 project.
Copyright (c) 2023 Efthymios Kritikos
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
//IOMEM: 1=IO 0=MEM
`define BIU_HALT 4'b0000
`define BIU_NEXT_ACTION 4'b0001
`define BIU_READ 4'b0010
`define BIU_RESET 4'b0011
`define BIU_PUT_BYTE 4'b0100
`define BIU_PUT_UNALIGNED_16BIT_DATA 4'b0101
`define BIU_PUT_ALIGNED_16BIT_DATA 4'b0110
`define BIU_PUT_UNALIGNED_PREP_NEXT 4'b0111
`define BIU_PUT_UNALIGNED_PREP_NEXT2 4'b1000
`define BIU_WRITE_EXIT 4'b1001
`define BIU_WRITE_RELEASE 4'b1010
`define BIU_GET_ALIGNED_DATA 4'b1011
`define BIU_GET_UNALIGNED_DATA 4'b1100
`define BIU_GET_SECOND_BYTE 4'b1101
`define BIU_GET_SECOND_BYTE1 4'b1110
module BIU (
/*outside world*/ input clock, input reset, output reg [19:0] external_address_bus,
/* */ inout [15:0] external_data_bus,output reg read, output reg write,output reg BHE,output reg IOMEM,
/* internal */ output reg [31:0] INSTRUCTION, output reg VALID_INSTRUCTION, output reg [15:0] INSTRUCTION_LOCATION, input [1:0] NEXT_POSITION,
/* */ input[15:0] ADDRESS_INPUT, inout [15:0] DATA, input write_request, input read_request, input Wbit, output reg VALID_DATA, input MEM_OR_IO
);
reg [15:0] data_bus_output_register;
assign external_data_bus=read?data_bus_output_register:16'hz;
reg [15:0] DATA_OUT;
reg DATA_DIR;
assign DATA=DATA_DIR ? 16'hz:DATA_OUT;
`define L1_CACHE_SIZE 4 // Don't change this! some parts of the code assume this
`define FIFO_SIZE_BYTES 16 //$pow(2,`L1_CACHE_SIZE)
reg [7:0] INPUT_FIFO [`FIFO_SIZE_BYTES-1:0];
reg [`L1_CACHE_SIZE-1:0] FIFO_start; /*inclusive*/
reg [`L1_CACHE_SIZE-1:0] FIFO_end; /*exclusive*/
wire [`L1_CACHE_SIZE-1:0] FIFO_SIZE = FIFO_end-FIFO_start;
reg [3:0] biu_state;
always @(negedge reset) begin
biu_state <= `BIU_HALT;
end
always @(posedge reset) begin
biu_state <= `BIU_RESET;
end
reg jump_req;
reg func;
reg [19:0]INSTRUCTION_ADDRESS;
reg [19:0]DATA_ADDRESS;
assign external_address_bus= func? INSTRUCTION_ADDRESS : DATA_ADDRESS ;
/* Read into the FIFO */
always @(posedge clock) begin
if ( jump_req ) begin
/* verilator lint_off BLKSEQ */
FIFO_start = 0;
/* verilator lint_on BLKSEQ */
FIFO_end <= 0;
INSTRUCTION_ADDRESS <= { 4'b0 , ADDRESS_INPUT };
INSTRUCTION_LOCATION <= ADDRESS_INPUT;
func <= 1;
jump_req <= 0;
if (biu_state==`BIU_READ)
biu_state <= `BIU_NEXT_ACTION;
end else begin
case(biu_state)
`BIU_HALT: begin
end
`BIU_NEXT_ACTION: begin /* decide if we can read, if we are full or if we need to do something else */
if (write_request) begin
func<=0;
DATA_ADDRESS <= { 4'b0 , ADDRESS_INPUT };
DATA_DIR <= 1 ;
IOMEM <= MEM_OR_IO;
biu_state <= (Wbit==0) ? `BIU_PUT_BYTE : (ADDRESS_INPUT[0:0]?`BIU_PUT_UNALIGNED_16BIT_DATA:`BIU_PUT_ALIGNED_16BIT_DATA) ;
end else if ( read_request ) begin
func<=0;
DATA_ADDRESS <= { 4'b0 , ADDRESS_INPUT };
DATA_DIR <= 0;
IOMEM <= MEM_OR_IO;
read <= 0;
BHE <= 0;
biu_state <= (ADDRESS_INPUT[0:0])?`BIU_GET_UNALIGNED_DATA:`BIU_GET_ALIGNED_DATA;
end else begin
if ( FIFO_SIZE!=4'hF ) begin
func<=1;
biu_state <= `BIU_READ;
write <= 1;
read <= 0;
IOMEM <= 0;
BHE <= 0;
end else begin
biu_state <= `BIU_NEXT_ACTION;
end
end
if((Isit1==1) && (FIFO_SIZE!=0) && `EARLY_VALID_INSTRUCTION_)begin
VALID_INSTRUCTION <= 1;
INSTRUCTION[31:24] <= INPUT_FIFO[FIFO_start];
end else if((fifoIsize==2) && (FIFO_SIZE > 1) && `EARLY_VALID_INSTRUCTION_)begin
VALID_INSTRUCTION <= 1;
INSTRUCTION[31:24] <= INPUT_FIFO[FIFO_start];
INSTRUCTION[23:16] <= INPUT_FIFO[FIFO_start+4'd1];
end else if((fifoIsize==3) && (FIFO_SIZE > 2) && `EARLY_VALID_INSTRUCTION_)begin
VALID_INSTRUCTION <= 1;
INSTRUCTION[31:24] <= INPUT_FIFO[FIFO_start];
INSTRUCTION[23:16] <= INPUT_FIFO[FIFO_start+4'd1];
INSTRUCTION[15: 8] <= INPUT_FIFO[FIFO_start+4'd2];
end else if(FIFO_SIZE>3)begin
VALID_INSTRUCTION <= 1;
INSTRUCTION[31:24] <= INPUT_FIFO[FIFO_start];
INSTRUCTION[23:16] <= INPUT_FIFO[FIFO_start+4'd1];
INSTRUCTION[15: 8] <= INPUT_FIFO[FIFO_start+4'd2];
INSTRUCTION[ 7: 0] <= INPUT_FIFO[FIFO_start+4'd3];
end
end
/*************** INSTRUCTION FIFO READ ***************/
`BIU_READ: begin
/* verilator lint_off BLKSEQ */
if(INSTRUCTION_ADDRESS[0:0]==0 && FIFO_SIZE<4'hD)begin
INPUT_FIFO[FIFO_end] = external_data_bus[7:0];
INPUT_FIFO[FIFO_end+4'd1] = external_data_bus[15:8];
FIFO_end <= FIFO_end+4'd2;
INSTRUCTION_ADDRESS <= INSTRUCTION_ADDRESS+20'd2;
end else if(INSTRUCTION_ADDRESS[0:0]==0)begin
INPUT_FIFO[FIFO_end] = external_data_bus[7:0];
FIFO_end <= FIFO_end+4'd1;
INSTRUCTION_ADDRESS <= INSTRUCTION_ADDRESS+1;
end else begin
INPUT_FIFO[FIFO_end] = external_data_bus[15:8];
FIFO_end <= FIFO_end+4'd1;
INSTRUCTION_ADDRESS <= INSTRUCTION_ADDRESS+1;
end
/* verilator lint_on BLKSEQ */
biu_state <= `BIU_NEXT_ACTION;
read<=1;
end
/*************** DATA WRITE ***************/
//TODO TODO TODO flush fifo, self modifying code
`BIU_PUT_UNALIGNED_16BIT_DATA:begin
//TODO Put that in the previous stage, save a clock cycle
`ifdef DEBUG_DATA_READ_WRITES
$display("Writing 16bit %04x at %04x",DATA,DATA_ADDRESS);
`endif
BHE <= 0;
data_bus_output_register <= {DATA[7:0],DATA[15:8]};
biu_state <= `BIU_PUT_UNALIGNED_PREP_NEXT;
end
`BIU_PUT_UNALIGNED_PREP_NEXT:begin
write <= 0;
biu_state <= `BIU_PUT_UNALIGNED_PREP_NEXT2;
end
`BIU_PUT_UNALIGNED_PREP_NEXT2:begin
write <= 1;
DATA_ADDRESS <= DATA_ADDRESS+1;
BHE <= 1;
biu_state <= `BIU_WRITE_EXIT;
end
`BIU_PUT_ALIGNED_16BIT_DATA:begin
`ifdef DEBUG_DATA_READ_WRITES
$display("Writing 16bit %04x at %04x",DATA,DATA_ADDRESS);
`endif
data_bus_output_register <= {DATA[15:8],DATA[7:0]};
biu_state <= `BIU_WRITE_EXIT;
end
`BIU_PUT_BYTE:begin
`ifdef DEBUG_DATA_READ_WRITES
$display("Writing 8bit %02x at %04x",DATA[7:0],DATA_ADDRESS);
`endif
biu_state <= `BIU_WRITE_EXIT;
if(ADDRESS_INPUT[0:0]==0) begin
BHE <= 1;
data_bus_output_register <= {8'b0,DATA[7:0]};
end else begin
BHE <= 0;
data_bus_output_register <= {DATA[7:0],8'b0};
end
end
`BIU_WRITE_EXIT:begin
write <= 0;
biu_state <= `BIU_WRITE_RELEASE;
end
`BIU_WRITE_RELEASE:begin
write <= 1;
biu_state <= `BIU_NEXT_ACTION;
end
/*************** DATA READ ***************/
`define finished_read \
DATA_DIR <= 0; \
VALID_DATA <= 1;\
if ( read_request == 0 ) begin \
biu_state <= `BIU_NEXT_ACTION;\
VALID_DATA <= 0;\
end
`BIU_GET_ALIGNED_DATA:begin
`ifdef DEBUG_DATA_READ_WRITES
if(Wbit==1)
$display("Reading 16bit %04x from %04x",external_data_bus,DATA_ADDRESS);
else
$display("Reading 8bit %02x from %04x",external_data_bus[7:0],DATA_ADDRESS);
`endif
DATA_OUT <= (Wbit==1)? external_data_bus : {8'b0,external_data_bus[7:0]} ;
read <=1;
`finished_read
end
`BIU_GET_UNALIGNED_DATA:begin
`ifdef DEBUG_DATA_READ_WRITES
if(Wbit==0)
$display("Reading 8bit %02x from %04x",external_data_bus[15:8],DATA_ADDRESS);
`endif
DATA_OUT[7:0] <= external_data_bus[15:8];
read <=1;
if(Wbit==1) begin
biu_state <= `BIU_GET_SECOND_BYTE;
end else begin
`finished_read
end
end
`BIU_GET_SECOND_BYTE:begin
DATA_ADDRESS <= DATA_ADDRESS+1;
biu_state <= `BIU_GET_SECOND_BYTE1;
read <=0;
end
`BIU_GET_SECOND_BYTE1:begin
`ifdef DEBUG_DATA_READ_WRITES
$display("Reading 16bit %02x from %04x",{external_data_bus[7:0],DATA_OUT[7:0]},DATA_ADDRESS-1);//read started a byte earlier
`endif
DATA_OUT[15:8] <= external_data_bus[7:0];
`finished_read
read <=1;
end
/*************** HOUSE KEEPING ***************/
`BIU_RESET: begin
/* verilator lint_off BLKSEQ */
FIFO_start = 4'b0;
/* verilator lint_on BLKSEQ */
FIFO_end <= 4'b0;
biu_state <= `BIU_NEXT_ACTION;
INSTRUCTION_ADDRESS <= 20'h0FFF0;
INSTRUCTION_LOCATION <= 16'hFFF0;
VALID_INSTRUCTION <= 0;
VALID_DATA <= 0;
DATA_DIR <= 0;
end
default: begin
biu_state <= `BIU_NEXT_ACTION;/*Should be unreachable*/
end
endcase
end
end
wire [2:0] Isize;
InstrSize InstrSize({INSTRUCTION[31:24],INSTRUCTION[21:19]},Isize);
/* verilator lint_off UNDRIVEN */
wire [2:0] fifoIsize;
wire Isit1;
/* verilator lint_on UNDRIVEN */
`ifdef EARLY_VALID_INSTRUCTION
InstrSize fifoInstrSize({INPUT_FIFO[FIFO_start][7:0],INPUT_FIFO[FIFO_start][5:3]},fifoIsize);
Is1 Is1(INPUT_FIFO[FIFO_start][7:0],Isit1);
`endif
always @( NEXT_POSITION ) begin
case(NEXT_POSITION)
2'b00:begin end /* no action */
2'b01:begin /* Next instruction */
/* verilator lint_off BLKSEQ */
FIFO_start = FIFO_start + {1'b0,Isize};
INSTRUCTION_LOCATION <= INSTRUCTION_LOCATION + {12'b0,Isize};;
/* verilator lint_on BLKSEQ */
VALID_INSTRUCTION <= 0;
end
2'b10:begin /* Jump to specific location based on register */
jump_req <= 1;
VALID_INSTRUCTION <= 0;
end
2'b11:begin /* Jump to absolute location */
end
endcase
end
endmodule
/* Pre-Decode the instruction size */
/* IN: {CIR[15:8],CIR[5:3]} */
/* OUT: number in bytes */
module InstrSize ( input [10:0] IN, output reg [2:0] VERDICT );
always @( IN ) begin
casez(IN)
11'b0000_010?_??? : VERDICT <= 3'd2+{2'b0,IN[3:3]}; /* ADD - Add Immediate word/byte to accumulator */
11'b1000_00??_101 : VERDICT <= 3'd3+{2'b0,(IN[4:3]==2'b01)}; /* SUB - Subtract immediate word/byte from register/memory */
11'b1000_00??_000 : VERDICT <= 3'd3+{2'b0,(IN[4:3]==2'b01)}; /* ADD - Add Immediate word/byte to register/memory */
11'b1000_00??_111 : VERDICT <= 3'd3+{2'b0,(IN[4:3]==2'b01)}; /* CMP - compare Immediate with register / memory */
11'b1011_????_??? : VERDICT <= 3'd2+{2'b0,IN[6:6]}; /* MOV - Move Immediate byte to register */
11'b1000_10??_??? : VERDICT <= 3'd2; /* MOV - Reg/Mem to/from register */
11'b0100_????_??? : VERDICT <= 3'd1; /* DEC - Decrement Register | INC - Increment Register */
11'b1111_111?_00? : VERDICT <= 3'd2; /* INC - Register/Memory | DEC - Register/Memory */
11'b1111_0100_??? : VERDICT <= 3'd1; /* HLT - Halt */
11'b0011_110?_??? : VERDICT <= 3'd2+{2'b0,IN[3:3]}; /* CMP - Compare Immediate with accumulator */
11'b0111_????_??? : VERDICT <= 3'd2; /* Conditional relative jumps ( JE/JZ, JS/JNS ... ) */
11'b1110_1011_??? : VERDICT <= 3'd2; /* JMP - Unconditional jump direct within segment (short) */
11'b1110_1000_??? : VERDICT <= 3'd3; /* CALL - Direct call within segment */
11'b1100_0011_??? : VERDICT <= 3'd1; /* RET - Return from call within segment */
11'b1010_101?_??? : VERDICT <= 3'd1; /* STOS - Write byte/word to [DI] and increment accordingly */
11'b0101_0???_??? : VERDICT <= 3'd1; /* PUSH - SP-=2; [SP]=REG */
11'b1111_011?_000 : VERDICT <= 3'd3+{2'b0,IN[3:3]}; /* TEST - Bitwise AND affecting only flags */
11'b0101_1???_??? : VERDICT <= 3'd1; /* POP - REG=[SP]; SP+=2 */
11'b1111_1111_100 : VERDICT <= 3'd2; /* JMP - Unconditional indirect within segment jump */
11'b1100_011?_000 : VERDICT <= 3'd3+{2'b0,IN[3:3]}; /* MOV - Move immediate to register/memory */
11'b1100_1101_??? : VERDICT <= 3'd2; /* INT - execute interrupt handler */
11'b1110_011?_??? : VERDICT <= 3'd2; /* OUT - write AL or AX to a defined output port */
11'b1100_1111_??? : VERDICT <= 3'd1; /* IRET - Return from interrupt */
default:begin end
endcase
end
endmodule
`ifdef EARLY_VALID_INSTRUCTION
module Is1 ( input [7:0] IN, output reg VERDICT );
always @( IN ) begin
casez(IN)
8'b0100_???? : VERDICT <= 1; /* DEC - Decrement Register | INC - Increment Register */
8'b1111_0100 : VERDICT <= 1; /* HLT - Halt */
8'b1100_0011 : VERDICT <= 1; /* RET - Return from call within segment */
8'b1010_101? : VERDICT <= 1; /* STOS - Write byte/word to [DI] and increment accordingly */
8'b0101_0??? : VERDICT <= 1; /* PUSH - SP-=2; [SP]=REG */
8'b0101_1??? : VERDICT <= 1; /* POP - REG=[SP]; SP+=2 */
8'b1100_1111 : VERDICT <= 1; /* IRET - Return from interrupt */
default:begin VERDICT<= 0; end
endcase
end
endmodule
`endif