M-07-6808Microcontroller.pptx
Document Details
Uploaded by MomentousMoldavite9655
Conestoga College
Tags
Full Transcript
SENG1040 Computer Architecture and Machine Language Assembly Level Programming Agenda Freescale Intro to the 6808 Looping in Assembly Calling a Function Timing and Cycle Counting Freescale? Freescale Semiconductor Inc. is a producer and designer of embedded hardware, with 17 billion s...
SENG1040 Computer Architecture and Machine Language Assembly Level Programming Agenda Freescale Intro to the 6808 Looping in Assembly Calling a Function Timing and Cycle Counting Freescale? Freescale Semiconductor Inc. is a producer and designer of embedded hardware, with 17 billion semiconductor chips in use around the world The company focuses on the automotive, consumer, industrial and networking markets with its product portfolio including microprocessors, microcontrollers, digital signal processors, digital signal controllers, sensors, RF power ICs and power management ICs In addition, the company offers software and development tools to enable complete solutions and to support product development Why Freescale We care about Freescale (mostly) because they offer development tools (software and development boards) for Hardware / Software Engineers. CodeWarrior is one of their tools and here in the SET program, we use it as our tool of choice for designing, developing and debugging assembly code Besides just offering CodeWarrior, Freescale also offers a number of chipsets (different processors and controllers) to be used with the tool The CodeWarrior IDE includes a piece of software known as an emulator that you can use with the different chipsets to design, develop and debug code for a processor / controller without even having to have the real chip in front of you The 6808 Microcontroll er Introduction to the 6808 The 6808 is a very versatile device. It is based on the 6800 family of CPUs which have been around since the 1970s It is an 8 bit processor that can address 16-bits of address space It is best suited for smaller, embedded tasks and is perfect as a platform for you to learn all about assembly programming As we have discussed previously, we know that the 6808 (like any processor) has a number of internal registers 6808 Registers The Accumulator (A) The Program Counter (PC) The Stack Pointer (SP) This is an address manipulation register The CPU uses the SP to tell it what address to return to after calling a function or handling an interrupt The H:X register (H:X) This is another address manipulation register It is called upon to do indexed address manipulations (like you do in C with an array) The Condition Code Register No discussion of any processor / controller would be complete without mentioning the condition-code register This register is used to reflect the state / status of the last operation that occurred in the ALU It should be mentioned that this register is also used (i.e. there is a bit there) to indicate whether the CPU has interrupts enabled or disabled Why Assembly You might be thinking “Who uses assembly language anymore?” If you were thinking this – you’d be wrong Assembly is as widely used today as it was almost 50 years ago - that’s right – 50) As we have seen in previous modules, compiled higher-level languages (like C, C++, C#, …) all depend on their compilers to translate your source code into this lower level instruction that the processor can run – even interpreted languages Every programming language depends on this lower level language Machine Language A (simple) piece of software designed and written in assembly language will usually outperform a piece of software designed and written in C (for example) This is because when a piece of software is designed and developed in a low-level language like assembly, the software engineer is aware of and plans the code for most effective use of the platform’s time and resources With higher level languages – you are depending on the compiler to do this high-to-low level translation for you in a general purpose way – and the code that it produces might not be the most effective or efficient way of implementing in assembly Shaving Cycles Why care about the small details of the speed of code? Ordinarily in the world of applications and systems that we sometimes live in, we do want efficient and responsive code – but we don’t really care about squeezing every last efficiency out of the code In the world of real-time or embedded systems however – this is a design goal and is constantly on the mind of the software developer Back to the 6808 We previously examined a fictitious CPU with 4 general purpose data registers (A, B, C and D) In the 6808 however, we have only one general purpose register – the Accumulator (A) In 6808, assembly instructions that require the register use it in an implied way. Consider: loop: LDA $0101 ; read from the status register AND #%10000000 ; and the accumulator to extract the ; high-order bit BEQ loop; if not, try again … LDA $0100 ; read the data and … STA $1000 ; … store it where needed JMP loop; wait for more data to be ready Executing Instructions Recall that assembly language itself is a high level language compared to the operation codes (op-codes) that actually get executed within the CPU Remember that the CPU goes through several steps FETCH a new instruction (the instruction’s op-code) Then update the program counter (PC) Then DECODE the instruction (to determine what the operation is) Then fetch the op-code’s argument (if need be) and update the PC Then EXECUTE the instruction with its argument(s) Fetch … Decode … Execute... Fetch … Decode … Execute... Fetch, Decode, Execute This “fetch, decode, execute” process is taken care of for the CPU by the IDU – the Instruction Decoding Unit. This processing unit’s job is to know about every op-code that the CPU is capable of running and to know which op-codes take which and how many parameters The IDU needs to know about all of the CPU’s addressing modes in order to know how to interpret operands How do we find out about addressing modes? Read the manual! Freescale Documentation There are many manuals offered by Freescale on the many variations of their 6808 family of chips … here are some useful links The MC9S08GB60 Datasheet – explaining the hardware configuration and setup of the chip A generic 6808 Software Development Manual – explaining addressing modes and instruction sets These resources are also posted on eConestoga The 6808 Memory Model The 6808 Memory Map The 6808 is a microcontroller with a number of ports, devices and peripherals built right into the chip along with its own memory on-board These ports, devices and peripherals are memory-mapped devices This means that the 6808 talks to the devices through memory addresses This also means that the 6808’s layout of memory must be predefined so a person programming the microcontroller knows where these devices are in memory, where their program will be stored, etc. What we are looking for is known as the memory map for the 6808 – and how do we find this? The 6808 Memory Map DIRECT-PAGE REGISTERS: (0x0000 – 0x007F) The processor uses this range to communicate with the most common set of the memory mapped devices, ports and peripherals it contains. This area is known as the DPR. It is called direct-page because of the addressing mode that a program needs to use to communication with these devices. RAM : (0x0080 – 0x107F) General purpose RAM for the 6808 – storage for variables, etc. There is a total of 4 Kbytes of RAM. The 6808 Memory Map FLASH: (0x1080 – 0x17FF, 0x182C – 0xFFFF) Non-volatile memory areas. This is where your program will be downloaded and stored in the 6808 ( Motorola S19 records). You cannot use this memory space for any runtime data storage. HIGH-PAGE REGISTERS: (0x1800 – 0x182B) There are more memory mapped devices (less commonly used) found here. Accessing 6808 Memory I need to highlight a couple of interesting points and facts out to you as you begin to program and use the 6808 Let’s say we want to load the accumulator with a constant value of 1010 – we do this with the following instruction LDA #$0A or LDA #10 or LDA #%00001010 If we refer to the manual, we find that this uses something called immediate addressing and this instruction takes two clock cycles to execute Accessing 6808 Memory Now let’s say we want to load the accumulator with the present value of the memory address 0x0042 – we could do so with the following instruction LDA $42 Previously, we saw that when assembled this instruction becomes a two byte set of op-code and argument in the final program : B642 If you refer to the programming manual (I know … refer to the manual again) you will find that this instruction takes three clock cycles to execute and is known as 8-bit direct addressing (why?) Accessing 6808 Memory Finally, if we wanted to be absolutely pristine and because the 6808 address space is 16 bits, what if we coded the following LDA $0042 In this case, the assembled instruction becomes a three byte set of op-code and argument in the final program : C60042 This instruction takes four clock cycles to execute and is known as 16-bit direct addressing (why?) Why should we care about the difference between LDA $42 and LDA $0042? Accessing 6808 Hardware 6808 Capabilities The 6808 has lot of pins on it If you refer to the various manuals, you’ll soon realize that these pins are used for input and output to and from the 6808 device. These pins are divided up into what the manuals refer to as the ports of the 6808. The device has Port A through Port G The different ports have different general purposes – for example: Port A is used for general input and supports the keyboard interrupt Port D hosts a number of different timers and can be used to gather input or drive output Port F is used to drive output (what the manual refers to as high-current output) – such as LEDs Let’s talk a little bit about these various ports and what we can do with them (and more importantly how) 6860 Ports For our purposes in this semester, this information is presented to you for interest only It may come in handy if you actually do some programming with actual chips and boards In general, most micro-controllers have ports like the 6808 – ports for input, output and timers On the 6808, all of these ports come equipped with 3 different registers that are used to configure the port’s use and interact with it A data register – for reading or writing values A data direction register – indicating if we will read or write data A data interpretation register – that tells us how to interpret the inputs that we read 6808 Port A The devices connected to this port are usually input devices (like switches or push-buttons) Before we can begin to hook our switches to this port, we need to configure it properly If you refer to the datasheet, you would see Port A’s: Data Register is found at $00 (by default, HIGH = Not Pressed) Data Interpretation Register is found at address $01 We use this data interpretation register to configure the data signals by writing an $FF (changes data to HIGH = Pressed) Data Direction Register is found at $03 Write $00 to this address to configure the port to read, Write $FF to this address to configure the port for writing 6808 Port A – Config Code PORT_A_DATA: EQU $00 ; define some constants PORT_A_INTERPRET: EQU $01 PORT_A_DIRECTION: EQU $03 configPortA: CLRA ; clear / zero the accumulator STA PORT_A_DIRECTION ; configure PORT A for reading input LDA #$FF ; interpretation value STA PORT_A_INTERPRET ; configure PORT A interpretation LDA PORT_A_DATA ; read the switch values Some notes on this code : CLRA is a special instruction that can be used to clear the accumulator’s value (zero it out). It is equivalent to LDA #$00 But CLRA only takes 1 clock cycle to execute (as opposed to the 2 that the load immediate instruction would) 6808 Port F The devices connected to this port are usually output devices (like LEDs) Before we can connect LEDs to this port, we need to configure it properly If you refer to the datasheet, you will see Port F’s: Data Register is found at $40 (HIGH = turn LED OFF) So writing the value $00 to this port will light up all of the LEDs Data Interpretation Register is found at $41 We can write $FF to this to flip the data register if we want Data Direction Register is found at $43 Since we are writing, then the value $FF needs to written here 6808 Port F – Config Code PORT_F_DATA: EQU $40 ; define some constants to use in code PORT_F_INTERPRET: EQU $41 ; PORT_F_DIRECTION: EQU $43 configPortF: LDA #$FF ; value to indicate output direction STA PORT_F_DIRECTION ; configure PORT F for writing output STA PORT_F_DATA ; make sure the LEDs are off ; if we had written the value $00 to Port F’s ; data register – all of the LEDs light up Looping in Assembly (and more) Writing an Assembly Program We previously looked at a generic CPU and its generic instruction set to see how you might actually use certain assembly code operations to write a program and perform some logic Please refer to that module for a review if you need it In that module we covered topics and instructions for doing Arithmetic and Boolean logic as well as Shifting Register and Memory manipulation (loading and storing) Branching and Jumping Also refer to “Useful tips on taking “C” Programs into the 6808 World” document on eConestoga to help you see how some of your C-Programming language ideas might translate into assembly code Branching, Jumping, and Looping We want to control how we branch, jump and loop – we do this through the use of the various statuses within the CCR Remember that the status bits in the CCR reflect the outcome of the last operation In the last module, we saw that assembly programming allows us to place labels within our source code These labels are used as markers within the code that we can branch to, jump to or loop on within the assembly source code When we assemble our code and produce the op-codes to execute, these labels are used in calculating offsets that can be added to the program counter and direct the program flow Branching, Jumping, and Looping - Example loop: LDA $0101 ; read the UART status register AND #%10000000 ; and the accumulator to extract ; the high-order bit BEQ loop ; if not, try again This translates to C60101A48027F9 in op-codes … let me explain where and how the F9 value maps back to loop Branching, Jumping and Looping According to the memory map for the 6808 (earlier in the slides) our code actually is loaded into memory starting at 0x182C – which we realize is a 16 bit address We saw that an operation’s argument value often time sets the addressing mode used when assembling the source code (i.e. immediate, 8-bit direct, 16-bit direct, etc.) The assembler is a smart program … it looks at your source code and keeps track of the number of bytes (of op-codes and argument values) that it has assembled between a label and a branch instruction. An instruction like BEQ uses relative addressing mode which takes a single signed byte value between -12810 and 12710 as an argument This argument value is added to the Program Counter (PC) to cause the branch / jump to occur Branching, Jumping and Looping Imagine our code was loaded at address 0x182C as below: 0x182C: C60101 0x182F: A480 0x1831: 27F9 When the IDU fetches the 27 opcode (BEQ) - remember that it update the Program Counter (PC) register to point to the next instruction … so the PC has the value 0x1832 When the IDU decodes the BEQ opcode, it realizes that it takes an argument – so it goes back out to memory to fetch the next byte. It reads the F9 argument and increments the PC to be pointing at 0x1833 So if we want to go back to 0x182C, what is F9? Branching, Jumping and Looping Imagine our code was loaded at address 0x182C as below: 0x182C: C60101 0x182F: A480 0x1831: 27F9 So if we want to go back to 0x182C, what is F9? After the F9 argument is read from memory, the PC is actually pointing at 0x1833 and we need to get back to 0x182C – we need to go back 7 bytes … we need to add -7 bytes to the PC F9 is the 2’s complement (negative) of 7! The assembler calculated the offset that instructs it to loop back (BEQ) to the label called loop Branching, Jumping and Looping We previously saw in the generic CPU that the goto instruction was implemented with a JMP command This is one way to unconditionally jump (or branch) If the conditions are right within your code, in 6808 assembly you can choose to use the BRA (branch always) instruction The only condition is that the place (label) that you are trying to branch to is within a relative address offset (a single byte) … -128 to 127 as with BEQ Branching, Jumping and Looping This is a matter of execution speed and efficiency (important for embedded programming) The JMP instruction uses 16-bit direct addressing and as such it takes 4 clock cycles to execute The BRA instruction uses relative addressing and a single byte offset – so it takes 3 clock cycles to execute – but the place that you’re jumping to needs to be within the -128 to 127 byte range Addressing Memory Let’s look at accessing memory From the memory map you might remember that RAM starts at $80 and goes to $107F If you need access memory, be conscious of the addresses you are working with $80 to $FF - uses 8-bit direct addressing (3 clock cycles) $100 to $107F - uses 16-bit direct addressing (4 clock cycles) Putting things Together Simple Example 6808 Ports A and F PORT_A_DATA: EQU $00 ; define some constants PORT_A_DIRECTION: EQU $03 PORT_A_INTERPRET: EQU $01 PORT_F_DATA: EQU $40 PORT_F_DIRECTION: EQU $43 main: BRA configurePorts ; call the “routine” to configure the ports loop: LDA PORT_A_DATA ; read the input values STA PORT_F_DATA ; reflect the switches in the LEDs BRA loop configurePorts: ; ---- Configure Port A ---- CLRA ; clear the value / zero out the accumulator STA PORT_A_DIRECTION ; configure PORT A for reading input LDA #$FF ; value to indicate pull-up interpretation STA PORT_A_INTERPRET ; configure PORT A to interpret a LOW from ; the switch to be a HIGH bit value ; ---- Configure Port F ---- LDA #$FF ; value to indicate output direction STA PORT_F_DIRECTION ; configure PORT F for writing output STA PORT_F_DATA ; make sure the LEDs are off by writing $FF to them BRA loop ; enter the main processing loop Architecting Your Assembly Code Architecting? We know it’s important to write routines / functions / methods in our code for reuse and structure So the question is – Can we write routines in assembly? Architecting? Of course we can! If we want to do this in a high-level language like C, we can do so by simply calling a function like this: doSomething(param1, param2); However, we aren’t that high-up on the programming food chain, so we need to take matters into our own hands Calling a Function In C when we call a function: We push the 2 parameter values onto the stack We remember the return address to come back to (by also pushing it onto the stack) We jump to the location in code (memory) where the function is found We use the 2 parameters from the stack to execute the function Then put the return value in the stack We jumping back to our remembered location Finally we pop the return value off the stack Calling a Function We need to do this in assembly. It sounds complicated, but because there are a couple of assembly instructions to help control this (JSR and RTS), the set of tasks we actually need to do is reduced Let’s take a quick view at how this might be accomplished in assembly A Simple DIVIDE Subroutine void main() { char numerator = 8; char denominator = 2; char answer = 0; // call a function to perform the dividing calculation answer = divide(numerator, denominator); } char divide(char num, char den) { char ans = 0; ans = num / den; return ans; } DIVIDE in Assembly NUMERATOR: EQU $80 ; value of numerator is at address $80 DENOMINATOR: EQU $81 ; value of denominator is at address $81 ANSWER: EQU $82 ; where to store the answer divide: PSHH ; preserve the H:X and A register values upon being called PSHX PSHA LDX 6, SP ; load the X register with the denominator CLRH ; need to clear out the H register LDA 7, SP ; load the numerator into the accumulator DIV ; perform the division STA 6, SP ; store the answer into the place on the stack where ; the denominator was PULA ; pop the saved H:X and A values off the stack PULX PULH RTS ; return to where we came from main: LDA NUMERATOR ; numerator value PSHA ; push numerator onto the stack – will get it later LDA DENOMINATOR ; denominator value PSHA ; push denominator onto the stack – will get it later JSR divide ; call the routine PULA ; pop the answer off the stack STA ANSWER AIS #1 ; clean up stack – there is one byte remaining – get rid of it What Just Happened? In order to call sub-routines (functions) we need to be aware and conscious of the fact that we don’t want to step on (and trash) any values currently stored in any other registers that we may want to use In order to temporarily save these values – we can store them on the stack We also need to use the 6808’s stack to pass and hold data when executing the sub-routine And we need to know that when the JSR mnemonic is executed, it pushes the current value of the PC onto the stack – so that when returning from the routine we know where to go back to, like a bread-crumb trail What Just Happened? We also need to be aware that the DIV mnemonic uses the 6808’s H:X register to help hold the numerator (in the H component and the A register) and the denominator (in the X component) The numerator is allowed to be a 16-bit value – in the H:A registers The denominator is allowed to be an 8 bit value in the X register And lastly, you need to be aware that the stack grows backwards into memory Meaning if the stack base is located at address $1000 and we push a byte value onto the stack, then the next place a value will be pushed onto the stack is at address $0FFF So now, let me explain the assembly code we just saw and tell you how it is executed Watch the Stack In our main program, we load the numerator value from its location in memory ($80) and push it onto the stack, then we do the same with the denominator and push its value onto the stack and then when we execute the JSR divide command – at this point, the system pushes the PC (16 bit register) onto the stack main: LDA NUMERATOR ; PSHA ; LDA DENOMINATOR ; PSHA ; JSR divide ; Watch the Stack Then inside the divide sub-routine – we are aware of the fact that the DIV mnemonic will use the H:X register and the A register. And since these registers may have had values stored in them that we care about – we need to preserve them – so we push those values onto the stack (for temporary storage) divide: PSHH PSHX PSHA Watch the Stack To call the 6808’s DIV instruction (from the manual): The denominator needs to be loaded into the X register The numerator is allowed to be a 16-bit value stored in the H and A registers as a pair (written as H:A). Our numerator is only 8-bits (how do we know that ?) so we zero out the H register and load the numerator into the A register The answer appears in the A register Watch the Stack LDX 6, SP ; load X with denominator CLRH ; clear H LDA 7, SP ; load A with numerator DIV ; perform the division But where do the mnemonic arguments “6, SP” and “7, SP” come from? Would it help if I told you that the 6808 treats its stack (and elements within it) like an array in C? Have a look at this diagram Watch the Stack LDX 6, SP ; load X with denominator CLRH ; clear H LDA 7, SP ; load A with numerator DIV ; perform the division So the LDX 6, SP and LDA 7, SP instructions are merely accessing the values stored at elements 6 and 7 within the stack array This is another addressing mode you find within devices – known as the indexed addressing mode Watch the Stack We now need to store (remember) the answer, which is presently in the A register. We store the answer in the stack where the denominator used to be We must also re-instantiate (restore) the values that we previously temporarily stored for the A, H and X registers We need to pop off the stack in the reverse order to which the values were pushed The stack shrinks as we do this STA 6, SP ; put the result in the stack PULA ; restore the saved PULX ; A, X, and H PULH Watch the Stack When we execute the RTS command in the sub-routine, the system (the 6808) pops the value of the 16-bit PC register off of the stack It knows it is there because it pushed it onto the stack when it called the sub-routine! As it pops off the value and restores it to the PC register we will resume execution at the address pointed to by the PC register Which happens to be the next executable line in our main loop after the JSR divide command Question : What do you think would happen at this point – if we accidentally overwrote one or both bytes of the PC on the stack while in our sub-routine ? Watch the Stack Back in the main loop – we pop the answer off the stack (and into the A register) and then store it into memory where it needs to go We decided to store the answer in the stack just after the PC value – we did this so that after we return (back in our main loop), we can just pop the answer off of the stack RTS ; return to where we came from main:... PULA ; pop the answer off the stack STA ANSWER Watch the Stack RTS main:... PULA STA ANSWER So now our stack looks like this Would it be a good idea to just leave the numerator value on the stack? How do we get rid of it and clean up the stack? Watch the Stack The final line of our main loop comes into play – the AIS #1 command The way that the AIS mnemonic works is that it takes an argument equal to the number of bytes to clean up (and throw away) that still remain on the stack AIS #1 ; clean up stack – there is one byte ; remaining – get rid of it Which leaves the stack looking empty Clean and ready for the next time we want to use it Well Structured Assembly Code We need to design our assembly code properly and as “cleanly” as possible If we aren’t paying attention to what we are doing with the stack or if we aren’t paying attention to the code inside of our subroutines We may be inadvertently coupling the code in our main and subroutines together We may be programming based on assumptions or side-effects of results in subroutines These are not good practices to follow and can cause you major headaches while executing and debugging Well Structured Assembly Code The next slide contains some pseudo-code for how you need to: set-up for … call … execute … and return from … a subroutine in assembly. This pseudo-code has been written assuming that we are calling a subroutine from our mainline assembly Region Step Action Taken Example MAIN 1 Push your function parameters onto the stack LDA NUMERATOR PSHA LDA DENOMINATOR PSHA MAIN 2 Call the subroutine JSR divide SUBROUTIN 3 Preserve the values in any registers you will use PSHH E in the subroutine PSHX PSHA SUBROUTIN 4 Do the work of the subroutine … remembering LDX 6, SP ; E to use the parameter values passed on the stack denominator as required CLRH LDA 7, SP ; numerator DIV SUBROUTIN 5 Store your answer (the return value from the STA 6, SP E function) on the stack in one of the locations used by the parameters from Step 1 SUBROUTIN 6 Re- Instantiate the values back into the registers PULA E that you preserved (undo Step 3) – in reverse PULX order! PULH SUBROUTIN 7 Return from the subroutine RTS E MAIN 8 Retrieve the answer from the stack (depending PULA on where you store it on the stack in Step 5 – it STA ANSWER could be the next on the stack) – and store it in memory if needed MAIN 9 Clean up all space still being use on the stack – AIS #1 leaving the stack as we found it before Step 1. NOTE: The number of bytes you need to remove from the stack is one less than the number of parameters pushed in Step 1 Subroutines That certainly was quite a lot of detail, but hopefully in breaking the execution and code down it shows you How much control you have over the CPU (and its registers) and How much detail you need to think about in the world of developing assembly programs This is not something to shy away from – there are great opportunities in the world of low-level (assembly) programming in real-time and embedded systems Another Example void main() { char celsiusTemp = 10; // let’s convert 10 Celsius into Fahrenheit char answer = 0; // call a function to perform the conversion answer = convertToFahrenheit(celsiusTemp);... } char convertToFahrenheit(char celsius) { // we will approximate the Celsius to Fahrenheit conversion as (X 2) + 32 char ans = 0; ans = (celsius * 2) + 32; return ans; } Watch the Stack! CELCIUS_TEMP: EQU $80 ; temperature to convert is at address $80 ANSWER: EQU $82 ; where to store the answer … conToFaren: PSHA ; why should we push the current accumulator value onto the stack ? LDA 4, SP ; load the Celcius temperature into the accumulator – why are we ; going to index 4 on the stack ? ASLA ; shift 1 bit to the left – what does this do ? ADD #32 ; add 32 to the value in the accumulator STA 4, SP ; we’ll return the Fahrenheit value on the stack in the ; same place as the Celcius value came in … why ? PULA ; restore the pre-subroutine value of the accumulator RTS ; return to where we came from … mainLoop: LDA CELCIUS_TEMP; get the temperature to convert into the accumulator PSHA ; push numerator onto the stack – to be used in subroutine CLRA ; zero out the accumulator – to make a point JSR conToFaren ; call the routine … NOP ; do nothing – but look at the accumulator – what is its value ? PULA ; pop the Fahrenheit temperature off the stack and STA ANSWER ; store it into memory where it is supposed to be stored ; any clean up to do on the stack – what does the stack look like?? BRA mainLoop Timing in the 6808 The Oscillator There are many different ways to create a clock signal within a CPU One of these ways is to use a crystal oscillator – like the Motorola 6808 uses The actual crystal used in the clock on the 6808 outputs a square wave (the clock signal) at a rate of 32 kHz (that’s 32000 oscillations per second) In order to stabilize the signal – the very first thing that happens to the clock signal is that it is divided by a factor of 2 This makes the actual clock speed of the 6808 only 16000 Hz (or 16 kHz) This means that the clock signal oscillates (goes up and down) 16000 times per second. The Clock This means that the clock signal oscillates (goes up and down) 16000 times per second. So what do we do if we want to know how long each clock cycle is? We know that there are 16000 of them in a second – but how long is each cycle? We use the relationship given by the following formula So, T = 1 / 16000 = 0.0000625 second = 62.5 microseconds Therefore the period (the time take for one complete clock cycle) in the 6808 is 0.0000625 seconds Measuring 6808 Code Measuring? Throughout this module, I’ve made mention of the fact of the number of clock-cycles that certain commands are taking As developers of low-level assembly code – this is something that we are always aware of and conscious of. We need to make sure that our code … Is efficient Makes the best use of resources Minimizes execution time Measuring? How do we do that? We do this by being aware of The instructions we write and use The addressing modes that we are using Where we store our data in RAM … we do this by being aware of our surroundings within the 6808 Table 4-10 (page 50) in the 6808’s Programming Reference show a master list of All instructions broken out into their various addressing modes It shows the effects that the operation has on the CCR It shows the number of clock-cycles that the instruction takes Cycle Counting Using this table and knowing which addressing-mode an instruction is using – we can calculate how long (the number of clock cycles) it will take a program to execute Let’s try it out loop: LDA $100 ; load a status value from memory location 0x0100 AND #%10000000 ; and the accumulator to extract the ; high-order bit BEQ loop ; if not, try again … LDA $101 ; read the data from memory location $0x0101 STA $1000 ; … store it where needed JMP loop ; wait for more data to be ready For each instruction we need to determine which addressing mode we are using and thereby determining the number of clock-cycles it takes to complete the instruction Table 4-10 (page 50) in the 6808’s Programming Reference comes in handy here Cycle Counting We can calculate how long (the number of clock cycles) it will take a program to execute Instruction Addressing Mode Clock Cycles LDA $100 16-Bit Direct (EXT) 4 AND #%10000000 8-Bit Immediate (IMM) 2 BEQ loop 8-Bit Relative (REL) 3 LDA $101 16-Bit Direct (EXT) 4 STA $1000 16-Bit Direct (EXT) 4 JMP loop 16-bit Direct (EXT) 3 * Remember that our program is stored at memory address 0x182C – a 16-bit address ! Cycle Counting So how much time will it take our program to determine if there is data to read, find out there is none and get ready to check again ? Time to execute is 4 + 2 + 3 clock cycles (at 16KHz) = 0.0005625 seconds And how much time does it take our program to determine if there is data to read, then to read it, store it and get ready to check again ? Time to execute is 4 + 2 + 3 + 4 + 4 + 3 clock cycles (at 16 KHz) = 0.00125 seconds Cycle Counting Are there optimizations we can make? Definitely! Instruction Addressing Mode Clock Cycles LDA $80 8-Bit Direct (DIR) 3 AND #%10000000 8-Bit Immediate (IMM) 2 BEQ loop 8-Bit Relative (REL) 3 LDA $81 8-Bit Direct (DIR) 3 STA $A0 8-Bit Direct (DIR) 3 JMP loop 16-bit Direct (EXT) 3 * Remember that our program is stored at memory address 0x182C – a 16-bit address ! Cycle Counting And now the time for our program to determine if there is data to read, then to read it, store it and get ready to check again is … Time to execute is 3 + 2 + 3 + 3 + 3 + 3 clock cycles (at 16 KHz) = 0.0010625 seconds That’s a savings of 0.0001875 seconds … if we were to repeat this operation 1,000,000 times in the execution of our program – that saves us 187.5 seconds (3 minutes, 7.5 seconds) in execution time