add first peripheral set
[shakti-peripherals.git] / src / peripherals / pwm / pwm.bsv
diff --git a/src/peripherals/pwm/pwm.bsv b/src/peripherals/pwm/pwm.bsv
new file mode 100644 (file)
index 0000000..010db57
--- /dev/null
@@ -0,0 +1,364 @@
+/*
+Copyright (c) 2013, IIT Madras
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+*  Redistributions of source code must retain the above copyright notice,
+    this list of conditions and the following disclaimer.
+*  Redistributions in binary form must reproduce the above copyright notice,
+    this list of conditions and the following disclaimer in the documentation
+    and/or other materials provided with the distribution.
+*  Neither the name of IIT Madras  nor the names of its contributors may be
+    used to endorse or promote products derived from this software without
+    specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-------------------------------------------------------------------------------------------------
+
+Code inpired by the pwm module at: https://github.com/freecores/pwm
+
+*/
+package pwm;
+  `define PWMWIDTH 32
+  /*=== Project imports ==*/
+  import Clocks::*;
+  /*======================*/
+  /*== Package imports ==*/
+  //import defined_types::*;
+  `include "instance_defines.bsv"
+  import ClockDiv::*;
+  import ConcatReg::*;
+       import Semi_FIFOF::*;
+       import BUtils ::*;
+  `ifdef PWM_AXI4Lite
+       import AXI4_Lite_Types::*;
+  `endif
+  `ifdef PWM_AXI4
+    import AXI4_Types::*;
+  `endif
+  /*======================*/
+
+  interface UserInterface;
+    method ActionValue#(Bool) write(Bit#(`PADDR) addr, Bit#(`Reg_width) data);
+    method Tuple2#(Bool, Bit#(`Reg_width)) read(Bit#(`PADDR) addr);
+  endinterface
+
+  interface PWMIO;
+    method Bit#(1) pwm_o;
+  endinterface
+
+  interface PWM;
+    interface UserInterface user;
+    interface PWMIO io;
+  endinterface
+
+  (*synthesize*)
+  module mkPWM#(Clock ext_clock)(PWM);
+
+    let bus_clock <- exposeCurrentClock;
+    let bus_reset <- exposeCurrentReset;
+
+    Reg#(Bit#(`PWMWIDTH)) period <- mkReg(0);
+    Reg#(Bit#(`PWMWIDTH)) duty_cycle <- mkReg(0);
+    Reg#(Bit#(`PWMWIDTH)) clock_divisor <- mkReg(0);
+    // =========== Control registers ================== //
+    Reg#(Bit#(1)) clock_selector <- mkReg(0);     // bit-0
+    Reg#(Bit#(1)) pwm_enable <- mkReg(0);         // bit-1
+    Reg#(Bit#(1)) pwm_start  <- mkReg(0);         // bit-2
+    Reg#(Bit#(1)) continous_once <- mkReg(0);     // bit-3
+    Reg#(Bit#(1)) pwm_output_enable <- mkReg(0);  // bit-4
+    Reg#(Bit#(1)) interrupt <- mkReg(0);          // bit-5
+    Reg#(Bit#(1)) reset_counter <- mkReg(0);      // bit-7
+    Reg#(Bit#(8)) control = concatReg8(reset_counter, readOnlyReg(0),
+                                       readOnlyReg(interrupt),
+                                       pwm_output_enable, continous_once,
+                                        pwm_start, pwm_enable,
+                                       clock_selector);
+    // ================================================ //
+
+    // Generate a reset signal is there is a reset from
+    // the bus interface of if the reset_counter
+    // bit in the control register is set. The new reset
+    // is called overall_reset. Only the counter
+    // and the output signals need to be reset by this.
+    MakeResetIfc control_reset <- mkReset(1,False, bus_clock);
+    rule generate_reset;
+      if(reset_counter==1)
+        control_reset.assertReset;
+    endrule
+    Reset overall_reset <- mkResetEither(bus_reset,control_reset.new_rst);
+
+    // Select between bus clock or external clock
+    MuxClkIfc clock_selection <- mkUngatedClockMux(ext_clock,bus_clock);
+    Reset async_reset <- mkAsyncResetFromCR(0,clock_selection.clock_out);
+    rule select_busclk_extclk;
+      clock_selection.select(clock_selector==1);
+    endrule
+
+    // The following register is required to transfer
+    // the divisor value from bus_clock to
+    // external clock domain. This is necessary if the
+    // clock divider needs to operate on the
+    // external clock. In this case, the divisor value
+    // should also come from the same clock domain.
+    Reg#(Bit#(`PWMWIDTH)) clock_divisor_sync <- mkSyncRegFromCC(0, 
+                                        clock_selection.clock_out);
+    rule transfer_data_from_clock_domains;
+      clock_divisor_sync <= clock_divisor;
+    endrule
+
+    // The PWM can operate on a slowed-down clock.
+    // The following module generates a slowed-down
+    // clock based on the value given in register divisor.
+    // Since the clock_divider works on a muxed
+    // clock domain of the external clock or bus_clock,
+    // the divisor (which operates on the bus_clock
+    // will have to be synchronized and sent to the divider
+    Ifc_ClockDiv#(`PWMWIDTH) clock_divider <- mkClockDiv(
+                                        clocked_by clock_selection.clock_out,
+                                         reset_by async_reset);
+    let downclock = clock_divider.slowclock;
+    Reset downreset <- mkAsyncReset(0,overall_reset,downclock);
+    rule generate_slow_clock;
+      clock_divider.divisor(clock_divisor_sync);
+    endrule
+
+    // ======= Actual Counter and PWM signal generation ======== //
+    Reg#(Bit#(1)) pwm_output <- mkReg(0,clocked_by downclock,
+                                        reset_by downreset);
+    Reg#(Bit#(`PWMWIDTH)) rg_counter <-mkReg(0,clocked_by downclock,
+                                        reset_by downreset);
+
+    // create synchronizers for clock domain crossing.
+    Reg#(Bit#(1)) sync_pwm_output <- mkSyncRegToCC(0,downclock,downreset);
+    ReadOnly#(Bit#(1)) pwm_signal <- mkNullCrossingWire(bus_clock, pwm_output);
+    Reg#(Bit#(1)) sync_continous_once <- mkSyncRegFromCC(0,downclock);
+    Reg#(Bit#(`PWMWIDTH)) sync_duty_cycle <- mkSyncRegFromCC(0,downclock);
+    Reg#(Bit#(`PWMWIDTH)) sync_period <- mkSyncRegFromCC(0,downclock);
+    Reg#(Bit#(1)) sync_pwm_enable <- mkSyncRegFromCC(0,downclock);
+    Reg#(Bit#(1)) sync_pwm_start <- mkSyncRegFromCC(0,downclock);
+    rule sync_pwm_output_to_default_clock;
+      sync_pwm_output <= pwm_output;
+    endrule
+
+    // capture the synchronized values from the default
+    // clock domain to the downclock domain for
+    // actual timer and pwm functionality.
+    rule sync_from_default_to_downclock;
+      sync_continous_once <= continous_once;
+      sync_duty_cycle <= duty_cycle;
+      sync_period <= period;
+      sync_pwm_enable <= pwm_enable;
+      sync_pwm_start <= pwm_start;
+    endrule
+    let temp = sync_period==0?0:sync_period-1;
+
+    // This rule generates the interrupt in the timer
+    // mode and resets it if the user-write interface
+    // writes a value of 1 to the reset_counter bit.
+    rule generate_interrupt_in_timer_mode;
+      if(pwm_enable==0)
+        interrupt <= sync_pwm_output;
+      else if(reset_counter==1)
+        interrupt <= 0;
+      else
+        interrupt <= 0;
+    endrule
+
+    // This rule performs the actual pwm and the timer
+    // functionality. if pwm_enable is 1 then the
+    // PWM mode is selected. Every time the counter
+    // value equals/crosses the period value it is
+    // reset and the output pwm_output signal is toggled.
+    // The timer mode is selected when pwm_enable is 0.
+    // Here again 2 more modes are possible. if the
+    // continous_once bit is 0 then the timer is in one time.
+    // In this case once the counter reaches
+    // the period value it raises an interrupt and
+    // stops the counter. In the continuous mode
+    // however, when the counter reaches the period value
+    // the interrupt is raise, the counter is
+    // reset to 0 and continues counting.
+    // During continuous counting the interrupt can be cleared by
+    // the user but will be set back when the counter reaches the period value.
+    rule compare_and_generate_pwm(sync_pwm_start==1);
+      let cntr = rg_counter+1;
+      if(sync_pwm_enable==1)begin // PWM mode enabled
+        if(rg_counter >= temp)
+          rg_counter <= 0;
+        else
+          rg_counter <= cntr;
+        if(rg_counter < sync_duty_cycle)
+          pwm_output <= 1;
+        else
+          pwm_output <= 0;
+      end
+      else begin  // Timer mode enabled.
+        if(sync_continous_once==0) begin // One time mode.
+          if(rg_counter >= temp)begin
+              pwm_output <= 1;
+          end
+          else
+            rg_counter <= cntr;
+        end
+        else begin // Continous mode.
+          if(rg_counter >= temp)begin
+            pwm_output <= 1;
+            rg_counter <= 0;
+          end
+          else begin
+            rg_counter <= cntr;
+          end
+        end
+      end
+    endrule
+
+    // ========================================================= //
+    interface user = interface UserInterface
+      method ActionValue#(Bool) write(Bit#(`PADDR) addr, Bit#(`Reg_width) data);
+        Bool err = False;
+        case(addr[4:2])
+          0: period <= truncate(data);
+          1: duty_cycle <= truncate(data);
+          2: begin control <= truncate(data);end
+          3: clock_divisor <= truncate(data);
+          default: err = True;
+        endcase
+        return err;
+      endmethod
+
+      method Tuple2#(Bool, Bit#(`Reg_width)) read(Bit#(`PADDR) addr);
+        Bool err = False;
+        Bit#(`Reg_width) data;
+        case(addr[4:2])
+          0: data = zeroExtend(period);
+          1: data = zeroExtend(duty_cycle);
+          2: data = zeroExtend(control);
+          3: data =  zeroExtend(clock_divisor);
+          default: begin err = True; data = 0; end
+        endcase
+        return tuple2(err,data);
+      endmethod
+    endinterface;
+    interface io = interface PWMIO
+      method pwm_o=pwm_output_enable==1?pwm_signal:0;
+    endinterface;
+  endmodule
+
+  `ifdef PWM_AXI4Lite
+    // the following interface and module will add the
+    // AXI4Lite interface to the PWM module
+    interface Ifc_PWM_bus;
+      interface PWMIO pwm_io;
+           interface AXI4_Lite_Slave_IFC#(`PADDR, `Reg_width,
+                                        `USERSPACE) axi4_slave;
+    endinterface
+
+    (*synthesize*)
+    module mkPWM_bus#(Clock ext_clock)(Ifc_PWM_bus);
+      PWM pwm <-mkPWM(ext_clock);
+               AXI4_Lite_Slave_Xactor_IFC#(`PADDR,`Reg_width, `USERSPACE)
+                                    s_xactor<-mkAXI4_Lite_Slave_Xactor();
+
+      rule read_request;
+                       let req <- pop_o (s_xactor.o_rd_addr);
+        let {err,data} = pwm.user.read(req.araddr);
+                       let resp= AXI4_Lite_Rd_Data {rresp:err?
+                                        AXI4_LITE_SLVERR:AXI4_LITE_OKAY,
+                                     rdata:data, ruser: ?};
+                       s_xactor.i_rd_data.enq(resp);
+      endrule
+
+      rule write_request;
+        let addreq <- pop_o(s_xactor.o_wr_addr);
+        let datareq <- pop_o(s_xactor.o_wr_data);
+        let err <- pwm.user.write(addreq.awaddr, datareq.wdata);
+        let resp = AXI4_Lite_Wr_Resp {bresp: err?
+                                        AXI4_LITE_SLVERR:AXI4_LITE_OKAY,
+                                    buser: ?};
+        s_xactor.i_wr_resp.enq(resp);
+      endrule
+
+      interface pwm_io = pwm.io;
+      interface axi4_slave = s_xactor.axi_side;
+    endmodule
+  `endif
+
+  `ifdef PWM_AXI4
+    // the following interface and module will add the
+    // AXI4 interface to the PWM module
+    interface Ifc_PWM_bus;
+      interface PWMIO pwm_io;
+           interface AXI4_Slave_IFC#(`PADDR, `Reg_width,`USERSPACE) axi4_slave;
+    endinterface
+
+    (*synthesize*)
+    module mkPWM_bus#(Clock ext_clock)(Ifc_PWM_bus);
+      PWM pwm <-mkPWM(ext_clock);
+               AXI4_Slave_Xactor_IFC#(`PADDR,`Reg_width,
+                                `USERSPACE) s_xactor<-mkAXI4_Slave_Xactor();
+
+      rule read_request;
+                       let req <- pop_o (s_xactor.o_rd_addr);
+        let {err,data} = pwm.user.read(req.araddr);
+        if(!(req.arsize == 2 && req.arlen == 0))
+          err = True;
+                       let resp= AXI4_Rd_Data {rresp:err?AXI4_SLVERR:AXI4_OKAY,
+                                     rdata:data, ruser: 
+                                        ?, rid:req.arid, rlast: True};
+                       s_xactor.i_rd_data.enq(resp);
+      endrule
+
+      rule write_request;
+        let addreq <- pop_o(s_xactor.o_wr_addr);
+        let datareq <- pop_o(s_xactor.o_wr_data);
+        let err <- pwm.user.write(addreq.awaddr, datareq.wdata);
+        if(!(addreq.awsize == 2 && addreq.awlen == 0))
+          err = True;
+        let resp = AXI4_Wr_Resp {bresp: err?AXI4_SLVERR:AXI4_OKAY, buser: ?,
+                                      bid:datareq.wid};
+        s_xactor.i_wr_resp.enq(resp);
+      endrule
+
+      interface pwm_io = pwm.io;
+    endmodule
+  `endif
+  `ifdef PWM_TEST
+  (*synthesize*)
+  module mkTb(Empty);
+    let clk <- exposeCurrentClock;
+    PWM pwm <- mkPWM(clk, 32);
+    Reg#(Bit#(5)) rg_state <- mkReg(0);
+
+    rule state1(rg_state==0);
+      rg_state<=1;
+      let x <- pwm.user.write(0,'d4);
+    endrule
+    rule state2(rg_state==1);
+      rg_state<=2;
+      let x <- pwm.user.write('d4,'d3);
+    endrule
+    rule state3(rg_state==2);
+      rg_state<=3;
+      let x <- pwm.user.write('hc,'d4);
+    endrule
+    rule state4(rg_state==3);
+      rg_state<=4;
+      let x <- pwm.user.write(8,'b0001_0110);
+    endrule
+  endmodule
+  `endif
+endpackage