/*
* PDP-8/I simulation based on the "LD20" from
* "The Art of Digital Design" (Prosser) 1984 chapters 7 and 8
* With some modifications for communication with the HPS in a DE10-nano SoC
*
* Brian White April-December 2018
*/
module pdp8_cpu (
pdp8_reset,
pdp8_clk,
pdp8_continue,
pdp8_halt_request,
pdp8_int_request,
pc_initial,
pdp8_sr,
pc_out,
ac_out,
ir_out,
state_out,
IO_addr,
IO_data_in,
IO_data_out,
IOP1,
IOP2,
IOP4,
IOSKIP,
ACCLR,
ORAC
);
input pdp8_reset;
input pdp8_clk; // master system clock
input pdp8_continue;
input pdp8_halt_request;
input pdp8_int_request;
input[11:0] pc_initial; // start value of PC on reset
input[11:0] pdp8_sr; // switch register
output[11:0] pc_out; // current pc for HPS monitoring
output[11:0] ac_out; // current accumulator for HPS monitoring
output[11:0] ir_out; // current instruction reg for HPS monitoring
output[3:0] state_out; // current processor state for HPS monitoring
// IO lines
output reg[5:0] IO_addr, next_IO_addr; // address of peripheral
input[11:0] IO_data_in; // input data to PDP8
output reg IOP1, IOP2, IOP4; // control lines from PDP8
reg next_IOP1, next_IOP2, next_IOP4;
output [11:0] IO_data_out; // out from PDP8
input IOSKIP, ACCLR, ORAC; // input signals from peripherals
// processor registers
reg HALT_internal, next_HALT_internal; // handles program-based halts
reg HALT_external; // handles halt requests from outside the processor
reg[11:0] ac, next_ac; // accumulator
reg carry, next_carry;
reg[11:0] pc, next_pc; // program counter
reg[11:0] ir, next_ir; // instruction register
reg[14:0] ma, next_ma; // memory address
reg[11:0] mb, next_mb; // memory write data
reg start_RD, start_WR;
reg[4:0] state, next_state;
reg int_en, next_int_en; // interrupt enable
reg interrupt_Delay, next_interrupt_Delay; /*
* prevents interrupt if next instruction is ION
* this is not the same as Prosser, but is needed
* to implement extended addressing
* see https://groups.google.com/d/msg/alt.sys.pdp8/5rK7PBwG1LQ/PqbIw2Xl62sJ
*/
reg CIF_Delay, next_CIF_Delay; /*
* needed to block interrupt after InstructionBuffer changed
* until the next JSB or JMS instruction
* needed to implement extended addressing as is Interrupt_Delay
* (same info source)
*/
reg interrupt_initiated, next_interrupt_initiated;
/* to prevent updating of IF from IB on the JSB 0
* instruction issued at the start of an INT
*/
// OP instruction priority registers
reg RQST1, next_RQST1, RQST2, next_RQST2, RQST3, next_RQST3, RQST4, next_RQST4;
reg link, next_link;
// IOP selector register (Prosser page 305, 308)
// MSB : LSB
// IOP4 IOP2 IOP1 x
reg[3:0] IOP, next_IOP;
/* extended memory
* see http://homepage.divms.uiowa.edu/~jones/pdp8/man/mmu.html
* see also "DEC Small computer Handbook" p 50+
*
* instruction_buffer is the storage place altered by CIF and read by others
* it is also what's saved on interrupt
* instruction_field is what's actually used to access memory
* it is updated from instruction_buffer on a JMP or JMS instruction
*/
reg[2:0] instruction_field, next_instruction_field; // 3 MSB of the address for instructions
reg[2:0] instruction_buffer, next_instruction_buffer; // buffer for 3 MSB of instruction address
reg[2:0] data_field, next_data_field; // 3 MSB of address for data
reg[5:0] save_field, next_save_field; // where these values are saved on interrupt
// flip flop for external halt requests
// asynchronous input; reset by pdp8_reset
always @ (posedge pdp8_halt_request, pdp8_reset)
begin
if (pdp8_reset)
HALT_external <= 0;
else if (pdp8_halt_request)
HALT_external <= 1'b1;
end
// states
localparam IDLE = 5'd0,
F1 = 5'd1,
F2 = 5'd2,
F3 = 5'd3,
F4 = 5'd4,
F5 = 5'd5,
F6 = 5'd6,
F7 = 5'd7,
F8 = 5'd8,
F9 = 5'd9,
E0 = 5'd10,
E1 = 5'd11,
E2 = 5'd12,
E3 = 5'd13,
E4 = 5'd14,
E5 = 5'd15,
E6 = 5'd16;
always @ (posedge pdp8_clk, pdp8_reset)
begin
if (pdp8_reset)
begin
HALT_internal <= 0;
ac <= 0;
pc <= pc_initial;
ir <= 0;
ma <= 0;
mb <= 0;
state <= IDLE;
int_en <= 1'b0;
interrupt_Delay <= 1'b0;
CIF_Delay <= 1'b0;
interrupt_initiated <= 1'b0;
RQST1 <= 0;
RQST2 <= 0;
RQST3 <= 0;
RQST4 <= 0;
link <= 0;
carry <= 0;
IO_addr <= 6'o00;
IOP <= 4'b0001;
IOP1 <= 1'b0;
IOP2 <= 1'b0;
IOP4 <= 1'b0;
instruction_buffer <= 3'b000;
instruction_field <= 3'b000;
data_field <= 3'b000;
save_field <= 6'b000000;
end
else
begin
HALT_internal <= next_HALT_internal;
ac <= next_ac;
pc <= next_pc;
ir <= next_ir;
ma <= next_ma;
mb <= next_mb;
state <= next_state;
int_en <= next_int_en;
interrupt_Delay <= next_interrupt_Delay;
CIF_Delay <= next_CIF_Delay;
interrupt_initiated <= next_interrupt_initiated;
RQST1 <= next_RQST1;
RQST2 <= next_RQST2;
RQST3 <= next_RQST3;
RQST4 <= next_RQST4;
link <= next_link;
carry <= next_carry;
IO_addr <= next_IO_addr;
IOP <= next_IOP;
IOP1 <= next_IOP1;
IOP2 <= next_IOP2;
IOP4 <= next_IOP4;
instruction_buffer <= next_instruction_buffer;
instruction_field <= next_instruction_field;
data_field <= next_data_field;
save_field <= next_save_field;
end
end
// useful signals for the state machine, etc
// NOMEM (page 295) = instructions where no memory access required
assign NOMEM = (ir[11:10] == 2'b11) | (!ir[8] & ((ir[11:9] == 3'b011) | (ir[11:9] == 3'b100) | (ir[11:9] == 3'b101)));
// AUTO (page 296) - if this is an auto-index location (0010o - 0017o)
assign AUTO = (ma[11:3] == 9'b000000001);
// INT (page 292) - if there's an enabled interrupt
// also see http://homepage.divms.uiowa.edu/~jones/pdp8/man/mmu.html
// for modifications needed for extended addressing
assign INT = pdp8_int_request & int_en & ~interrupt_Delay & ~CIF_Delay;
// RQSTi flip flop signals - indicate if an OP instruction with that proirity needs attention
// see Prosser p 337
// Priority 1: CLA, CLL, Skips
assign PRI1 = ( (~ir[8]) & (ir[7] | ir[6]) ) | ( (ir[8]) & (ir[6] | ir[5] | ir[4] | ir[3]) );
// Priority 2: CMA, CML, CLA
assign PRI2 = ( (~ir[8]) & (ir[5] | ir[4]) ) | (ir[8] & ir[7]);
// Priority 3: IAC, OSR, HLT
assign PRI3 = ( (~ir[8]) & ir[0] ) | ( (ir[8]) & (ir[2] | ir[1]) );
// Priority 4: rotates - note difference from Prosser
// Prosser has ir[3] XOR ir[2]
// to implement PDP-8/I-specific instruction 7354 this must be OR
// see http://www.ibiblio.org/pub/academic/computer-science/history/pdp-8/Generic%20Programs/WHATAMI.pdf
assign PRI4 = (~ir[8]) & (ir[3] | ir[2]);
// OP decoding signals see Prosser p268, 269, 314, 318
assign G1 = (ir[8] == 1'b0);
assign G2 = (ir[8] == 1'b1);
assign SKIP0 = (ir[6] & ac[11]) | (ir[5] & (ac == 0)) | (ir[4] & link);
assign SKIP = ir[3] ^ SKIP0;
// MOREOP from Prosser pages 311 and 338
wire MOREOP;
pdp8_MOREOP MOP (
.RQST1 (RQST1),
.RQST2 (RQST2),
.RQST3 (RQST3),
.RQST4 (RQST4),
.MOREOP (MOREOP)
);
// next state and control logic
always @ (*)
begin
next_HALT_internal = HALT_internal;
next_ac = ac;
next_pc = pc;
next_ir = ir;
next_ma = ma;
next_mb = mb;
next_int_en = int_en;
next_interrupt_Delay = interrupt_Delay;
next_CIF_Delay = CIF_Delay;
next_interrupt_initiated = interrupt_initiated;
next_RQST1 = RQST1;
next_RQST2 = RQST2;
next_RQST3 = RQST3;
next_RQST4 = RQST4;
next_link = link;
next_carry = carry;
next_IO_addr = IO_addr;
next_IOP = IOP;
next_IOP1 = IOP1;
next_IOP2 = IOP2;
next_IOP4 = IOP4;
next_instruction_field = instruction_field;
next_instruction_buffer = instruction_buffer;
next_data_field = data_field;
next_save_field = save_field;
start_RD = 1'b0;
start_WR = 1'b0;
case (state)
IDLE: // same as Prosser's IDLE
begin
if (pdp8_continue)
next_state = F1;
else
next_state = IDLE;
end
F1: // same as Prosser's F1 with added extended memory details
begin
next_IOP = 4'b0001; // reset to starting condition on each new instruction)
next_IOP1 = 1'b0; // reset all the IOP signals
next_IOP2 = 1'b0;
next_IOP4 = 1'b0;
if (HALT_internal | HALT_external)
next_state = IDLE;
else if (INT) // valid interrupt request
begin
next_int_en = 1'b0; // prevent new interrupts
next_ma = 15'o00000; // jump to 0000
next_ir = 12'o4000; // JMS 0 to ensure proper return from interrupt
next_save_field = {instruction_field, data_field}; // save extended mem info
next_instruction_field = 3'b000;
next_instruction_buffer = 3'b000;
next_data_field = 3'b000;
next_interrupt_initiated = 1'b1;
next_state = E0;
end
else
begin
next_ma = {instruction_field, pc}; // get ready to read the current instruction
next_mb = pc; // save PC for EA computation (page 239)
next_interrupt_Delay = 1'b0; // reset the delay if no interrupt
next_state = F2;
end
end
F2: // same as Prosser's F2
begin
next_pc = pc + 12'o0001; // point to next instruction
start_RD = 1'b1;
next_state = F3;
end
F3: // same as Prosser's F3
begin
next_ir = mem;
if (!CC)
next_state = F3;
else
begin
/* Effective Address calculation
* use proper bit numbering MSB = 11; LSB = 0
* (this is the reverse of Prosser but easier to follow)
*/
next_ma = {instruction_field, ir[7] ? {mb[11:7], ir[6:0]} : {5'b00000, ir[6:0]}};
// set the RQSTi bits - the ones that control OP instruction priorites
next_RQST1 = PRI1;
next_RQST2 = PRI2;
next_RQST3 = PRI3;
next_RQST4 = PRI4;
// need to update IF on JMP, JMS so address is correct
if (ir[11:9] == 3'b101) // JMP
begin
if (CIF_Delay) // if update pending
begin
next_instruction_field = instruction_buffer;
next_CIF_Delay = 1'b0;
end
end
else if (ir[11:9] == 3'b100) // JMS
begin
if (interrupt_initiated)
next_interrupt_initiated = 1'b0; // don't update IF on INT-generated JMS
else if (CIF_Delay)
begin
next_CIF_Delay = 1'b0;
next_instruction_field = instruction_buffer;
end
end
// need to send out the IOADDR before the IOPs in E0
if (ir[11:9] == 3'b110) // IOT
next_IO_addr = ir[8:3]; // send out device address
if (NOMEM)
next_state = E0;
else
begin
next_state = F4;
end
end
end
F4: // added to make memory timing work
begin
start_RD = 1'b1;
next_state = F5;
end
F5: // same as first parts of Prosser's F4
begin
next_mb = mem;
if (!CC)
next_state = F5;
else
begin
if (!ir[8]) // direct addressing
next_state = E0;
else
if (AUTO)
begin
next_mb = mb + 1;
next_state = F6;
end
else
next_state = F8;
end
end // F5
F6: // added to make memory timing work - part of Prosser's F4
begin
start_WR = 1'b1;
next_state = F7;
end
F7: // same as Prosser's F5 - part of auto indexing
begin
if (!CC)
next_state = F7;
else
next_state = F8;
end
F8: // same as Prosser's F6 - indirect and auto-indexed
begin
if (ir[11:9] == 3'b100) // JMS - use IF not DF
next_ma = {instruction_field, mb};
else
next_ma = {data_field, mb};
if ((ir[11:9] == 3'b011) | (ir[11:9] == 3'b100) | (ir[11:9] == 3'b101))
next_state = E0;
else
begin
start_RD = 1'b1;
next_state = F9;
end
end
F9: //same as Prosser's F7 - indirect addressing
begin
next_mb = mem;
if (!CC)
next_state = F9;
else
next_state = E0;
end
E0: // same as Prosser's E0
begin
if (ir[11:9] == 3'b001) // TAD
begin
{next_carry, next_ac} = ac + mb;
if (next_carry)
next_link = ~link;
next_state = F1;
end
else if (ir[11:9] == 3'b000) // AND
begin
next_ac = ac & mb;
next_state = F1;
end
else if (ir[11:9] == 3'b101) // JMP
begin
next_pc = ma;
next_state = F1;
end
else if (ir[11:9] == 3'b010) // ISZ
begin
next_mb = mb + 1;
next_state = E1;
end
else if (ir[11:9] == 3'b011) // DCA
begin
next_mb = ac;
next_state = E1;
end
else if (ir[11:9] == 3'b100) // JMS
begin
next_mb = pc;
next_state = E1;
end
else if (ir[11:9] == 3'b111) // OP
begin
if (RQST1)
begin
if (G1 & ir[7]) // CLA
next_ac = 12'o0000;
if (G1 & ir[6]) // CLL
next_link = 1'b0;
if (G2 & SKIP) // SKIPs
next_pc = pc + 1;
next_RQST1 = 1'b0;
end // RQST1
else if (RQST2)
begin
if (G1 & ir[5]) // CMA
next_ac = ~ac;
if (G1 & ir[4]) // CML
next_link = ~link;
if (G2 & ir[7]) // CLA
next_ac = 12'o0000;
next_RQST2 = 1'b0;
end // RQST2
else if (RQST3)
begin
if (G1 & ir[0]) // IAC
{next_carry, next_ac} = ac + 1;
if (next_carry)
next_link = ~link;
if (G2 & ir[2]) // OSR
next_ac = pdp8_sr | ac;
if (G2 & ir[1]) // HLT
next_HALT_internal = 1'b1;
next_RQST3 = 1'b0;
end // RQST3
else if (RQST4)
begin
if (ir[3] & (~ir[2])) // right
begin // rotate right
if (ir[1]) // double !single
begin // RTR
next_ac = {ac[0], link, ac[11:2]};
next_link = ac[1];
end
else
begin // RAR
next_ac = {link, ac[11:1]};
next_link = ac[0];
end
end // rotate right
else if (ir[2] & (~ir[3])) // left
begin // rotate left
if (ir[1]) // double !single
begin // RTL
next_ac = {ac[9:0], link, ac[11]};
next_link = ac[10];
end
else // RAL
begin
next_ac = {ac[10:0], link};
next_link = ac[11];
end
end // rotate left
else if (ir[3] & ir[2]) // both left and right 7354
begin
next_ac = {link, ac[10:1], link};
next_link = ac[11];
end
next_RQST4 = 1'b0;
end // RQST4
// all OP's end up here
if (MOREOP)
next_state = E0;
else
next_state = F1;
end // of OP unit
else if (ir[11:9] == 3'b110) // IOT
begin
next_IOP = IOP << 1; // enable next IOP bit
next_IOP1 = ir[0] & next_IOP[1]; // enable IO signal lines as appropriate
next_IOP2 = ir[1] & next_IOP[2];
next_IOP4 = ir[2] & next_IOP[3];
next_state = E3;
end // IOT
else // unlikely to get here - this happens if ir doesn't match anything
next_state = F1;
end // E0
E1: // added delay state for memory writes
begin
start_WR = 1'b1;
next_state = E2;
end
E2: // same a Prosser's E1
begin
if (!CC)
next_state = E2;
else
begin
if (ir[11:9] == 3'b010) // ISZ
begin
if (mb == 12'o0000)
next_pc = pc + 1;
next_state = F1;
end // ISZ
else if (ir[11:9] == 3'b011) // DCA
begin
next_ac = 12'o0000;
next_state = F1;
end // DCA
else if (ir[11:9] == 3'b100) // JMS
begin
next_pc = ma + 1;
next_state = F1;
end // JMS
end // if CC true
end // E2
// E3-E6 are states for the IOT instructions
E3: // added to Prosser's E0
begin
// special cases
if (ir == 12'o6001) // ION - enable interrupts
begin
next_int_en = 1'b1;
next_interrupt_Delay = 1'b1; // prevent immediate interrupt
next_state = F1;
end // ION
else if (ir == 12'o6002) // IOF - disable interrupts
begin
next_int_en = 1'b0;
next_state = F1;
end // IOF
else if (ir[11:6] == 6'o62) // 62xx Extended addressing instructions
begin
if (ir[2:0] == 3'o1) // 62x1 CDF - change data field
next_data_field = ir[5:3];
else if (ir[2:0] == 3'o2) // 62x2 CIF - change instruction field
// really the buffer
begin
next_instruction_buffer = ir[5:3];
next_CIF_Delay = 1'b1; // delay interrupts
end
else if (ir[5:0] == 6'o14) // 6214 RDF - read data field
next_ac = ac | {6'o00, data_field, 3'o0};
else if (ir[5:0] == 6'o24) // 6224 RIF - read instruction field
next_ac = ac | {6'o00, instruction_field, 3'o0};
else if (ir[5:0] == 6'o34) // 6234 RIB - read interrupt buffer
next_ac = ac | {6'o00, save_field};
else if (ir[5:0] == 6'o44) // 6244 RMF - restore memory field
begin
{next_instruction_buffer, next_data_field} = save_field;
next_CIF_Delay = 1'b1; // delay interrupts
end
next_state = F1;
end // extended addressing instructions
else // all other IO instructions
begin
next_state = E4;
end
end // E3
E4: // Prosser's E3
begin
if (IOSKIP && IOP[1])
next_pc = pc + 1;
next_state = E5;
end // E4
E5: // Prosser's E4
begin
if (ACCLR && IOP[2])
next_ac = 12'o0000;
next_state = E6;
end // E5
E6: // Prosser's E5
begin
if (ORAC && IOP[3])
next_ac = ac | IO_data_in;
if (IOP[3]) // last one?
next_state = F1;
else
next_state = E0;
end // E6
endcase
end // next state and control logic
// 32K words of RAM
wire CC;
wire[11:0] mem;
pdp8_RAM RAM (
.pdp8_clk (pdp8_clk),
.pdp8_reset (pdp8_reset),
.pdp8_addr (ma),
.pdp8_DI (mb),
.pdp8_start_RD (start_RD),
.pdp8_start_WR (start_WR),
.pdp8_DO (mem),
.pdp8_RAM_CC (CC)
);
// outputs for HPS monitoring
assign pc_out = pc;
assign ac_out = ac;
assign ir_out = ir;
assign state_out = state;
// IO
assign IO_data_out = ac;
endmodule
This page: |
Maintained by: |
brian.white@umb.edu |
|
Created: | Thu Dec 6 19:56:28 2018 |
|
From: |
../../pdp8_cpu.v |