From fbad46bd150892375df921f9e31d082b8e53a954 Mon Sep 17 00:00:00 2001 From: Luke Kenneth Casson Leighton Date: Tue, 17 Jul 2018 05:53:06 +0100 Subject: [PATCH] need pwm.bsv peripheral, to be modified to take a parameter of num of pwms --- src/bsv/bsv_lib/pwm.bsv | 324 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 src/bsv/bsv_lib/pwm.bsv diff --git a/src/bsv/bsv_lib/pwm.bsv b/src/bsv/bsv_lib/pwm.bsv new file mode 100644 index 0000000..c8796bd --- /dev/null +++ b/src/bsv/bsv_lib/pwm.bsv @@ -0,0 +1,324 @@ +/* +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 "defined_parameters.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; + 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 + (*synthesize*) + module mkTb(Empty); + let clk <- exposeCurrentClock; + PWM pwm <- mkPWM(clk); + 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 +endpackage -- 2.30.2