/*
*   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_cpuIndex (
  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

Verilog converted to html by v2html 7.30.1.3 (written by Costas Calamvokis).Help