*       Fuzzy Logic Inferrence Engine
*
** This is the fifth variation of this fuzzy engine. The first used
*  a macro and conditional assembly for entry of rules and MFs. It
*  also combined fuzzification with rule evaluation. The 2nd version
*  was more efficient because fuzzy ins are only calculated once.
*  Also a c program was written to simplify rule and MF entry rather
*  than a macro and conditional assembly which were not very
*  portable from one assembler to another. The 2nd version used fixed
*  data structures for 4 ins and 2 outs. This, the third version, is
* identical to the 2nd version, except that the input and output size
* is variable. The number of Inputs and Outputs is set by the Knowledge
* Base Generator (kbg_11b1.exe). KBG_11B1 counts the actual number of 
* Inputs and Outputs that you defined and generates an assembly language
* define statement for this Inference Engine to use.
*
* Revision: B2 - fourth variation
* Author  : Raymond Carr (813) 461-6963
* Filename: FUZZ11B2.ASM
* Date    : 06/30/92
*       Notes:  Errors were found in version Rev B1 which would not allow for
*               more than 2 outputs. The reason for this was the mask used 
*               after the THEN_LOOP label was a #$0F.  This mask is ANDed
*               with the B accumulator to strip off the MSB indicating the
*               THEN portion of the rule under evaluation. Since there are
*               8 singletons per output this allowed access to only 16 ($0F)
*               singletons, hence 2 outputs.  This mask was changed to #$7F to
*               allow for up to 16 outputs.  The KBG knowledge base generator
*               will provide a valid rule base for only 4 outputs at this time.
*
*               Revision B1 was designed to save memory by dynamically
*               allocating memory via a EQU for the number of inputs, NUMINP,
*               and the number of outputs, NUMOUT.  The equations using these
*               EQU's were enclosed in parenthesis, e.g. (8*NUMINP).  The AS11
*               assembler available on the FREEWARE BBS will evaluate this
*               expression and return a result of 0.  The equations were
*               modified to take out the parenthesis and reorder the 
*               evaluation.  AS11 will work with 8*NUMINP+FUZ_INS, but not
*               FUZ_INS+8*NUMINP.  Also, the dynamic allocation of inputs
*               required a change that was not made in Rev B1.  At the very
*               beginning of the inference engine the program PUSHes the
*               current actual inputs onto the stack starting with the last
*               one.  This works well because it allows the grading routine
*               easy access to the next input by PULLing it off.  However,
*               the dynamic allocation of inputs requires dynamic PUSHing
*               of the stack, otherwise every time you exit the routine
*               your stack would be off by 4-(# of inputs).  Dynamic allocation
*               works now.
*
* Revision: B3 - fifth variation
* Author  : Raymond Carr (813) 461-6963
* Filename: FUZZ11B3.ASM
* Date    : 07/01/92
*       Notes:  A logic error was found in the rule evaluation when looking  
*               for the smallest component of an; IF .. IS ..; clause, the
*               program was maxing all THEN outputs to $FF due to a
*               BHS instead of a BLS 3 lines before the FIND_THEN label.
*               Also, make sure if you are using the knowledge base generator
*               routines you have a version later than kbg_11b2.exe.  The
*               earlier versions evaluate the rules incorrectly for this ASM
*               file.
*               An important observation is that if an output is not associated
*               with a rule in the rule base whose IF .. IS.. clause provides
*               some degree of membership the output value will be a 0.  This
*               is important if you have defined an output SPEED with 
*               singletons of HardReverse, MedReverse, SmallReverse,
*               StandStill, SmallForward, MedForward, HardForward.  
*               HardReverse is associated with an actual binary value of 0 and
*               StandStill is associated with a value of $7F.  Therefore if 
*               SPEED is not associated with a rule whose IF .. IS.. clause is
*               somewhat true it will get a value of 0, not $7F, your car will
*               proceed backwards instead of standing still. i.e. HardReverse
*               instead of StandStill.  One way to overcome this problem is to
*               provide a THEN clause for all defined outputs for every 
*               IF .. IS .. rule.

*** Data structures and variables
*

            ORG     $0000       ;Beginning of HC11 RAM
CURRENT_INS RMB     NUMINP      ;Storage for 8-bit inputs
FUZ_INS     RMB     8*NUMINP    ;Storage for fuzzy inputs (RIGHT NOW MAX=8)
FUZ_OUTS    RMB     8*NUMOUT    ;Storage for fuzzy outputs (RIGHT NOW MAX=4)
COG_OUTS    RMB     NUMOUT      ;Defuzzified outputs
SUM_OF_FUZ  RMB     2           ;11-bit sum of fuzzy outs
SUM_OF_PROD RMB     3           ;19-bit sum of products
COGDEX      RMB     1           ;Current out # for COG loop
LP_COUNT    RMB     1           ;Index for fuz loop
SUMDEX      RMB     1           ;Index for sum loop

            ORG     $B600   ;Beginning of HC11 EEPROM
* Add the output from KBG_11C.exe here.....
***** Fuzzy Inferrence Engine Starts Here
* Program assumes at least 1 valid input
*
FUZZIFY     LDY     #FUZ_INS                    ;Point to fuzzy input table
            LDX     #CURRENT_INS+NUMINP-1       ;Point to last input value
PUSH_LOOP   LDAA    0,X                         ;get value
            PSHA                                ;and store on stack
            CPX     #CURRENT_INS                ;last value?
            BEQ     DONE_PUSH                   ;if so, then branch
            DEX                                 ;else point to next byte
            BRA     PUSH_LOOP                   ;branch for next value

DONE_PUSH   LDX     #INPUT_MFS    ;Point to input MF specs
NXTIN_LP    PULA                  ;Get next current input value
            LDAB    #7            ;For 8 loop passes
            STAB    LP_COUNT      ;Initialize loop counter
GRAD_LOOP   EQU     *             ;Get grade for 1 label of 1 input

*******************************************************************
* GET_GRADE - Routine to project a discrete input value onto      *
*   the associated input membership function (fuzzification).     *
*   Enter with Input value in A register and X pointing at the    *
*   points and slopes defining the membership function.           *
*   Finishes with grade in B, X points at next MF spec (X+4)      *
*   and A is unchanged.                                           *
*******************************************************************
GET_GRADE   PSHA                  ;Save input value of A
            CLRB                  ;In case grade = 0
            SUBA    2,X           ;Input value D pt2 -> A
            BLS     NOT_SEG2      ;If input < pt2
            LDAB    3,X           ;Slope 2
            BEQ     HAV_GRAD      ;Skip if vert slope     
            MUL                   ;(In D pt1) * slp2 -> A:B
            TSTA                  ;Check for > $FF
            BEQ     NO_FIX        ;If upper 8 = 0
            CLRB                  ;Limit grade to 0
            BRA     HAV_GRAD      ;In limit region of seg 2
NO_FIX      SUBB    #$FF          ;B D $FF
            NEGB                  ;$FF D B
            BRA     HAV_GRAD      ;($FF D((In D pt2) * slp2))
NOT_SEG2    ADDA    2,X           ;Restore input value
            SUBA    0,X           ;Input value D pt1 -> A
            BLO     HAV_GRAD      ;In < pt1 so grade = 0
            LDAB    1,X           ;Slope 1
            BEQ     VERT_SLP      ;Skip if vert slope     
            MUL                   ;(In D pt1) * slp1 -> A:B
            TSTA                  ;Check for > $FF
            BEQ     HAV_GRAD      ;Result OK in B
VERT_SLP    LDAB    #$FF          ;Limit region or vert slope
HAV_GRAD    INX                   ;Point at next MF spec
            INX
            INX
            INX
            PULA                                ;Restore A register

            STAB    0,Y                         ;Save @ fuzzy input
            INY                                 ;Point at next
            DEC     LP_COUNT                    ;
            BPL     GRAD_LOOP                   ;For 8 labels of 1 input
            CMPY    #NUMINP*8+FUZ_INS           ;Done with all fuzzy ins?
            BNE     NXTIN_LP                    ;If not, process next input
* Done with fuzzification, evaluate rules next

            LDX     #FUZ_OUTS     ;Point at first fuzzy output
            LDAA    #8*NUMOUT     ;Number of fuzzy outputs
CLR_OUTS    CLR     0,X           ;Clear a fuzzy output
            INX                   ;Point at next
            DECA                  ;Loop index
            BNE     CLR_OUTS      ;Continue till all fuzzy outs 0
            LDY     #RULE_START   ;Point to start of 1st rule
RULE_TOP    LDAA    #$FF          ;Begin processing a rule string
* A will hold grade of smallest (min) if part
IF_LOOP     LDAB    0,Y           ;Get rule byte 000X XAAA; If X is A
            BMI     THEN_LOOP     ;If MSB=1, exit to then loop
            INY                   ;Point at next rule byte
            LDX     #FUZ_INS      ;Point at fuzzy inputs
            ABX                   ;Point to specific fuzzy input
            CMPA    0,X           ;Is this fuzzy input lower?
            BLS     IF_LOOP       ;If not don't replace lowest
            LDAA    0,X           ;Replace lowest if
            BNE     IF_LOOP       ;Unless zero, do next rule byte
* A zero value fuzzy input makes rule completely not true
* so skip rest of if parts and all then parts to start of next rule
FIND_THEN   LDAB    0,Y           ;Get next rule byte
            BMI     FIND_IF       ;MSB set means its a then part
            INY                   ;Point at next rule byte
            BRA     FIND_THEN     ;Loop till pointing at a then part
FIND_IF     INY                   ;Adv rule pointer to if part
            LDAB    0,Y           ;Get next rule byte
            BPL     RULE_TOP      ;MSB clear means its an if part
            CMPB    #$FF          ;$FF is no more rules marker
            BNE     FIND_IF       ;Continue looking for if or $FF
            BRA     DEFUZ         ;When all rules done, go defuzzify

THEN_LOOP   LDX     #FUZ_OUTS     ;Point at fuzzy outputs
            ANDB    #$7F          ;Save 8 times out # + label # 
            ABX                   ;X points at fuzzy output
* Grade of membership for rule is in A accumulator
            CMPA    0,X           ;Compare to fuzzy output
            BLO     NOT_HIER      ;Branch if not higher
            STAA    0,X           ;Grade is higher so update
NOT_HIER    INY                   ;Adv rule pointer to next byte
            LDAB    0,Y           ;Get rule byte
            BPL     RULE_TOP      ;If MSB=0 its a new rule
CHK_END     CMPB    #$FF          ;Check for end of rules flag
            BNE     THEN_LOOP     ;If not $FF, must be a then part

* All rules evaluated. Now defuzzify fuzzy outputs          
DEFUZ       LDY     #SGLTN_POS    ;Point at 1st output singleton
            LDX     #FUZ_OUTS     ;Point at 1st fuzzy output
            CLR     COGDEX        ;Loop index will run from 0->1
COG_LOOP    LDAB    #8            ;8 fuzzy outs per COG output
            STAB    SUMDEX        ;Inner loop runs 8->0
            LDD     #$0000        ;Used for quicker clears
            STD     SUM_OF_FUZ    ;Sum of fuzzy outputs
            STD     SUM_OF_PROD+1 ;Low 16-bits of sum of products
            STAA    SUM_OF_PROD   ;Upper 8-bits
SUM_LOOP    LDAB    0,X           ;Get a fuzzy output
            CLRA                  ;Clear upper 8-bits
            ADDD    SUM_OF_FUZ    ;Add to sum of fuzzy outputs
            STD     SUM_OF_FUZ    ;Update RAM variable
            LDAA    0,X           ;Get fuzzy output again
            LDAB    0,Y           ;Get Output singleton position
            MUL                   ;Position times weight
            ADDD    SUM_OF_PROD+1 ;Low 16-bits of sum of products
            STD     SUM_OF_PROD+1 ;Update low 16-bits
            LDAA    SUM_OF_PROD   ;Upper 8-bits
            ADCA    #0            ;Add carry from 16-bit add
            STAA    SUM_OF_PROD   ;Upper 8-bits of 24-bit sum
            INY                   ;Point at next singleton pos.
            INX                   ;Point at next fuzzy output
            DEC     SUMDEX        ;Inner loop index
            BNE     SUM_LOOP      ;For all labels this output
            PSHX                  ;Save index for now
            CLRA                  ;In case divide by zero
            LDX     SUM_OF_FUZ    ;Demominator for divide
            BEQ     SAV_OUT       ;Branch if denominator is 0
            TST     SUM_OF_PROD   ;See if more than 16-bit
            BNE     NUM_BIG       ;If not zero, # is > 16-bits
            LDD     SUM_OF_PROD+1 ;Numerator for divide
            IDIV                  ;Result in low 8-bits of X
            XGDX                  ;Result now in B
            TBA                   ;Move result to A
            BRA     SAV_OUT       ;Go save output
NUM_BIG     LDD     SUM_OF_PROD   ;Numerator upper 16 of 24-bit
            TST     SUM_OF_PROD+2 ;Check for rounding error
            BPL     NO_ROUND      ;If MSB clear, don't round
            ADDD    #1            ;Round numerator up 1
NO_ROUND    FDIV                  ;D/X -> X, use upper 8 of 16
            XGDX                  ;Result now in A
SAV_OUT     LDX     #COG_OUTS     ;Point to 1st defuz output
            LDAB    COGDEX        ;Curent output number
            ABX                   ;Point to correct output
            STAA    0,X           ;Update defuzzified output
            PULX                  ;Recover index
            INCB                  ;Increment loop index
            STAB    COGDEX        ;Update
            CMPB    #NUMOUT       ;Done with outputs?
            BNE     COG_LOOP      ;If not, continue loop
*****
* Inference engine has completed one pass of all rules.
*****

