
`default_nettype none

module ternarynucleus_interrupt
(
  input wire clk,
  input wire resetq,
  input wire interrupt_request,

  output wire io_rd,
  output wire io_wr,
  output wire [23:0] io_addr,
  output wire [23:0] io_dout,
  input  wire [23:0] io_din
);

  assign io_addr = st0;
  assign io_dout = st1;

// -----------------------------------------------------------------------------
//  Memory
// -----------------------------------------------------------------------------

  reg [23:0] mem [32767:0]; initial $readmemh("nucleus.hex", mem, 0, 8191);

  // Save a lot of memory by converting to binary address first...
  wire [14:0] code_addr_binary; ternary2binary10tritspositive _code_addr (code_addr, code_addr_binary);
  wire [14:0]  mem_addr_binary; ternary2binary10tritspositive  _mem_addr ( mem_addr,  mem_addr_binary);

  reg  [23:0] insn_from_mem;
  reg  [23:0] fetch;

  always @(posedge clk) begin
    insn_from_mem <= mem[code_addr_binary];
    if (mem_wr) mem[mem_addr_binary] <= st1; else fetch <= mem[mem_addr_binary];
  end

  wire [23:0] insn = interrupt ? IRQOPCODE : insn_from_mem;  // Interrupt entry

// -----------------------------------------------------------------------------
//  Data and return stack
// -----------------------------------------------------------------------------

  parameter DEPTH = 42;
  localparam CELL =  2*WIDTH;
  localparam BITS = (CELL * DEPTH) - 1;

  reg [7:0]  depth; wire [7:0]  depthN;  trits_add4  _depthN ( depth, dmove,  depthN);  // Data   stack pointer, 4 trits
  reg [7:0] rdepth; wire [7:0] rdepthN;  trits_add4 _rdepthN (rdepth, rmove, rdepthN);  // Return stack pointer, 4 trits

  localparam INCS   = {O, O, O, P}; // +1
  localparam NOPS   = {O, O, O, O}; //  0
  localparam DECS   = {O, O, O, N}; // -1

  reg [BITS:0] dstack;
  reg [BITS:0] rstack;

  wire [3:0] alu_dmove = insn[9:6] & 4'b1011; // Remove T->N signal for depth change purposes

  always @(posedge clk)
  begin

    if (is_lit) dstack <= {        dstack[BITS-CELL:   0], {O, insn[23:2]} }; else // Push literal
    if (is_jzn) dstack <= {ZERO,   dstack[BITS     :CELL]                  }; else // Drop one element for conditional jumps

    if ( is_alu & (alu_dmove == {O, P}) ) dstack <= {            dstack[BITS-CELL : 1*CELL], func_T_N ? st0 : dstack[1*CELL-1:0*CELL], aluN }; else // d +1 T->N :   hgfedcb a  -->   gfedcbN T
    if ( is_alu & (alu_dmove == {O, O}) ) dstack <= {            dstack[BITS      : 2*CELL], func_T_N ? st0 : dstack[2*CELL-1:1*CELL], aluN }; else // d  0 T->N :   hgfedcb a  -->   hgfedcN T
    if ( is_alu & (alu_dmove == {O, N}) ) dstack <= {ZERO,       dstack[BITS      : 2*CELL],                                           aluN }; else // d -1      :   hgfedcb a  -->   0hgfedc T
    if ( is_alu & (alu_dmove == {N, P}) ) dstack <= {ZERO, ZERO, dstack[BITS      : 3*CELL],                                           aluN };      // d -2      :   hgfedcb a  -->   00hgfed T
  end

  wire [23:0] st0 = dstack[1*CELL-1 : 0*CELL];
  wire [23:0] st1 = dstack[2*CELL-1 : 1*CELL];
  wire [23:0] st2 = dstack[3*CELL-1 : 2*CELL];

  wire [7:0] dmove = is_lit ? INCS :
                     is_jzn ? DECS :
                     is_alu ? {4'd0, alu_dmove} :
                              NOPS;

  always @(posedge clk)
  begin
    if (is_call | (is_alu & insn[4] /* (insn[5:4] == P) */ ) ) rstack <= {       rstack[BITS-CELL : 0*CELL], is_call ? call_return : st0 }; else // Push   one element to   return stack
    if (           is_alu & insn[5] /* (insn[5:4] == N) */   ) rstack <= { ZERO, rstack[BITS      : 1*CELL]                              };      // Remove one element from return stack
  end

  wire [23:0] rst0 = rstack[CELL-1:0];

  wire [7:0] rmove = is_call ? INCS :
                     is_alu  ? {6'd0, insn[5:4]} :
                               NOPS;

// -----------------------------------------------------------------------------
//  ALU
// -----------------------------------------------------------------------------

  wire [23:0]  not3; trits_not   _not3 (st0, not3);
  wire [23:0]  rdn3; trits_rdn   _rdn3 (st0, rdn3);
  wire [23:0]  rup3; trits_rup   _rup3 (st0, rup3);
  wire [23:0] sign3; trits_sign _sign3 (st0, sign3);

  wire [23:0] any3; trits_any _any3 (st1, st0, any3);
  wire [23:0] cmp3; trits_cmp _cmp3 (st1, st0, cmp3);
  wire [23:0] con3; trits_con _con3 (st1, st0, con3);
  wire [23:0] equ3; trits_equ _equ3 (st1, st0, equ3);
  wire [23:0] min3; trits_min _min3 (st1, st0, min3);
  wire [23:0] max3; trits_max _max3 (st1, st0, max3);
  wire [23:0] mul3; trits_mul _mul3 (st1, st0, mul3);
  wire [23:0] sum3; trits_sum _sum3 (st1, st0, sum3);

  wire [23:0] lshift3; trits_lshift _lshift3 (st1, st0, lshift3);
  wire [23:0] rshift3; trits_rshift _rshift3 (st1, st0, rshift3);

  wire [23:0] add3; wire [1:0] addcarry3; trits_add _add3 (st1,  st0, add3, addcarry3);
  wire [23:0] sub3; wire [1:0] subcarry3; trits_add _sub3 (st1, not3, sub3, subcarry3);

  wire [23:0] equflag = st0 == st1  ? TRUE : FALSE ;

  wire condition = ~sign3[0]; // ~(sign3[1:0] == P); // (st0 <= 0)

  reg [23:0] aluN;

  always @*
  begin
    case (insn[19:14])

      {N, N, N} : aluN = st0;                 // -13   : T          tlit3#  00---0000000 ;
      {N, N, O} : aluN = st1;                 // -12   : N          tlit3#  00--00000000 ;
      {N, N, P} : aluN = st2;                 // -11   : 3OS        tlit3#  00--+0000000 ;
      {N, O, N} : aluN = rst0;                // -10   : rT         tlit3#  00-0-0000000 ;
      {N, O, O} : aluN = {16'd0,  depth};     //  -9   : status     tlit3#  00-000000000 ;
      {N, O, P} : aluN = {16'd0, rdepth};     //  -8   : rstatus    tlit3#  00-0+0000000 ;
      {N, P, N} : aluN = fetch;               //  -7   : mem[T]     tlit3#  00-+-0000000 ;
      {N, P, O} : aluN = io_din;              //  -6   : io[T]      tlit3#  00-+00000000 ;
      {N, P, P} : aluN = add3;                //  -5   : T+N        tlit3#  00-++0000000 ;
      {O, N, N} : aluN = {22'd0, addcarry3};  //  -4   : T+Ncarry   tlit3#  000--0000000 ;
      {O, N, O} : aluN = sub3;                //  -3   : N-T        tlit3#  000-00000000 ;
      {O, N, P} : aluN = equflag;             //  -2   : N==T       tlit3#  000-+0000000 ;
      {O, O, N} : aluN = cmp3;                //  -1   : N>=<T      tlit3#  0000-0000000 ;
      {O, O, O} : aluN = sign3;               //   0   : signflagT  tlit3#  000000000000 ;
      {O, O, P} : aluN = lshift3;             //   1   : NlshiftT   tlit3#  0000+0000000 ;
      {O, P, N} : aluN = rshift3;             //   2   : NrshiftT   tlit3#  000+-0000000 ;
      {O, P, O} : aluN = rdn3;                //   3   : rdnT       tlit3#  000+00000000 ;
      {O, P, P} : aluN = rup3;                //   4   : rupT       tlit3#  000++0000000 ;
      {P, N, N} : aluN = not3;                //   5   : ~T         tlit3#  00+--0000000 ;
      {P, N, O} : aluN = any3;                //   6   : NanyT      tlit3#  00+-00000000 ;
      {P, N, P} : aluN = con3;                //   7   : NconT      tlit3#  00+-+0000000 ;
      {P, O, N} : aluN = equ3;                //   8   : NequT      tlit3#  00+0-0000000 ;
      {P, O, O} : aluN = max3;                //   9   : NmaxT      tlit3#  00+000000000 ;
      {P, O, P} : aluN = min3;                // -10   : NminT      tlit3#  00+0+0000000 ;
      {P, P, N} : aluN = mul3;                // -11   : NmulT      tlit3#  00++-0000000 ;
      {P, P, O} : aluN = sum3;                // -12   : NsumT      tlit3#  00++00000000 ;
      {P, P, P} : aluN = TWIDTH;              // -13   : TWIDTH     tlit3#  00+++0000000 ;

        default : aluN = st0;
    endcase
  end

// -----------------------------------------------------------------------------
//  Instruction decoder
// -----------------------------------------------------------------------------

  wire is_jzn  = insn[3:0] == {N, N};
  wire is_jmp  = insn[3:0] == {O, N};
  wire is_call = insn[3:0] == {P, N};
  wire is_lit  = insn[1:0] ==     O;
  wire is_alu  = insn[1:0] ==     P;

  wire is_alu_exit = is_alu & insn[2]; // (insn[3:2] == P);

  wire func_T_N   = insn[8];  // insn[9:8] == P;

  wire func_write = insn[10]; // insn[11:10] == P;
  wire func_read  = insn[11]; // insn[11:10] == N;

  wire func_iow   = insn[12]; // insn[13:12] == P;
  wire func_ior   = insn[13]; // insn[13:12] == N;

  wire func_eint  = insn[22]; // insn[23:22] == P;
  wire func_dint  = insn[23]; // insn[23:22] == N;

  wire   mem_wr = is_alu & func_write;
  wire   mem_rd = is_alu & func_read;
  assign io_wr  = is_alu & func_iow;
  assign io_rd  = is_alu & func_ior;

  wire   eint   = is_alu & func_eint;
  wire   dint   = is_alu & func_dint;

// -----------------------------------------------------------------------------
//  Interrupt logic
// -----------------------------------------------------------------------------

  wire interrupt = interrupt_request & irq_enable;

  parameter IRQOPCODE = {O, O, O, O, O, O, O, O, O, P, P, N}; // 000000000++- Call 1

  wire irq_enable   =   pc[22];                 // pc[23:22] == P;
  wire irq_reenable =   pc[23];                //  pc[23:22] == N;
  wire handler_exit = rst0[23] & is_alu_exit; // rst0[23:22] == N;

  wire [23:0] call_return = interrupt ? {N, P, pc[19:0]} : {irq_enable|irq_reenable ? P : O, pc[21:20], pc_plus_1};

  wire [3:0] irqstateN;

  // Are interrupts enabled, disabled, or to be reenabled in the next clock cycle?

  assign irqstateN[3:2] = dint | interrupt    ? O : // On interrupt entry or when DINT is requested, disable further interrupts
                          eint | irq_reenable ? P : // If EINT is requested or if an interrupt reenable is pending, turn on interrupts
                          handler_exit        ? N : // On ALU exit, consider a reenable arriving from the return stack, pending for the next clock cycle
                                                pc[23:22]; // Unchanged otherwise

  // Are we executung a handler currently? Just for convenience, this is not necessary for the interrupts to work.

  assign irqstateN[1:0] = interrupt    ? P : // On interrupt entry, remember a reenable note is pushed to the return stack
                          handler_exit ? O : // On returns, check if the reenable is consumed, this denotes return from handler
                                         pc[21:20]; // Unchanged otherwise

// -----------------------------------------------------------------------------
//  Program counter and fetch address for the next clock cycle
// -----------------------------------------------------------------------------

  // New address for PC:

  reg  [23:0] pc;
  wire [19:0] pc_plus_1; trits_inc10 _pcinc (pc[19:0], pc_plus_1);

  wire [23:0] pcN = (is_jzn & condition) | is_jmp | is_call ? {irqstateN, insn[23:4]} :  // Jumps & Calls: Destination address
                     is_alu_exit                            ? {irqstateN, rst0[19:0]} :  // ALU+exit: Return.
                                                              {irqstateN, pc_plus_1 } ;  // Default: Increment PC to next opcode

  // New top of data stack register contents for fetch, but only 20 bits for 10 trits:

  // Note the memory fetch happens from dualport memory in every cycle which does not store,
  // so the memory location is already fetched the moment the actual fetch opcode is executed.
  // Therefore, we need a shortcut for the address that will be in TOS the next cycle.
  // Also the memory model fails if  ! @  opcodes are executed back-to-back.

   wire [19:0] fetch_addr = is_alu ? aluN[19:0] :  // ALU instruction
                            is_lit ? insn[21:2] :  // Literal
                            is_jzn ? st1 [19:0] :  // Conditional jump
                                     st0 [19:0] ;  // Jump or call do not change data stack

  // Where to fetch the next instruction, and where to access memory for RW:

  wire [19:0] code_addr = resetq ? pcN[19:0] : 0;
  wire [19:0]  mem_addr = mem_wr ? st0[19:0] : fetch_addr;

  always @(posedge clk)
  begin
    if (!resetq) { pc, depth, rdepth } <= 0;
    else         { pc, depth, rdepth } <= { pcN, depthN, rdepthN };
  end

endmodule

module ternary2binary10tritspositive
(
  input wire        [19:0] ti,
 output wire signed [14:0] bo
);

  assign bo = (ti[ 0] ?      1 : ti[ 1] ?      -1  : 0) +
              (ti[ 2] ?      3 : ti[ 3] ?      -3  : 0) +
              (ti[ 4] ?      9 : ti[ 5] ?      -9  : 0) +
              (ti[ 6] ?     27 : ti[ 7] ?     -27  : 0) +
              (ti[ 8] ?     81 : ti[ 9] ?     -81  : 0) +
              (ti[10] ?    243 : ti[11] ?    -243  : 0) +
              (ti[12] ?    729 : ti[13] ?    -729  : 0) +
              (ti[14] ?   2187 : ti[15] ?   -2187  : 0) +
              (ti[16] ?   6561 : ti[17] ?   -6561  : 0) +
              (ti[18] ?  19683 :                     0) ;

endmodule
