/* Copyright 2008, Unpublished Work of Technologic Systems
 * All Rights Reserved.
 *
 * THIS WORK IS AN UNPUBLISHED WORK AND CONTAINS CONFIDENTIAL,
 * PROPRIETARY AND TRADE SECRET INFORMATION OF TECHNOLOGIC SYSTEMS.
 * ACCESS TO THIS WORK IS RESTRICTED TO (I) TECHNOLOGIC SYSTEMS 
 * EMPLOYEES WHO HAVE A NEED TO KNOW TO PERFORM TASKS WITHIN THE SCOPE
 * OF THEIR ASSIGNMENTS AND (II) ENTITIES OTHER THAN TECHNOLOGIC
 * SYSTEMS WHO HAVE ENTERED INTO APPROPRIATE LICENSE AGREEMENTS.  NO
 * PART OF THIS WORK MAY BE USED, PRACTICED, PERFORMED, COPIED, 
 * DISTRIBUTED, REVISED, MODIFIED, TRANSLATED, ABRIDGED, CONDENSED, 
 * EXPANDED, COLLECTED, COMPILED, LINKED, RECAST, TRANSFORMED, ADAPTED
 * IN ANY FORM OR BY ANY MEANS, MANUAL, MECHANICAL, CHEMICAL, 
 * ELECTRICAL, ELECTRONIC, OPTICAL, BIOLOGICAL, OR OTHERWISE WITHOUT
 * THE PRIOR WRITTEN PERMISSION AND CONSENT OF TECHNOLOGIC SYSTEMS.
 * ANY USE OR EXPLOITATION OF THIS WORK WITHOUT THE PRIOR WRITTEN
 * CONSENT OF TECHNOLOGIC SYSTEMS COULD SUBJECT THE PERPETRATOR TO
 * CRIMINAL AND CIVIL LIABILITY.
 */

/* PWM motor controller core with quadrature inputs.
 *
 * Register map:
 * base + 0x0: PWM channel #0 (0-256 for 0%-100% duty)
 * base + 0x2: PWM channel #1
 * base + 0x4: PWM channel #2
 * base + 0x6: PWM channel #3
 * base + 0x8: PWM channel #4
 * base + 0xa: PWM channel #5
 * base + 0xc: PWM channel #6
 * base + 0xe: PWM channel #7
 * base + 0x10: quadrature count #0 (16-bits, read-only)
 * base + 0x12: quadrature count #1
 * base + 0x14: quadrature count #2
 * base + 0x16: quadrature count #3
 * base + 0x18: index pulse count #0 (16-bits, read-only)
 * base + 0x1a: index pulse count #1
 * base + 0x1c: index pulse count #2
 * base + 0x1e: index pulse count #3
 * base + 0x20: configuration register (read-write)
 *   bit 3-0: Hbridge enable chan#3 to chan#0 (LSB is chan#0)
 *   bit 7-4: index pulse enable chan#3 to chan#0 (LSB is chan #0)
 *   bit 11-8: index pulse polarity chan#3 to chan#0 (LSB is chan #0)
 *   bit 15-12: quadrature enable chan#3 to chan#0 (LSB is chan #0)
 * base + 0x22: GPIO inputs (read-only)
 *   bit 15-8: quadrature input pins 7-0
 *   bit 7-0: general input pins 7-0
 * base + 0x24: overcurrent status
 *   bit 15-4: reserved
 *   bit 3-0: overcurrent status chan#3 to chan#0
 *
 * * If quadrature channels are disabled, quadrature counts become
 *   free-running upcounters and index pulse counts become edge 
 *   counters.
 *
 * * If overcurrent is tripped, H-bridge enable is automatically
 *   temporarily deasserted.  This effects as negative feedback
 *   keeping current maxed out at approximately the trip point.
 */
module motor(
  wb_clk_i,
  wb_rst_i,
  wb_cyc_i,
  wb_stb_i,
  wb_we_i,
  wb_ack_o,
  wb_adr_i,
  wb_sel_i,
  wb_dat_i,
  wb_dat_o,

  quadrature_i,
  gpio_i,
  hbridge_o,
  hbridge_en_o,
  overcurrent_i
);

input wb_clk_i, wb_rst_i, wb_cyc_i, wb_stb_i, wb_we_i;
output wb_ack_o;
input [31:0] wb_adr_i;
input [15:0] wb_dat_i;
output [15:0] wb_dat_o;
input [1:0] wb_sel_i;

input [3:0] overcurrent_i;
input [7:0] gpio_i, quadrature_i;
output [3:0] hbridge_en_o;
output [7:0] hbridge_o;

