==================== m4 support in Opbasm ==================== The m4 preprocessor adds powerful facilities for enhanced PicoBlaze assembly. m4 is typically already present on most Linux systems. A precompiled Windows binary is included with Opbasm. Opbasm will automatically run m4 if a source file has the extension ".psm4" or ".m4" or on any file if the ``--m4`` option is used. The included macro package depends on some GNU extensions so GNU m4 must be used if the built-in macros are employed. Around 200 predefined macros are provided with Opbasm covering the following areas: +----------------------------------------------------+------------------------------------------------------+ | * `Stack operations`_ | * `String and table operations`_ | | * `Bitfield manipulations`_ | * `Scratchpad memory operations`_ | | * `Shift and rotate by multiple bits`_ | * `BCD conversion`_ | | * `Conditional jump, call, and return`_ | * `16-bit arithmetic, logical, and shift operators`_ | | * `Conditional if-then-else`_ | * `16-bit I/O operations`_ | | * `Looping`_ | * `Multiply and divide routines`_ | | * `Procedures and functions`_ | * `Expressions`_ | | * `Delay generators`_ | * `Random numbers`_ | +----------------------------------------------------+------------------------------------------------------+ None of the macros use the PicoBlaze-6 inactive bank registers. They are all portable to both targets although a few lose some functionality on PicoBlaze-3. Using these macros you can write code in a higher-level style like the following Fibonacci generator. +----------------------------------------------------------------+----------------------------------------------------------------+ | With macros: | After expansion: | +================================================================+================================================================+ |.. code-block:: picoblaze |.. code-block:: picoblaze | | | | | fibonacci: | fibonacci: | | ; Generate the first 10 Fibonacci numbers | ; Generate the first 10 Fibonacci numbers | | vars(s0 is counter, s1 is two_prev := 0, | LOAD s1, 00 ; Var two_prev := 0 | | s2 is prev := 1, s3 is next) | LOAD s2, 01 ; Var prev := 1 | | for (counter := 0, counter < 10, counter := counter + 1) { | ; Expression: s0 := 0 | | if (counter < 2) { | LOAD s0, 00 | | LOAD next, counter | | | } else { | FOR_f1_0001: | | ; Compute next number | ; If s0 < 10 | | expr(next := two_prev + prev) | COMPARE s0, 0a | | LOAD two_prev, prev | JUMP nc, GE_f1_0004 | | LOAD prev, next | | | } | ; If s0 < 2 | | push(next) | COMPARE s0, 02 | | CALL print_num ; Output the next number | JUMP nc, GE_f1_0006 | | } | LOAD s3, s0 | | RETURN | JUMP ENDIF_f1_0007 | | | | | | GE_f1_0006: | | | ; Compute next number | | | ; Expression: s3 := s1 + s2 | | | LOAD s3, s1 | | | ADD s3, s2 | | | LOAD s1, s2 | | | LOAD s2, s3 | | | | | | ENDIF_f1_0007: | | | STORE s3, (sf) ; Push | | | SUB sf, 01 | | | CALL print_num ; Output the next number | | | | | | NEXTFOR_f1_0002: | | | ; Expression: s0 := s0 + 1 | | | ADD s0, 01 | | | JUMP FOR_f1_0001 | | | GE_f1_0004: | | | ENDLOOP_f1_0003: | | | RETURN | | | | +----------------------------------------------------------------+----------------------------------------------------------------+ .. _guidance on installing m4 under Windows: Installing m4 on Windows ------------------------ Opbasm includes GnuWin32 Windows binaries for `m4 `_. It will be installed by the setup.py script. In the Cygwin environment, the included binary will not be executed and you must install the Cygwin version of m4. In the `Cygwin installer `_ select the m4 package (under Interpreters) for installation. Opbasm will be able to use m4 immediately within a Cygwin shell. Overview of m4 -------------- m4 is a general purpose macro preprocessor. It performs text based string manipulation through repeated expansion of previously defined macros including support for recursion. The preprocessor has a number of built in macros and provides a means to define your own. Macros have a name that contains letters, digits, and underscores. Any arguments are enclosed in parentheses and delimited by commas. Expansion is suppressed by enclosing text in quote characters which are ```'`` by default. Comments start with "#" by default but have been changed to use ";" to match PicoBlaze syntax. m4 uses different syntax from PicoBlaze assembly to represent different types of literals. It is important to know what context you are operating in to determine which type of literal to put in your source. ============ ============= ============ Type PicoBlaze m4 ============ ============= ============ Decimal 10'd 10 Hexadecimal 0a 0x0a Binary 00001010'b 0b1010 Char "A" A or ```A'`` ============ ============= ============ In general you use m4 syntax for literals passed as arguments to macros within parentheses. The only exception is :pb:macro:`pbhex` which takes a list of hex values in PicoBlaze format. Be careful not to use PicoBlaze hex format in other m4 contexts as it will be misinterpreted as decimal if only digits 0-9 are used. m4 includes the ability to evaluate arbitrary integer expressions using the builtin ``eval()`` macro. Its default output is an m4 decimal integer so the similar :pb:macro:`evalh`, :pb:macro:`evald`, and :pb:macro:`evalb` are provided to evaluate expressions resulting in PicoBlaze hex, decimal, or binary format. .. code-block:: picoblaze load s0, evald(4 * 5 + 1) ; Expands to "load s0, 21'd" The expression evaluator permits the natural use of negative decimal literals: .. code-block:: picoblaze load s0, evalh(-20) ; Expands to "load s0, ec" The :pb:macro:`evala` macro works like :pb:macro:`evalh` but expands to a 12-bit PicoBlaze address. .. code-block:: picoblaze define(DATA_ORG, 0x200) address evala(DATA_ORG) ; Expands to "address 200" m4 expressions support all of the C language operators as well as ``**`` for exponentiation. Note that :pb:macro:`evalh`, :pb:macro:`evald`, and :pb:macro:`evalb` cannot be nested within other macros because they expand with a comment reporting the original expression to make the listing file easier to read. If you need to evaluate an expression within another macro you should use the builtin ``eval()`` macro. Of particular note it is important to know that Picoblaze :ref:`inst-constant` directives are temporarily converted into an undocumented ``const()`` macro so that constants defined in Picoblaze syntax are accessible to m4. As a consequence you can't use the custom eval macros that generate a comment to compute a constant value. .. code-block:: picoblaze constant BAD_CONST, evalh(1+1) ; This will fail during m4 expansion constant GOOD_CONST, eval(1+1, 16, 2) ; Generate zero-padded hex constant An :pb:macro:`evalx` macro is available which works like the builtin ``eval()`` but also accepts strings that are not valid expressions. .. code-block:: picoblaze load s0, evalx(9 + 2, 16, 2) ; Expands to "load s0, 0b" constant CNAME, 1f load s0, evalx(CNAME) ; Expands to "load s0, CNAME" .. _m4-define: You can define aliases for registers without altering the original as with :ref:`inst-namereg`. .. code-block:: picoblaze define(alt_name, s0) load alt_name, 01 ; Expands to "load s0, 01" add s0, 01 ; s0 register is still visible Special logic is implemented in a preprocessor stage so that PicoBlaze constants are visible to m4. They are automatically converted from PicoBlaze format into m4 format. .. code-block:: picoblaze constant THE_ANSWER, 42'd expr(s0 := s1 + THE_ANSWER) ; Same as expr(s0 := s1 + 42) if(s0 > THE_ANSWER, `output s1, 00', `output s2, 00') ; Left operand is treated like a constant You can use also use ``define()`` to establish constants that are visible to m4 and create more complex macros. `Michael Breen's notes on m4 `_ provide a good introductory overview to m4. The `Gnu m4 manual `_ provides more detailed documentation. Type conversions ---------------- Some basic macros are provided to perform type conversions. They are useful for constructing parameters to other macros that only expect decimal values. The :pb:macro:`pbhex` macro is used to convert a list of values in PicoBlaze hex format into m4 decimals. .. code-block:: picoblaze pbhex(0a, 0b, ff) ; Expands to "10, 11, 255" The :pb:macro:`asciiord` macro converts a string of one or more characters to a list of decimals representing their ASCII encoding. Quotes are not strictly necessary but guard against including trailing whitespace. .. code-block:: picoblaze asciiord(0) ; Expands to "48" asciiord(`any str') ; Expands to "97, 110, 121, 32, 115, 116, 114" If you need a NUL terminated string, the :pb:macro:`cstr` macro works the same but appends a terminating 0: .. code-block:: picoblaze cstr(`1234') ; Expands to "49, 50, 51, 52, 0" The :pb:macro:`words_le` and :pb:macro:`words_be` macros convert a list of 16-bit numbers into little-endian or big-endian bytes. .. code-block:: picoblaze words_le(0xff01, 0xff02) ; Expands to "1, 255, 2, 255" words_be(0xff01, 0xff02) ; Expands to "255, 1, 255, 2" .. _m4-conditional-code: Conditional code ---------------- You may want to conditionally generate portions of a program or pass build time parameters to macros for different results. This can be accomplished with the m4 ``ifdef()`` macro. .. code-block:: picoblaze ifdef(`VARNAME`, ` ', ` ') ifdef(`VARNAME', `load s0, 10') ; Defined ifdef(`VARNAME',, `load s0, 20') ; Not defined load s1, MAXVAL You can omit either block of the ``ifdef()`` macro if you want generation only for the defined or undefined conditions. To control the selected code block you pass defined variables with the ``-D`` option to Opbasm: .. code-block:: console opbasm -DVARNAME -DMAXVAL=42 foo.psm4 This will define "VARNAME" as an empty string and "MAXVAL" with the string "42" which will be passed on unaltered to the assembler. These defined variables become macros which will be substituted with their value like any other macro. General purpose macros ---------------------- A few of the macros depend on modifying a temporary register. To simplify the macro calls, a preallocated temp register is used. It is set to `sE` by default. You can change it to another register by calling :pb:macro:`use_tempreg`. The temp register can be accessed in your own macros by using the ``_tempreg`` macro. The temp register is never preserved on the stack and you should not store data you want preserved across invocations of Opbasm macros. .. code-block:: picoblaze use_tempreg(sA) ; Switch to sA for the temp register The following macros use the temp register: ================ ================= ============= ============== =============== expr2s load_out load_store setcy use_multiply8x8 use_multiply8x8s use_multiply8x8su use_divide8x8 use_divide8x8s use_divide16x8 use_divide16x8s use_divide8xk use_random8 use_memcopy use_memwrite use_bcdwrite use_hexwrite use_int2bcd use_ascii2bcd use_bcd2int ================ ================= ============= ============== =============== The other :pb:macro:`expr` macros use the temp register indirectly when the mul and div operations are invoked. You can guard against accidentally using the temp register for long term storage by renaming it with the :ref:`inst-namereg` directive: .. code-block:: picoblaze namereg sE, TEMPREG use_tempreg(TEMPREG) Now you can't accidentally assign something to ``sE`` that will be overwritten by a macro using the ``_tempreg`` macro. PicoBlaze programs commonly contain lists of constant declarations for IO port addresses. The :pb:macro:`iodefs` macro simplifies their declaration by allowing contiguous sequences of ports to be named in one statement. It can also be used to define scratchpad addresses. .. code-block:: picoblaze ; Usage: iodefs(, [port names]+) iodefs(0, P_control, P_read, P_write) ; Expands to: constant P_control, 00 constant P_read, 01 constant P_write, 02 The :pb:macro:`vars` macro allows you to associate alias names with a register. Unlike the :ref:`inst-namereg` directive, the original register name is still available. An optional initial value can be provided: .. code-block:: picoblaze ; Usage: vars([ is [:= ]]+) vars(`s0 is count := 0', `s1 is sum') ; Expands to: load s0, 00 Symbols "count" and "sum" can now be used in place of s0 and s1. You should quote each variable declaration to avoid macro expansion errors when redefining an existing variable. Use the :pb:macro:`popvars` macro to remove all variables defined in the previous call to :pb:macro:`vars`. .. _stack-operations: Stack operations ---------------- A set of macros are available to simulate a stack using the scratchpad RAM. You initialize the stack and establish the stack pointer register with a call to :pb:macro:`use_stack`. After that you can call :pb:macro:`push` and :pb:macro:`pop` to manage registers on the stack. You can push and pop any number of registers at once. Pops happen in reverse order to preserve register values when passed the same list as :pb:macro:`push`. The stack grows down so the initial address should be the highest the stack will occupy. .. code-block:: picoblaze namereg sF, SP ; Protect sF for use as the stack pointer use_stack(SP, 0x3F) ; Start stack at end of 64-byte scratchpad ... my_func: push(s0, s1) pop(s0, s1) return The :pb:macro:`getstack`, :pb:macro:`getstackat`, and :pb:macro:`dropstack` macros can be used to retrieve and drop values from a stack frame. This provides a facility for passing function arguments on the stack and is particularly useful for writing functions that take a variable number of arguments. The argument to :pb:macro:`dropstack` can be a register to drop a variable number of arguments. .. code-block:: picoblaze load s0, BE push(s0) ; First argument load s0, EF push(s0) ; Second argument call my_func2 my_func2: getstack(s3, s4) ; Retrieve first and second argument dropstack(2) ; Remove arguments from the stack return You can use the :pb:macro:`getstackat` macro to retrieve values from the stack one at a time in any order. .. code-block:: picoblaze my_func3: getstackat(s4, 1) ; Retrieve second argument (SP + 1) getstackat(s3, 2) ; Retrieve first argument (SP + 2) dropstack(2) ; Remove arguments from the stack return You may wish to allocate temporary space on the stack for local variables in a function. Use the :pb:macro:`addstack` macro to accomplish this. :pb:macro:`putstack` and :pb:macro:`putstackat` are used to store register values on the stack without altering the stack pointer. .. code-block:: picoblaze my_func4: addstack(4) ; Add 4 bytes to the stack to work with putstack(s0, s1, s2, s3) getstackat(s4, 2) dropstack(4) ; Remove local frame .. _Bitfield manipulations: Bitfield operations ------------------- A set of macros are available to manipulate bitfields without manually constructing hex masks. .. code-block:: picoblaze load s0, f0 setbit(s0, 0) ; s0 = f1 setbit(s0, 2) ; s0 = f5 clearbit(s0, 7) ; s0 = 75 setmask(s0, mask(0,1,2,3)) ; s0 = 7f clearmask(s0, mask(4,5,6,7)) ; s0 = 0f testbit(s0, 0) ; Test if bit-0 is set or clear jump nz, somewhere The :pb:macro:`maskh` macro works like :pb:macro:`mask` but produces a result in PicoBlaze hex format so it can be used as a direct argument to any instruction that takes a constant. .. code-block:: picoblaze load s0, maskh(0,1,2,6,7) ; Expands to "load s0, c7" .. _Shift and rotate by multiple bits: Shift and rotate ---------------- Shifts and rotates are inconvenient in PicoBlaze assembly because they must be performed one bit at a time. Macros are provided that generate shifts and rotates by any number of bits more easily. The shift amount must be a constant integer. It cannot come from another register. .. code-block:: picoblaze load s0, 01 sl0(s0, 4) ; Shift left by 4 bits s0 = 00010000'b sr1(s0, 3) ; Shift right by 3 bits with 1's inserted s0 = 11100010'b All 10 of the PicoBlaze shift and rotate instructions have macro equivalents. The original instructions can still be used as usual. =============== =============== =============== =============== =============== :pb:macro:`sl0` :pb:macro:`sl1` :pb:macro:`sla` :pb:macro:`slx` :pb:macro:`rl` :pb:macro:`sr0` :pb:macro:`sr1` :pb:macro:`sra` :pb:macro:`srx` :pb:macro:`rr` =============== =============== =============== =============== =============== .. _Conditional jump, call, and return: Conditional jump call and return -------------------------------- PicoBlaze assembly depends on using the carry and zero flags directly to handle conditional :ref:`inst-jump` and :ref:`inst-call` instructions. It can be difficult to remember how the carry flag is interpreted so a set of macros are provided to perform more natural conditional instructions. .. code-block:: picoblaze compare s0, s1 jne(not_equal) ; Jump if s0 != s1 jeq(equal) ; Jump if s0 == s1 jge(greater_or_equal) ; Jump if s0 >= s1 jlt(less_than) ; Jump if s0 < s1 callne(not_equal) ; Call if s0 != s1 calleq(equal) ; Call if s0 == s1 callge(greater_or_equal) ; Call if s0 >= s1 calllt(less_than) ; Call if s0 < s1 retne ; Return if s0 != s1 reteq ; Return if s0 == s1 retge ; Return if s0 >= s1 retlt ; Return if s0 < s1 Conditional if-then-else ------------------------ A high level :pb:macro:`if` macro is present that provides evaluation of infix Boolean expressions. It takes the form of ``if(,,[,...|])``. The expression syntax uses conventional C operators ==, !=, <, ,>=, >, <=, &, and ~&. Additional expressions after the first true block produce else-if evaluation similar to m4's ``ifelse()`` macro. It is important to guard code blocks with m4 quotes to avoid errors caused by m4 splitting strings with internal commas. The :pb:macro:`if` macro implements a :ref:`inst-compare` instruction and generates the appropriate branch logic to test the flags. Unique generated labels are inserted into the code to manage the sequencing of the code blocks. .. code-block:: picoblaze load s0, 05 if(s0 < 10, `load s1, "T" output s1, 00', ; else-if s0 < 8, `load s1, "t" output s1, 01', ;else `load s1 "F" output s1, 02' ) In addition, the & and ~& operators can be used to generate a :ref:`inst-test` instruction instead of :ref:`inst-compare`. For & the true block is executed if the test result is non-zero: .. code-block:: picoblaze ; Check if MSB is set if(s0 & 0x80, `load s1, 00') For ~& the true block is executed if the test result is zero: .. code-block:: picoblaze ; Check if MSB is clear if(s0 ~& 0x80, `load s1, 00') You can invoke signed comparison using the :pb:macro:`compares` macro by wrapping the expression in :pb:macro:`signed`: .. code-block:: picoblaze load s0 evalh(-10) ; -10 = 0xF6 which evaluates as > 5 in unsigned comparison if(signed(s0 < 5),`load s1, 00') ; evaluate as < 5 using signed comparison Macros can be used within the code blocks including nested :pb:macro:`if` macros: .. code-block:: picoblaze if(s0 < s1, `', ; else `if(s2 >= s3,`')' ) .. note:: The ``>`` and ``<=`` operators have to be simulated because the limited Picoblaze ALU flags don't permit them to be implemented directly. If both operands are registers they are swapped and the reverse comparison operation (``<`` or ``>=`` ) is performed. If the right operand is a constant it has to be adjusted by adding one to its value and swapping the true and false conditional blocks. For instance "s0 > 0x20" is converted to "s0 <= 0x21" with the false condition (originally true) executed when s0 is greater than 0x20. This can lead to problems when doing comparisons with 0xFF because the 0x100 can't be used as an immediate instruction value. You may have to find alternate ways to express comparison logic when dealing with the 0xFF and 0x00 boundary values. Consider a loop counter that you want to terminate after passing 0xFF. Instead of testing for "sN > 0xFF" you should test for "sN != 0" and ensure that this won't cause early termination at the start of the loop. .. _c-style-if-then: C-style syntax ~~~~~~~~~~~~~~ The m4 syntax for the :pb:macro:`if` macro is a little untidy but an alternate C-style syntax can be used. It is implemented using an initial preprocessing step where pattern matching converts C-style control flow statements into m4 syntax. Instead of m4 quotes, code blocks are surrounded by mandatory curly braces. Unlike m4 macros, whitespace is permitted between the ``if`` keyword and its comparison expression. .. code-block:: picoblaze if (s0 < s1) { load s0, "T" } else if (s2 == s3) { load s0, "t" } else { load s0, "F" } A set of lower level if-then-else macros are provided to expose the internal workings of :pb:macro:`if`. The macros are :pb:macro:`ifeq`, :pb:macro:`ifne`, :pb:macro:`ifge`, and :pb:macro:`iflt`. Unlike :pb:macro:`if`, no :ref:`inst-compare` or :ref:`inst-test` instruction is generated from an expression. You have to prepare the flags on your own. The first argument is the code to execute for the true condition. An optional second argument is used for the else clause. .. code-block:: picoblaze compare s0, s1 ifeq( `load s4, 20 output s4, PORT', ; else `load s4, 30 output s4, PORT2') This expands to the following: .. code-block:: picoblaze compare s0, s1 jump nz, NEQ_f1_0001 load s4, 20 output s4, PORT jump ENDIF_f1_0002 NEQ_f1_0001: ; else load s4, 30 output s4, PORT2 ENDIF_f1_0002: Looping ------- Similarly to :pb:macro:`if` there are a set of high level looping macros :pb:macro:`for`, :pb:macro:`while`, and :pb:macro:`dowhile`. They implement the corresponding looping constructs using the syntax ``for(,,,)`` and ``[do]while(,)``. Signed comparison is supported just as with :pb:macro:`if` using the :pb:macro:`signed` macro as a modifier. The for loop macro uses the :pb:macro:`expr` :ref:`macro syntax` for the *init* and *update* fields. .. code-block:: picoblaze for(s0 := -10, signed(s0 < 10), s0 := s0 + 1, `output s1, P_FOO' ) .. code-block:: picoblaze ; Output s1 to port 00 10 times load s0, 00 while(s0 < 10, `output s1, P_FOO add s0, 01' ) .. _c-style-looping: C-style syntax ~~~~~~~~~~~~~~~ An alternate C-style syntax is also available for :pb:macro:`for`, :pb:macro:`while`, and :pb:macro:`dowhile`. Note that the :pb:macro:`for` macro continues to use commas to separate the sections. .. code-block:: picoblaze ; For loops for (s0 := 0, s0 < s1, s0 := s0 + 1) { output s0, P_FOO } ; While loops while (s0 < s1) { add s0, 01 output s0, P_FOO } ; Do-while loops do { add s0, 01 output s0, P_FOO } while (s0 < s1) Two macros, :pb:macro:`break` and :pb:macro:`continue`, are available to exit the current loop and restart a loop respectively. In a for loop the :pb:macro:`continue` macro will execute the *update* field expression to prepare the next iteration. .. code-block:: picoblaze ; "continue" resumes execution here while (s0 < s1) { add s0, 01 if (s3 == 4) { continue } if (s2 == 5) { break } output s0, 00 } ; "break" resumes execution here Procedures and Functions ------------------------ A set of macros are available that can streamline the creation of procedures, functions, and interrupt service routines. All of these macros have a C-style block syntax which is the preferred way to invoke them. proc ~~~~ The most basic is the :pb:macro:`proc` macro which is a convenience routine creating a labeled code block with an included :pb:macro:`vars` macro for variable definitions, a final :ref:`inst-return` instruction, and automatic ";PRAGMA" comments identifying it as a function. .. code-block:: picoblaze proc addinc(s0 is count, s1 is inc) { add count, inc } ... call addinc ; Expands to: ;PRAGMA function addinc [s0 is count, s1 is inc] begin addinc: ADD s0, s1 RETURN ;PRAGMA function addinc end CALL addinc The "argument" list to proc is passed on to the :pb:macro:`vars` macro. It can include local variables used by the procedure. You are responsible for loading arguments into registers and cleaning up temporary registers. func ~~~~ The :pb:macro:`func` macro provides a more elaborate function generator that takes care of handling arguments by passing them on the stack. A dynamically generated macro is created for calling each defined function. :pb:macro:`func` takes a list of registers to pass as arguments as well as an optional number of bytes for values returned on the stack. those registers are placed on the stack and then popped into local registers that are saved and restored after the function completes. The argument list is in the same "Sn is Y" syntax used by the :pb:macro:`vars` macro but you can also just list register names without providing an alias. .. code-block:: picoblaze ; func () : {} func addinc(s0 is count, s1 is inc): 1 { add count, inc retvalue(count, 1) ; Save the return value on the stack } ... ; Call function with s3 and s4 as args addinc(s3, s4) pop(s5) ; Get the return value ; Expands to: ;PRAGMA function addinc [stack(s0 is count, s1 is inc : 1)] begin addinc: ADD s0, s1 LEAVE_addinc: RETURN ;PRAGMA function addinc end ; Call function with s3 and s4 as args ; Push arguments: STORE s3, (sf) ; Push SUB sf, 01 STORE s4, (sf) ; Push SUB sf, 01 CALL addinc ADD sf, 01 FETCH s5, (sf) ; Pop After the function call the registers will be in the same state they were before the function call and any return values will be on the stack. Unlike with :pb:macro:`proc` the parameter list is only used to define arguments. You are responsible for preserving any registers used internally for local variables. The :pb:macro:`retvalue` macro takes a register for its first argument and the index of the return byte from the top of the stack starting from 1. You cannot use a :ref:`inst-return` instruction inside the code body of a :pb:macro:`func` macro because the stack cleanup code will not be executed. Instead you must call the :pb:macro:`leave_func` macro whenever you want to exit early. It will ensure the cleanup code is executed. isr ~~~ A variant of the :pb:macro:`func` macro is available for defining ISRs. The :pb:macro:`isr` macro is similar to :pb:macro:`func` but you specify an address for the interrupt vector instead of a name and in place of the return byte count you specify whether the ISR returns with interrupts enabled or disabled. Interrupts are enabled by default if the last parameter is omitted. .. code-block:: picoblaze ; isr
() : [enable | disable] {} isr 0x3FF(s0) : enable { output s0, FF } ; Expands to: __ISR: ADDRESS 3ff ; 0x3FF JUMP __ISR ADDRESS __ISR ;PRAGMA function __ISR begin OUTPUT s0, FF LEAVE___ISR: RETURNI enable ;PRAGMA function __ISR end ISRs take no arguments and the variable list only serves to identify which registers are used in the ISR so that they can be saved on the stack. There can only be one :pb:macro:`isr` macro call in a program. You can use :pb:macro:`leave_func` or the equivalent :pb:macro:`leave_isr` macro to exit early from an ISR. Do not call :ref:`inst-returni` directly within the ISR code block as that will leave saved registers on the stack without cleaning up. Delay generators ---------------- A set of delay generator macros are available to implement software delays. The simplest is :pb:macro:`delay_cycles` which delays by a number of instruction cycles (each being two clock cycles). By default it is implemented with recursive loops and requires no registers to function. .. code-block:: picoblaze delay_cycles(40) ; Delay for 40 instructions (80 clock periods) This expands to the following recursive code implemented in 13 instructions: .. code-block:: picoblaze CALL DTREE_f1_0001_4 ; Delay for 33 cycles JUMP DTREE_f1_0001_end DTREE_f1_0001_4: CALL DTREE_f1_0001_3 DTREE_f1_0001_3: CALL DTREE_f1_0001_2 DTREE_f1_0001_2: CALL DTREE_f1_0001_1 DTREE_f1_0001_1: CALL DTREE_f1_0001_0 DTREE_f1_0001_0: RETURN DTREE_f1_0001_end: CALL DTREE_f1_0002_1 ; Delay for 5 cycles JUMP DTREE_f1_0002_end DTREE_f1_0002_1: CALL DTREE_f1_0002_0 DTREE_f1_0002_0: RETURN DTREE_f1_0002_end: LOAD sf, sf ; NOP LOAD sf, sf ; NOP The delay can be from 0 to approximately 100e9 but a practical limit would be to keep the delay less than 200 cycles to restrict the amount of generated code. You must ensure that there is enough space on the call stack to perform the recursive calls. In the example above the 33-cycle delay block extends five calls deep. An alternate implementation of :pb:macro:`delay_cycles` can be invoked by first configuring it with the :pb:macro:`use_delay_reg` macro. You call it with a single register to use for a delay counter. This register must be different than the ones used for the long period delay macros described next. With a delay register configured, the :pb:macro:`delay_cycles` macro will be implemented as a small loop for delays of 511 cycles or less. Longer delays will fall back to using recursive delay trees. .. code-block:: picoblaze use_delay_reg(s6) delay_cycles(40) ; Expands to: LOAD s6, 13 ; (40 - 1) / 2 DLOOP_f1_0001: SUB s6, 01 JUMP nz, DLOOP_f1_0001 LOAD se, se ; NOP Time delays ~~~~~~~~~~~ Delays by microseconds and milliseconds are implemented with the :pb:macro:`delay_us` and :pb:macro:`delay_ms` macros. Before using these you must establish the system clock frequency with the :pb:macro:`use_clock` macro. These delays are cycle accurate if the requested delay is an integer multiple of the clock period. They have the ability to adjust the delay down by a certain number of instructions if needed to account for function call or loop overhead. .. code-block:: picoblaze use_clock(100) ; 100 MHz system clock use_delay_reg(s6) ; Use compact internal delay loop ; 10 ms delay subroutine delay_10ms: delay_ms(10, s4,s5, 2) ; Adjust delay by 2 instructions for call and return return ... call delay_10ms ; Exactly 10 ms have passed here ... delay_ms(10, s4, s5) ; Inline delay by 10 ms ; Exactly 10 ms have passed here The ``delay_*()`` macros take a delay value, a pair of registers and an optional instruction adjustment as arguments. The delay value is the amount of delay in the associated units. The upper delay limit depends on the clock frequency. It has a complex relationship that is approximated by the equation ``max_delay = 22.05e6 * clock_freq ^ -1.0016`` . You will get a macro error if a delay is too large for the currently selected frequency. The following table shows the maximum delays for representative clock frequencies: ======= ======= 50 MHz 429 ms 75 MHz 286 ms 100 MHz 214 ms 125 MHz 171 ms 150 MHz 143 ms ======= ======= The registers are used for an internal 16-bit counter. The internal delay loop is automatically adjusted to ensure the count value fits within 16-bits. When implementing a delay as a subroutine, an adjustment can be added to account for the :ref:`inst-call` and :ref:`inst-return` instructions. Variable delays ~~~~~~~~~~~~~~~ If you need to use multiple delays it may be desirable to have a common delay routine that supports variable delay counts. This is provided by the :pb:macro:`var_delay_us` and :pb:macro:`var_delay_ms` macros. They are similar to the fixed delays but are not cycle accurate and have no provision for adjustment. .. code-block:: picoblaze use_clock(50) ; 50 MHz system clock define(MAX_DELAY, 200) ; Maximum 200 us delay var_delay: var_delay_us(MAX_DELAY, s4,s5) return ... load16(s4,s5, var_count_us(20, MAX_DELAY)) ; 20 us delay call var_delay ... load16(s4,s5, var_count_us(150, MAX_DELAY)) ; 150 us delay call var_delay The first argument to the ``var_delay_*()`` macros is the maximum delay value to support. When a delay is needed you must load the count registers with a constant computed with the ``var_count_*()`` macros. .. _string and table ops: .. _String and table operations: String and table operations --------------------------- PicoBlaze-3 doesn't have the ability to handle strings as efficiently as PB6 because it lacks the :ref:`inst-load_return` instruction but it is still necessary to work with them at times. Suppose that you have a subroutine "write_char" that writes characters in s0 out to a peripheral. You can write entire strings with the following: .. code-block:: picoblaze callstring(write_char, s0, `My string') ; Note use of m4 quotes `' to enclose the string This expands to the following: .. code-block:: picoblaze load s0, "M" call write_char load s0, "y" call write_char load s0, " " call write_char ... load s0, "n" call write_char load s0, "g" call write_char Similarly you can call with arbitrary bytes in a table. The :pb:macro:`pbhex` macro is useful here to express hex numbers with less clutter. .. code-block:: picoblaze calltable(write_char, s0, pbhex(DE, AD, BE, EF)) There are four targets for string and table macros: "call", "output", "store", and "inst". They work similarly to the "call" macros above but generate :ref:`inst-output`, :ref:`inst-store`, or :ref:`inst-inst` instructions in place of :ref:`inst-call`. ========== ============ =========== ============= ========================== callstring outputstring storestring storestringat calltable outputtable storetable storetableat insttable_le, insttable_be ========== ============ =========== ============= ========================== The :pb:macro:`storestringat` and :pb:macro:`storetableat` macros take a register as a pointer to the destination scratchpad address. The pointer register is incremented after storing each byte except for the last. .. code-block:: picoblaze constant M_DATA, 10 load s0, M_DATA storestringat(s0, sF, `Store this') ; sF is used as a temp register The :pb:macro:`insttable_le` and :pb:macro:`insttable_be` macros generate packed :ref:`inst-inst` directives for use as static data. The former generates little-endian instructions while the latter is big-endian. .. code-block:: picoblaze insttable_le(pbhex(0a, 0b, 0c)) ; Expands to: inst 00b0a ; inst 0000c insttable_be(pbhex(0a, 0b, 0c)) ; Expands to: inst 00a0b ; inst 00c00 The insttable macros only accept a list of decimal values directly but the :pb:macro:`asciiord` macro can be used to convert strings to numeric data. .. code-block:: picoblaze insttable_le(asciiord(`Pack strings into ROM')) ; Expands to: inst 06150 inst 06b63 inst 07320 ... inst 0206f inst 04f52 inst 0004d This permits the compact storage of data bytes in the PicoBlaze ROM. If synthesized as a dual-ported block RAM, the data can be retrieved with external logic. The ``picoblaze_dp_rom`` component included with `picoblaze_rom.vhdl `_ provides a second read/write port for this purpose. Escaped strings ~~~~~~~~~~~~~~~ The native PicoBlaze syntax does not permit the use of character escapes in strings. The macros :pb:macro:`estr` and :pb:macro:`cstr` provide a means for generating escaped strings without and with a NUL terminator respectively. They generate a list of integers representing each character in the string. The following C-style backslash escape codes are supported: ====== ==================== Escape Meaning ====== ==================== `\\\\` Literal "\\" \\n Newline \\ Line Feed \\r Carriage Return \\b Backspace \\a Bell \\e Esc \\s Literal semicolon ====== ==================== On PicoBlaze-6 you can apply the output of these macros directly in a :ref:`inst-table` directive as follows: .. code-block:: picoblaze table hello#, [dec2pbhex(cstr(`Hello\r\n'))] ; This expands to: table hello#, [48, 65, 6c, 6c, 6f, 0d, 0a, 00] table hello2#, [dec2pbhex(estr(`Hello\r\n'))] ; This expands to: table hello2#, [48, 65, 6c, 6c, 6f, 0d, 0a] For PicoBlaze-3 you can pass the output of :pb:macro:`estr` and :pb:macro:`cstr` to the :pb:macro:`calltable`, :pb:macro:`storetable`, and :pb:macro:`outputtable` macros or use the portable string macros described next. If you need to know the length of a string constant you can use :pb:macro:`strlenc` to generate that value. It takes a single string argument that can contain escaped characters. It is passed through :pb:macro:`estr` to remove escapes before characters are counted. :pb:macro:`strlenc` only works at compile time when passed a string literal or a named portable/packed string. It does not work at runtime on dynamic string buffers. .. code-block:: picoblaze load s0, strlenc(`foobar\r\n') ; Expands to 8 You can also pass the label to a string defined with :pb:macro:`string` or :pb:macro:`packed_string` to retrieve their length. .. code-block:: picoblaze packed_string(my_string, `This is a string') load s0, strlenc(my_string) ; Expands to 16 .. note:: m4 has a builtin macro ``len()`` that also returns the length of strings. However, it does not account for escape characters and will include blackslashes in its count. .. _Portable string and table operations: Portable strings ~~~~~~~~~~~~~~~~ A simplified system for generating efficient, portable strings is provided by the macro package. With this you can create string handling code that will expand into the most efficient form for PicoBlaze-3 or PicoBlaze-6 allowing you to easily migrate between platforms. You must first setup the portable string system with the :pb:macro:`use_strings` macro. It configures the registers and a character handling routine used when processing a string. :pb:macro:`use_strings` takes the following arguments: * Arg1: Register loaded with each character * Arg2, Arg3: MSB, LSB of string address (Only used on PB6. Use dummy registers for PB3) * Arg4: Label of a user provided function called to process each character * Arg5: Optional name of the macro to define new strings (default is "string") After configuring string handling with :pb:macro:`use_strings` you must define each string using the :pb:macro:`string` macro. It takes two arguments. The first is a label to identify the string and the second is the string. You can use any of the escapes supported by :pb:macro:`estr` and :pb:macro:`cstr` in a string. Strings are reproduced by calling them with the label used in their definition. Labels should not end with a "$" like with the :ref:`inst-string` directive. .. code-block:: picoblaze jump main use_strings(s0, s5,s6, write_char) proc write_char(s0) { output s0, 00 } string(hello, `Hello world\r\n') ; Define a string called "hello" main: ... call hello ; Call write_char on each character in the "hello" string This expands to the following when targeting PB6: .. code-block:: picoblaze JUMP main ; PB6 common string handler routine __string_handler: CALL@ (s5, s6) ; Read next char COMPARE s0, 00 ; Check if NUL RETURN z CALL write_char ; Handle the char ADD s6, 01 ; 1 ADDCY s5, 00 ; Increment address JUMP __string_handler ;PRAGMA function write_char [s0] begin write_char: OUTPUT s0, 00 RETURN ;PRAGMA function write_char end ; "Hello world\r\n" TABLE hello#, [48, 65, 6c, 6c, 6f, 20, 77, 6f, 72, 6c, 64, 0d, 0a, 00] hello: LOAD s5, _hello_STR'upper LOAD s6, _hello_STR'lower JUMP __string_handler _hello_STR: LOAD&RETURN s0, hello# ; Define a string called `"hello"' main: ... CALL hello ; Call write_char on each character in the "hello" string Note that a common string processing routine ``__string_handler`` is generated after the call to ``jump main`` and the escaped string is implemented with :ref:`inst-load_return` instructions. When targeting PB3 the following expansion results: .. code-block:: picoblaze JUMP main ;PRAGMA function write_char [s0] begin write_char: OUTPUT s0, 00 RETURN ;PRAGMA function write_char end ; "Hello world\r\n" hello: LOAD s0, 48 CALL write_char LOAD s0, 65 CALL write_char LOAD s0, 6c CALL write_char LOAD s0, 6c CALL write_char ... LOAD s0, 0d CALL write_char LOAD s0, 0a CALL write_char RETURN ; Define a string called `"hello"' main: ... CALL hello ; Call write_char on each character in the "hello" string The PB3 version does not generate a common handler routine but instead generates code to handle each string in place using the :pb:macro:`calltable` macro. You are limited to a single user provided function for processing each character in a string. If you need to perform different operations on strings then you will have to use a register or scratchpad value to select the desired behavior before calling the string label and write a handler routine that checks what operation is needed for each character it receives. Packed strings ~~~~~~~~~~~~~~ A set of macros for handling packed strings is available for use. These work similarly to the portable string macros but rely on character data packed with :ref:`inst-inst` directives. This is the most efficient way to store uncompressed strings in PicoBlaze memory. Access to the data must be implemented with external hardware that can read instruction memory through a second port. The ``picoblaze_dp_rom`` component defined in `picoblaze_rom.vhdl `_ shows a way to accomplish that. The same code is generated for both PB3 and PB6. To configure packed strings you need to call the :pb:macro:`use_packed_strings` macro. It is similar to :pb:macro:`use_strings` but you also need to provide a function that retrieves character pairs from an address in memory. Its arguments are the following: * Arg1: Register to store even characters (0, 2, 4, ...) * Arg2: Register to store odd characters (1, 3, 5, ...) * Arg3, Arg4: Registers for MSB, LSB of address to string * Arg5: Label of user provided function called to process each character (Only needs to handle the even char register) * Arg6: Label of user provided function called to read pairs of characters from memory * Arg7: Optional name of the macro to define new strings (default is "packed_string") Character pairs are stored in big-endian order. The first character in a string is stored in the upper byte of an :ref:`inst-inst` directive. The read routine takes a set of registers for the address of a packed character pair. It must retrieve the ``INST`` data at that location and load the upper byte into the even character register and lower byte in the odd character register. A common handler routine ``__packed_string_handler`` is generated so you must ensure the execution path bypasses the generated code. After configuration you define strings with the :pb:macro:`packed_string` macro just as with the :pb:macro:`string` macro. .. code-block:: picoblaze jump main mem16(P_ROM, 0x0b,0x0a) ; Define 16-bit port addresses for dual-ported ROM use_packed_strings(s0,s1, s5,s6, write_char, read_next_chars) proc write_char(s0) { output s0, 00 ; Using register for even chars } proc read_next_chars(s0,s1, s5,s6) { output16(s5,s6, P_ROM) ; Select next address from second port nop input16(s0,s1, P_ROM) ; Read back upper and lower byte } packed_string(hello, `Hello world\r\n') ; Define a packed string called "hello" main: ... call hello ; Call write_char on each character in the "hello" string This expands to the following on both target processors: .. code-block:: picoblaze ; "Hello world\r\n" hello: LOAD s5, _hello_STR'upper LOAD s6, _hello_STR'lower JUMP __packed_string_handler _hello_STR: INST 04865 INST 06c6c INST 06f20 INST 0776f INST 0726c INST 0640d INST 00a00 ; Define a packed string called `"hello"' main: CALL hello You can see that the 13 byte string is stored into 7 instruction words providing the densest string storage possible without resorting to compression. If you have existing code using the portable string macros, you can convert it to use packed strings by changing the macro name with the optional seventh argument: .. code-block:: picoblaze use_packed_strings(s0,s1, s5,s6, write_char, read_next_chars, string) Multi-function strings ~~~~~~~~~~~~~~~~~~~~~~ Most of the previous string handling routines are hard-coded to use a single callback routine like ``write_char`` to process characters. This function does not need to be limited to just outputting data on a port. It also does not need to be limited to a single operation. You can use a register or scratchpad location to alter its behavior for different needs. .. code-block:: picoblaze constant M_CHAR_MODE, 00 constant P_CONSOLE, FF constant CHAR_OUT, 01 constant CHAR_COPY, 02 use_strings(s0, s5,s6, handle_char) proc handle_char(`s0 is ch', `sA is ptr') { fetch _tempreg, M_CHAR_MODE if(_tempreg == CHAR_COPY) { ; Store in a scratchpad buffer store ch, (ptr) add ptr, 01 } else { ; CHAR_OUT ; Write to console output ch, P_CONSOLE } } string(hello, `Hello again\n') ... ; Write string to a port load_store(CHAR_OUT, M_CHAR_MODE) call hello ; Copy string to a scratchpad buffer load_store(CHAR_COPY, M_CHAR_MODE) load sA, 10 ; Start address call hello load_store(NUL, sA) ; Write NUL to end of string buffer .. _Scratchpad memory operations: Scratchpad memory operations ---------------------------- A set of routines are available for manipulating arrays in scratchpad memory. They are accessed by invoking a ``use_XXX()`` generator macro to create the functions with register allocations of your choice. All of these macros take an initial argument that is the name of the generated function. They all preserve their input and temporary registers on the stack unless reused for a return value. memset ~~~~~~ The :pb:macro:`use_memset` macro creates a function that can set an array to a fixed value. .. code-block:: picoblaze ; use_memset(memset, s0, s1, s2) ... load s0, 20 ; Destination at 0x20 in scratchpad load s1, 05 ; 5 bytes in the array load s2, "A" ; Value to initialize with call memset After the call every byte of the array will be initialized to the contents of the value register. memcopy ~~~~~~~ :pb:macro:`use_memcopy` creates a function to copy an array from one location to another in scratchpad. .. code-block:: picoblaze ; use_memcopy(memcopy, s0, s1, s2) ... load s0, 20 ; Source at 0x20 load s1, 10 ; Destination at 0x10 load s2, 05 ; Copy 5 bytes call memcopy After the call the bytes from 0x10 to 0x14 contain the data copied from 0x20 to 0x24. memwrite ~~~~~~~~ The :pb:macro:`use_memwrite` macro scans an array in scratchpad and writes the raw bytes to a fixed output port. .. code-block:: picoblaze constant ConsolePort, FE ; use_memwrite(memwrite, s0, s1, ConsolePort) load s0, 20 ; Source array load s1, 05 ; Writing 5 bytes call memwrite This performs an output to port 0xFE for each of the bytes from 0x20 to 0x24. hexwrite ~~~~~~~~ Similar to :pb:macro:`use_memwrite` is the :pb:macro:`use_hexwrite` macro. It writes an array of bytes converted to ASCII hex values. This macro destructively modifies the global ``_tempreg`` register. .. code-block:: picoblaze ; use_hexwrite(hexwrite, s0, s1, ConsolePort) ... load_store(0x5A, 0x20) load_store(0x11, 0x21) load_store(0x42, 0x22) load s0, 20 ; Source array load s1, 03 ; Writing 3 bytes call hexwrite This writes the string "5A1142" to the output port. Every byte expands into two hex digits. bcdwrite ~~~~~~~~ Another similar output routine is the :pb:macro:`use_bcdwrite` macro. It writes an array to an output port but treats the bytes as unpacked BCD digits. Each digit is converted to an ASCII digit before writing to the port. Any leading 0 digits are skipped. Invalid BCD digits are not detected. .. code-block:: picoblaze ; use_bcdwrite(bcdwrite, s0, s1 , ConsolePort) ... load_store(0x00, 0x20) load_store(0x01, 0x21) load_store(0x05, 0x22) load s0, 20 ; Source array load s1, 03 ; Writing 3 bytes call bcdwrite This converts the array to ASCII characters and sends "15" to the output port. This is useful for printing the output from ``int2bcd`` described below. .. _BCD conversion: BCD conversion -------------- A pair of generator macros create functions for converting between unsigned integers and unpacked BCD. They are designed to work with arbitrary sized integers consisting of one or more bytes. The :pb:macro:`use_int2bcd` macro takes a list of integer bytes on the stack and writes the BCD representation into a fixed size buffer. .. code-block:: picoblaze ; use_int2bcd(int2bcd, 5, s0, s1, s2,s3,s4,s5) ... load s0, 20 ; Use buffer from 0x20 to 0x24 load s1, 02 ; Convert 16-bit integer (2 bytes) load16(s4,s3, 30789) push(s3, s4) ; Place integer on stack low byte first, high byte last (on top) call int2bcd After conversion the array at scratchpad 0x20 contains the hex values ``[03 00 07 08 09]``. This result can then be processed by ``bcdwrite`` to write an integer value out to a port. The result is right justified in the array with leading 0's for any unused digits. No error detection is performed if the result requires more digits than the generator macro was defined to use. .. code-block:: picoblaze load16(s4,s3, 512) push(s3, s4) call int2bcd The result is ``[00 00 05 01 12]`` at 0x20. For converting numeric string inputs to binary, a pair of generator macros can be used. First is :pb:macro:`use_ascii2bcd` which will convert a numeric ASCII string into BCD format. .. code-block:: picoblaze ; use_ascii2bcd(ascii2bcd, s0, s1) load_store("X", 0x20) ; Simulate text input load_store("1", 0x21) load_store("2", 0x22) load_store("4", 0x23) load_store("9", 0x24) load s0, 20 ; Use array at 0x20 load s1, 05 ; Convert 5 characters from 0x20 to 0x24 call ascii2bcd The resulting array contains BCD: ``[00 01 02 04 09]``. Any non-digit characters in the string are converted to 0. The :pb:macro:`use_bcd2int` macro is used to convert from BCD to an integer. This finishes the conversion of numeric string input into a usable integer value after first converting ASCII to BCD using :pb:macro:`ascii2bcd `. .. code-block:: picoblaze ; use_bcd2int(bcd2int, s0, s1, s2,s3,s4,s5,s6) load s0, 20 ; Use array at 0x20 load s1, 05 ; Convert 5 digits from 0x20 to 0x24 call bcd2int The converted integer value is overwritten into the array from left to right, destroying some of the BCD digits. The first byte in the array is the least significant. The total number of converted binary integer bytes is returned in the length register (s1 in this case). After conversion the array contains ``[E1 04 02 04 09]``. 0x04E1 is 1249 from the original ASCII string. The integer result is guaranteed to always be smaller than the largest BCD number that will fit in an array (999...) so an overflow is impossible. 8-bit arithmetic ---------------- The :pb:macro:`not` and :pb:macro:`negate` macros are available to perform logical inversion and 2's complement negation on 8-bit registers. The :pb:macro:`abs` macro produces the absolute value of signed registers. You can perform signed comparison with the :pb:macro:`compares` macro. It takes the same arguments as the native :ref:`inst-compare` instruction. The ``C`` flag is set in accordance with their signed relationship. However, the ``Z`` flag is not set correctly. Use the :ref:`inst-compare` instruction to test for equality or inequality of signed values. If you need to convert an 8-bit signed value to 16-bit, use the :pb:macro:`signex(MSB, LSB) ` macro to extend the sign bit onto the upper register. The 8-bit register to be extended is passed in as the LSB argument. .. _16-bit arithmetic, logical, and shift operators: 16-bit arithmetic ----------------- The need will frequently arise to handle values larger than the capacity of an 8-bit register. The following macros provide quick access to 16-bit operations. You can define aliases for pairs of 8-bit registers with :pb:macro:`reg16` and then pass them into the 16-bit arithmetic macros: .. code-block:: picoblaze reg16(rx, s4, s3) ; Virtual 16-bit register rx is composed of (s4, s3) reg16(ry, s6, s5) load16(rx, 1000) load16(ry, 3000 + 500) ; You can use arbitrary expressions for constants add16(rx, ry) ; rx = rx + ry add16(rx, -100) ; rx = rx + (-100) This is much less obtuse than manually calculating 16-bit constants and repeatedly implementing the operations in pieces. The virtual name always expands into its original two registers and can be used on any macro that takes an MSB,LSB register pair as an argument. You can retrieve the upper and lower byte registers indirectly from a virtual name with the :pb:macro:`regupper` and :pb:macro:`reglower` macros. This makes it easy to reallocate the registers if needed. .. code-block:: picoblaze load s0, reglower(rx) ; s0 = s3 load s1, regupper(rx) ; s1 = s4 The :pb:macro:`mem16` macro defines 16-bit constants for scratchpad and port addresses. Like :pb:macro:`reg16` it creates a new m4 macro that lets you refer to the pair of port addresses together. In addition, two constants are created with the same name suffixed with "_H" and "_L" to identify the high and low ports respectively. .. code-block:: picoblaze mem16(M_DATA, 0x05, 0x04) load16(rx, 1000) store16(rx, M_DATA) The following 16-bit functions are available. All other than :pb:macro:`not16`, :pb:macro:`negate16`, and :pb:macro:`abs16` take a constant or a 16-bit register as their second argument. ==================== ==================== ==================== ==================== :pb:macro:`load16` :pb:macro:`reg16` :pb:macro:`mem16` :pb:macro:`add16` :pb:macro:`sub16` :pb:macro:`and16` :pb:macro:`or16` :pb:macro:`xor16` :pb:macro:`test16` :pb:macro:`not16` :pb:macro:`negate16` :pb:macro:`abs16` ==================== ==================== ==================== ==================== The :pb:macro:`test16` macro is implemented differently on PicoBlaze-3 due to the lack of the :ref:`inst-testcy` instruction. The ``Z`` flag is set when the AND of both bytes with the test word is zero but the ``C`` flag does not represent the XOR of all 16 bits. A full suite of 16-bit shifts and rotates are also available. They work the same as their 8-bit equivalents. ================== ================== ================== ================== :pb:macro:`sl0_16` :pb:macro:`sl1_16` :pb:macro:`sla_16` :pb:macro:`slx_16` :pb:macro:`sr0_16` :pb:macro:`sr1_16` :pb:macro:`sra_16` :pb:macro:`srx_16` :pb:macro:`rl16` :pb:macro:`rr16` ================== ================== ================== ================== .. code-block:: picoblaze sl0_16(rx, 4) ; Multiply by 2**4 .. _16-bit I/O operations: 16-bit IO --------- 16-bit versions of the port and scratchpad I/O operations are available. You can use the :pb:macro:`mem16` macro to define pairs of memory and port addresses for simplification. The variants using a pointer register increment by two so that successive calls can be made to work on contiguous ranges of addresses. =================== =================== =================== =================== :pb:macro:`fetch16` :pb:macro:`store16` :pb:macro:`input16` :pb:macro:`output16` =================== =================== =================== =================== .. code-block:: picoblaze mem16(M_ACCUM, 0x1b, 0x1a) reg16(rx, s4, s3) fetch16(rx, M_ACCUM) ; Fetch direct from address load s0, M_ACCUM_L ; Low byte constant defined by mem16() fetch16(rx, s0) ; Fetch from indirect pointer fetch16(rx, s0) ; Fetch next word Similarly for port I/O. .. code-block:: picoblaze mem16(P_ACCUM, 0x1b, 0x1a) input16(rx, P_ACCUM) ; Input direct from address load s0, P_ACCUM_L input16(rx, s0) ; Input from indirect pointer input16(rx, s0) ; Input next word .. _Multiply and divide routines: Multiply and divide ------------------- The general purpose PicoBlaze 8x8 multiply and divide routines are made available with arbitrary register allocations to suit your needs. A set of constant multiply and divide routines can also be generated for faster results than the general purpose functions. The following macros are available: ================================= ======================================= :pb:macro:`use_multiply8x8` 8x8-bit unsigned :pb:macro:`use_multiply8x8s` 8x8-bit signed :pb:macro:`use_multiply8x8su` 8-bit signed x 8-bit unsigned :pb:macro:`use_divide8x8` 8/8-bit unsigned :pb:macro:`use_divide8x8s` 8/8-bit signed :pb:macro:`use_divide16x8` 16/8-bit unsigned :pb:macro:`use_divide16x8s` 16/8-bit signed :pb:macro:`use_multiply8xk` 8-bit x constant :pb:macro:`use_multiply8xk_small` 8-bit x constant (result less than 256) :pb:macro:`use_divide8xk` 8-bit / constant ================================= ======================================= .. code-block:: picoblaze init: ... jump main ; Skip over our functions ; Configure multiply and divide functions (sE is a temp register) reg16(rx, s5, s4) use_multiply8x8(mul8, s0, s1, rx) ; rx = s0 * s1 use_divide8x8(div8, s0, s1, s6, s7) ; s6 = s0 / s1 rem. s7 use_multiply8xk(mul8k7, s0, 7, rx) ; rx = s0 * 7 (Multiplier can be greater than 255) use_multiply8xk_small(mul8k7s, s0, 7, s1) ; s1 = s0 * 7 (Result must fit in one byte) use_divide8xk(div8k, s0, 7, s1) ; s1 = s0 / 7 (No remainder) main: load s0, 20'd load s1, 3'd call mul8 ; rx = 20 * 3 call div8 ; s6 = 20 / 3 call mul8k7 ; rx = 20 * 7 call mul8k7s ; s1 = 20 * 7 call div8k ; s1 = 20 / 7 .. _expression-parser: Expressions ----------- A family of :pb:macro:`expression evaluator ` macros are provided that can implement arithmetic and other operations using pseudo-infix notation. The basic principle is borrowed from the PL360 high level assembler. You can write an assignment expression of the form ``expr( := op [op ]*)``. Spaces are required between all symbols. ``val`` is one of: +----------------------------------------------------------------------------+ |register | +----------------------------------------------------------------------------+ |literal expression (with no internal spaces) | +----------------------------------------------------------------------------+ |"`sp[]`" reverse assignment to scratchpad address | +----------------------------------------------------------------------------+ |"`spi[]`" reverse assignment to indirect scratchpad address in register| +----------------------------------------------------------------------------+ ``op`` is one of: ============= =========================================== +, -, `*`, / arithmetic: add, subtract, multiply, divide &, `|`, ^ bitwise operations: and, or, xor <<, >> shifts: left and right =: reverse assignment ============= =========================================== Operations are evaluated from left to right with *no precedence*. The target register is used as the left operand of all operations. It is updated with the result after each operation. .. code-block:: picoblaze expr(s0 := s1 + s2 =: s3 >> 2) Arithmetic is performed on ``s0`` at each stage. The reverse assignment to ``s3`` captures the intermediate result of ``s1 + s2`` and then continues with the right shift applied to ``s0``. This expands to: .. code-block:: picoblaze ; Expression: s0 := s1 + s2 =: s3 >> 2 LOAD s0, s1 ADD s0, s2 LOAD s3, s0 SR0 s0 SR0 s0 If you want to use the existing value of a register use it as the first operand after the assignment: .. code-block:: picoblaze load s0, 03 expr(s0 := s0 + 100) Here are all of the expression macros available: =================== ==================== =================================== ================================ Macro Target x Operand Supported operators Notes =================== ==================== =================================== ================================ :pb:macro:`expr` 8x8 +, -, `*`, /, &, `|`, ^, <<, >>, =: :pb:macro:`exprs` 8x8 +, -, `*`, /, &, `|`, ^, <<, >>, =: signed `*`, /, and >> :pb:macro:`expr2` 16x8 `*` +, -, `*`, /, <<, >>, =: :pb:macro:`expr2s` 16x8 `*` +, -, `*`, /, <<, >>, =: signed for all except << :pb:macro:`expr16` 16x16 +, -, &, `|`, ^, <<, >>, =: :pb:macro:`expr16s` 16x16 +, -, &, `|`, ^, <<, >>, =: signed >> =================== ==================== =================================== ================================ `*` *The expr2 macros support 16-bit literals as operands of + and -. The first register after the assignment can be 16-bits.* 16-bit registers must be comma separated register pairs in ``MSB,LSB`` order or named 16-bit registers created with :pb:macro:`reg16`. For multiplication and division support you must initialize the internal functions with one of the following: ====== ======================================================== ======================================================== Macro Multiply Divide ====== ======================================================== ======================================================== expr :pb:macro:`use_expr_mul` :pb:macro:`use_expr_div` exprs :pb:macro:`use_expr_muls` :pb:macro:`use_expr_divs` expr2 :pb:macro:`use_expr_mul` :pb:macro:`use_expr_div16` expr2s :pb:macro:`use_expr_muls` and :pb:macro:`use_expr_mulsu` :pb:macro:`use_expr_div16s` ====== ======================================================== ======================================================== As an expedient you can invoke :pb:macro:`use_expr_all` to include all of them and then eliminate any unused mul or div routines with the ``--remove-dead-code`` option to Opbasm. These macros need to be called before any call to ``expr*()`` that uses multiplication or division. It is best to place them at the start of the program and jump over them to reach the startup code. The stack must be configured (:pb:macro:`use_stack`) before calling these macros because additional modified registers must be saved and restored. By default these macros configure the mul and div functions to use the ``s8,s9`` or ``s7,s8, and s9`` registers for input and output. You can modify the register allocation by passing arguments to the ``use_*`` macros. The registers ``sA``, ``sB``, and sometimes ``sC`` are temporarily altered and restored. The common temp register (default ``sE``) is destructively modified. You can change the tempreg with the :pb:macro:`use_tempreg` macro. The MSB of multiplication is ignored by subsequent operations. Division by 0 is not detected. An example of signed expressions applied to converting temperatures: .. code-block:: picoblaze use_stack(sF, 0x3F) jump start use_expr_all ; Invoke all of the mul and div routines ; Setup register aliases reg16(rx, s0,s1) reg16(ry, s2,s3) vars(s4 is celsius, s5 is fahrenheit) ; Convert temperature c_to_f: load reglower(rx), celsius ; Load 8-bit Celsius temperature signex(rx) ; Sign extend to 16-bits expr2s(rx := rx * 9 / 5 + 32) ; Perform 16x8-bit signed arithmetic to get Fahrenheit return c_to_f_fast: ; Saves approx. 130 instructions compared to c_to_f with multiply load reglower(ry), celsius ; Load 8-bit Celsius temperature signex(ry) ; Sign extend to 16-bits expr16s(rx := ry << 3 + ry) ; Multiply by 9 with shift and add expr2s(rx := rx / 5 + 32) ; Perform 16x8-bit signed arithmetic to get Fahrenheit return f_to_c: load reglower(rx), fahrenheit ; Load 8-bit Fahrenheit temperature signex(rx) ; Sign extend to 16-bits expr2s(rx := rx - 32 * 5 / 9 ) ; Perform 16x8-bit signed arithmetic to get Celsius return start: ... Random numbers -------------- A pair of simple pseudo-random number generators are included in the macro package. They are implemented using the xorshift algorithm with coefficients selected for minimal code on PicoBlaze. They generate a full cycle of every value in their range except 0. :pb:macro:`use_random8` generates 8-bit numbers and :pb:macro:`use_random16` generates 16-bit. You must set a non-zero seed value to initialize the PRNGs. .. code-block:: picoblaze namereg sA, SEED use_random8(random, SEED) ... load SEED, 5A ; You should use an entropy source to set the initial seed call random ... call random The new random value is in the ``SEED`` register after each call to ``random``. The 16-bit PRNG is similar but you must provide two additional registers for temporary values. Their contents are not preserved across calls. .. code-block:: picoblaze namereg sA, SEEDH namereg sB, SEEDL reg16(SEED, SEEDH,SEEDL) use_random16(random, SEED, sC,sD) ... load16(SEED, 0x1234) ; You should use an entropy source to set the initial seed call random ... call random If you don't want to dedicate a register to storing the seed you can create a wrapper that fetches from scratchpad: .. code-block:: picoblaze constant M_SEED, 00 ; Address to store seed variable use_random8(random_core, s0) proc random(s0) { fetch s0, M_SEED call random_core store s0, M_SEED } load_store(M_SEED, 0x5A) ; You should use an entropy source to set the initial seed ... call random Miscellaneous ------------- A few miscellaneous utility macros are included: ====================== ============================ ============================== Macro Description Example ====================== ============================ ============================== :pb:macro:`nop` No-operation :pb:macro:`clearcy` Clear the carry flag :pb:macro:`setcy` Set the carry flag ``setcy or setcy()`` :pb:macro:`isnum` Test if a string is a number :pb:macro:`load_out` Load and output value ``load_out(0x01, P_uart)`` :pb:macro:`load_store` Load and store value ``load_store(0x01, M_var)`` :pb:macro:`reverse` Reverse arguments ``reverse(1,2,3)`` :pb:macro:`swap` Swap registers ``swap(s0, s1)`` :pb:macro:`randlabel` Random label name ``randlabel(PREFIX_)`` :pb:macro:`uniqlabel` Unique label name ``uniqlabel(PREFIX_)`` ====================== ============================ ============================== Manually running m4 ------------------- Some users may be unable to use Opbasm due to formal release procedures requiring a "golden" assembler. The m4 macro package can still be used with other PicoBlaze assemblers by manually running code through m4: .. code-block:: console > m4 picoblaze.m4 [input source] > expanded_macros.gen.psm The picoblaze.m4 file is located in the opbasm_lib directory of the source distribution.