reg [15:0] cfg;
wire [3:0] hbridge_en = cfg[3:0];
wire [3:0] idx_en = cfg[7:4];
wire [3:0] idx_pol = cfg[11:8];
wire [3:0] quad_en = cfg[15:12];
reg [31:0] count;
reg [8:0] duty [0:7];
reg [15:0] qcount [0:3];
reg [15:0] icount [0:3];
reg [7:0] hbridge;
reg [3:0] a, b, idx_in, idx_in_prev;
wire [3:0] quadrature_a = {quadrature_i[6], quadrature_i[4], 
  quadrature_i[2], quadrature_i[0]};
wire [3:0] quadrature_b = {quadrature_i[7], quadrature_i[5], 
  quadrature_i[3], quadrature_i[1]};
wire [3:0] qcount_en = (a ^ quadrature_a ^ b ^ quadrature_b) | ~quad_en;
wire [3:0] qcount_dir = (quadrature_a ^ b) | ~quad_en;
assign hbridge_o = hbridge;
assign hbridge_en_o = hbridge_en & ~overcurrent_i;
wire [2:0] chan = count[2:0];
wire [1:0] qchan = count[1:0];
wire [7:0] period = (count >> 3);
reg wb_ack;
reg [15:0] wb_dat;
assign wb_dat_o = wb_dat;
assign wb_ack_o = wb_ack;
integer i;

always @(posedge wb_clk_i) begin
    /* if quadrature disabled (quad_en == 4'd0), qcounters will be freerunning
     * up counters and icounters will be index pulse edge counters. */

    if (qcount_en[qchan] & idx_in[qchan] & ~idx_in_prev[qchan]) begin
      qcount[qchan] <= 16'd0;
      icount[qchan] <= icount[qchan] + 1'b1;
    end else if (qcount_en[qchan] && qcount_dir[qchan]) 
      qcount[qchan] <= qcount[qchan] + 1'b1;
    else if (qcount_en[qchan]) qcount[qchan] <= qcount[qchan] - 1'b1;

    if (wb_cyc_i && wb_stb_i && !wb_ack && wb_we_i && 
      (wb_adr_i[5:0] == {2'h0, chan, 1'b0})) duty[chan] <= wb_dat_i;
end

always @(posedge wb_clk_i or posedge wb_rst_i) begin
  if (wb_rst_i) begin
    count <= 32'd0;
    cfg <= 16'd0;
    hbridge <= 8'd0;
    a <= 4'hf;
    b <= 4'hf;
    idx_in <= 4'hf;
    idx_in_prev <= 4'hf;
    wb_ack <= 1'b0;
    wb_dat <= 16'h0;
  end else begin
    if (period >= duty[chan]) hbridge[chan] <= 1'b0;
    else hbridge[chan] <= 1'b1;

    count <= count + 1'b1;

    wb_ack <= 1'b0;
    if (wb_cyc_i && wb_stb_i && !wb_ack) begin
      if (wb_we_i) begin
        if (wb_adr_i[5:0] == {2'h0, chan, 1'b0}) begin
          wb_ack <= 1'b1;
        end else if (wb_adr_i[5:0] >= 6'h20) begin
          cfg[15:0] <= wb_dat_i;
          wb_ack <= 1'b1;
        end
      end else begin
        if (wb_adr_i[5:0] == {2'h0, chan, 1'b0}) begin /* 0x0-0xe */
          wb_dat <= duty[chan];
          wb_ack <= 1'b1;
        end else if (wb_adr_i[5:0] == {3'h2, qchan, 1'b0}) begin /* 0x10-0x16 */
          wb_dat <= qcount[qchan];
          wb_ack <= 1'b1;
        end else if (wb_adr_i[5:0] == {3'h3, qchan, 1'b0}) begin /* 0x18-0x1e */
          wb_dat <= icount[qchan];
          wb_ack <= 1'b1;
        end else if (wb_adr_i[5:0] == 6'h20) begin
          wb_dat <= cfg;
          wb_ack <= 1'b1;
        end else if (wb_adr_i[5:0] == 6'h22) begin
          wb_dat <= {quadrature_i, gpio_i};
          wb_ack <= 1'b1;
        end else if (wb_adr_i[5:0] == 6'h24) begin
          wb_dat <= {12'hxxx, overcurrent_i};
          wb_ack <= 1'b1;
        end
      end
    end

    if (qchan == 3'h3) begin
      a <= quadrature_a;
      b <= quadrature_b;
    end
    
    idx_in <= idx_en & (idx_in | (gpio_i[3:0] ^ idx_pol));

    if (qcount_en[qchan]) idx_in_prev[qchan] <= idx_in[qchan];
    if (qcount_en[qchan] & idx_in[qchan] & ~idx_in_prev[qchan]) begin
      idx_in[qchan] <= 1'b0;
    end
  end
end

endmodule
