Shop OBEX P1 Docs P2 Docs Learn Events
Cogjects - load cog code from SD and save hub ram - Page 2 — Parallax Forums

Cogjects - load cog code from SD and save hub ram

2

Comments

  • stevenmess2004stevenmess2004 Posts: 1,102
    edited 2011-03-30 05:07
    Did I hear someone mention Oz UPE? When and where?

    Sorry for hijacking the thread...
  • Cluso99Cluso99 Posts: 18,069
    edited 2011-03-30 06:08
    Easter in SA somewhere near Noarlunga (~30km S of Adelaide)
  • Dave HeinDave Hein Posts: 6,347
    edited 2011-03-30 09:30
    Dr_Acula,

    I'm confused about the point of this thread. Isn't the goal just to re-use the memory space taken by cog images? I don't understand why you need to dynamically load the drivers from an SD card. Is it because the total code space required is greater than 32k? In that case, it would make sense to have a temporary 2k buffer to load one driver at a time and start it.

    Why is this becoming so complicated?

    Dave
  • Oldbitcollector (Jeff)Oldbitcollector (Jeff) Posts: 8,091
    edited 2011-03-30 10:14
    I've been quietly lurking this thread... I love the concept, but I'm having trouble seeing the practical application.

    Thinking out loud:
    Wondering if this could be adapted to that NES emulator that was written last year to load small roms in and out.

    OBC
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2011-03-31 02:11
    Yes, Oz UPE happening soon!
    I'm confused about the point of this thread. Isn't the goal just to re-use the memory space taken by cog images? I don't understand why you need to dynamically load the drivers from an SD card. Is it because the total code space required is greater than 32k? In that case, it would make sense to have a temporary 2k buffer to load one driver at a time and start it.

    Good questions.

    Well, maybe I'll start with a practical problem which is that by the time you have an sd driver, keybaord, some sort of display, serial driver(s), mouse and some real world I/O, there isn't much space left for your actual program

    Loading cog code through a common space saves nearly half the hub ram, so that ought to be useful.
    In that case, it would make sense to have a temporary 2k buffer to load one driver at a time and start it.

    That is what it is doing at the moment, and so far, there isn't much of an obvious advantage.

    But I think this is getting close to being able to do more. The first thing (which I am working on) is to be able to do something that you can't do in standard spin, which is to swap between multiple video modes on the fly withing a program.

    The second is to combine this with DOL, and actually have objects behave as objects, ie blocks of code that you can load and unload on the fly. I like to think of objects as a tool, like a spanner. You grab it, you use it, you put it down and get another tool. Right now with current spin, you are only allowed 7 tools. I'd like my spin programs to have access to the entire Obex toolbox, all pre-loaded on an SD card.

    Back to coding. I think demo software might explain this better than words?
  • Heater.Heater. Posts: 21,230
    edited 2011-03-31 02:24
    Dr_A,
    That is what it is doing at the moment, and so far, there isn't much of an obvious advantage.

    How come?

    Is it so that by the time you have a file system and loader and all to get these PASM objects running you have used as much space for that code as if you just included the objects in your code and started them normally?

    I'm kind of had the feeling that the direction was to:

    1) Boot the prop into a loader that fetches all required objects for an app from SD (or wherever) and starts them up one at a time through the 2K buffer.
    2) All such loaded and running objects are running and available to accept commands through mailboxes and buffers.
    3) The loader now pulls in the actual app you want, which can be much bigger than normal as it does not include the PASM objects.
    4) The app now runs making uses of the objects through the mailboxes and buffers.
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2011-03-31 03:37
    Is it so that by the time you have a file system and loader and all to get these PASM objects running you have used as much space for that code as if you just included the objects in your code and started them normally?

    Yes... and No.

    It depends on whether you consider an SD driver a core part of your code. A lot of code doesn't and so no, there is not a huge advantage for those programs.

    But I find SD code so incredibly useful. Use it for datalogging, for loading in graphics images, for loading other programs (KyeDOS).

    So given the assumption that you need some sort of SD driver code, then the aim is to save as much hub ram as possible assuming that SD code is always there.

    Of course, you could strip the SD code right down to the bare essentials (no writes, read only etc), but I keep finding a need for the code I commented out earlier!

    The description you gave is more DOL than Cogjects. Cogjects are a step along the way. Do the PASM part first. Then look at reloadable spin objects next.

    Here is some code that I don't think you can do very easily with standard spin. Display three different VGA text modes one after the other by loading in new VGA drivers.
    PUB Main | i,cols,rows
        
      fat.FATEngineStart(_SD_DO, _SD_CLK, _SD_DI, _SD_CS, _SD_WP, _SD_CD, _RTC_DAT, _RTC_CLK, -1)
      cogarray := fat.addressofcog                          ' get location of the fat cog array
      fat.mountPartition(0)                                 ' mount the sd card
      'ifnot(fat.partitionMounted)                          ' commented out - if the sd card isn't working, can't load a cog to be able to display this
      '  printstringcr(string("Card Not Mounted "))
      ReadCogFile(string("Serial.Cog"))                     ' cog 2
      serial.start(cogarray,31,30,0,38400)                  ' start the cog at this baud rate
    
      ReadCogFile(string("Keyboard.Cog"))                   ' cog 3
      kb.start(cogarray,26,27)                              'start the keyboard
    
      ' vga 128x64 green on black
      cols := 128
      rows := 64
      ReadCogFile(string("VGA12864.Cog"))
      vga128.start(cogarray, 16, @screen, @colors, @cx0, @sync)      ' start vga driver  
      repeat i from 0 to rows - 1 
        colors[i] := %%0000_0300                              ' green on black
    
        'fill screen with spaces
      repeat i from 0 to (cols*rows) - 1
        screen.byte[i] := 32
    
      delay.pause1ms(2000)
      bytemove(@screen.byte[0],string("Hello World"),11)
      delay.pause1ms(2000)
      cogstop(4)                    ' stop the vga driver cogs
      cogstop(5)
      
      ' vga 100x50 white on blue
      cols := 100
      rows := 50
      ReadCogFile(string("VGA10050.Cog"))
      vga100.start(cogarray, 16, @screen, @colors, @cx0, @sync)      ' start vga driver
      repeat i from 0 to rows - 1
        colors[i] := %%0020_3330                              ' white on blue
    
      delay.pause1ms(4000)
      cogstop(4)                    ' stop the vga driver cogs
      cogstop(5)
      
      ' vga 80x40 gold on blue
      cols := 80
      rows := 40
      ReadCogFile(string("VGA8040.Cog"))                     ' cog 4 and 5
      vga80.start(cogarray, 16, @screen, @colors, @cx0, @sync)      ' start vga driver
      repeat i from 0 to rows - 1
        colors[i] := %%0020_3300                              ' gold on blue
    
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-03-31 04:27
    This thread very much reminds me on my own work - which I also started to describe in my BLOG and which has been mentioned here and there in different threads. For example PASMstore simply writes *.COG files to SD card...

    Saving the COG-code to SD card is where I started as well. But I switched to EEPROM. The upper half of a 64k EEPROM can store 14 drivers plus some managing information like name of the driver, it's version and so on. I think for critical drivers it's better to store them in EEPROM.

    So, the application only needs an I2C driver to load all other drivers, which has a smaller footprint than an SD driver. AND an EEPROM is static whereas the SD card can be removed. Try to display an error-message in case the display-driver is also placed on the SD card which is missing or simply no longer works due to broken sectors ;o)

    PASM code usually uses HUB RAM to communicate with other COGs or SPIN. For that I have a table of drivers at the end of HUB RAM which holds the driver name (as hash value) and the address of the communication buffer, which is also requested and taken from the end of HUB RAM. This allows to exchange the SPIN-code during runtime, keeping the drivers running (have some code that already is doing that) AND would also allow to exchange drivers. I was planning to have suspend and resume commands for dumping a running COG to SD or external RAM or whatever ...

    So, everything is possible ... run PASM cogs on demand and run SPIN snippets on demand which then uses the pre-loaded drivers.

    Unfortunately I my effords are unregularly. Well ... let's see what I can offer this evening ...
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2011-03-31 04:44
    MagIO2, your code sounds very interesting.

    I think there are now several of us working on the same thing. We should try to work together to translate some more Objects!

    I do like your idea of loading from eeprom. I lent Cluso some propeller chips and eeproms a few weeks ago, and he is kindly giving me 64k eeproms to replace the 32k ones I sent him. So I can experiment with your I2C driver. With eeprom storage, maybe more people (eg ones without SD cards) can join in the fun!
    Try to display an error-message in case the display-driver is also placed on the SD card which is missing or simply no longer works due to broken sectors ;o)

    True!!

    There are two more objects I really would like to get working. One is a mouse driver. The other is Kye's 160x120 full color driver. I need to ask Kye some questions about the merits of using the PAR method vs poking variables to fixed locations vs using the screen buffer to pass values. All three ways have advantages and disadvantages.

    Addit: Mouse working see attached.
    Now there are 6 cogjects - serial, keyboard, mouse, vga80x40, vga100x50 and vga128x64

    The mouse was easy as it used the PAR method.
  • Cluso99Cluso99 Posts: 18,069
    edited 2011-03-31 05:14
    Heater: Your thoughts were where I thought we were heading. We have a number of apps on the SD card. The apps have device drivers which are somewhat removed from the hardware, so they access via rendezvous locations (like Sphinx). That way, on the fly, we can change a serial port to a USB port, or switch from VGA to TV, or whatever. We have an app that is precompiled, but it does not know, or care about its input (keyboard or pc) nor its output (TV, VGA, PC) etc. So I can send you my compiled app and you can run it withougt having to recompile.

    This is just a real o/s for the prop, but takes account of the flexible nature of the prop.

    All these things being done by different people will eventually come together. After all, the original micros like the Apple ][ took quite a while to evolve.
  • Dave HeinDave Hein Posts: 6,347
    edited 2011-03-31 07:58
    Dr_Acula,

    I think I understand the point of this thread now. I was confused by your original post, and thought it was just to re-use the hub space used by PASM drivers. So what you really want to do is write an OS. I agree with Cluso that the best approach is to use a rendezvous table. With a rendezvous table any app could use a resource, independent of the language it was written in. In theory, a Catalina C program could talk to a driver written in Spin while a PropBasic program is running in another cog.

    In spinix, I used a modified version of CLIB called clib_os.spin. Serial I/O was done with an object like FullDuplexSerial, but split into two parts - API routines and PASM code. File I/O used a modified FSRW object that was split in a similar way, except that the FSRW API routines are accessed through a resident kernel app. The kernel app also provides other system services, such as loading and killing other applications.

    The serial and file I/O drivers and the kernel are loaded by the boot program. The boot program tells the kernel to load a shell application, which provides a user command-line interface. The kernel kills the boot program and adds it memory to the heap before starting the shell. The user can load additional drivers and start other applications by typing commands to the shell. Most of the shell commands are actually implemented as apps, such as cat, ls, cp, mv, cd, etc.

    The rendezvous file is shown below. It defines locations in high hub memory that are used by all of the programs to perform serial and file I/O and other kernel commands. The rendezvous file also defines locations for the heap manager for managing free memory. I think a heap manager is important if you want to build a flexible system.

    Dave
    CON
      start              = $7efc
      
      ' Kernel process table
      cogtable           = $7efc ' 8x24 = 192 bytes
      
      ' I2C Driver
      i2c_cmd            = $7fbc
      i2c_parm           = $7fc0
      
      ' Kernel data
      filelock           = $7fc4
      filecmd            = $7fc8
      fileparm           = $7fcc
    
      ' SD CLIB data
      'File I/O
      spi_engine_cog     = $7fd0
      spi_command        = $7fd4
      spi_block_index    = $7fd8
      spi_buffer_address = $7fdc
    
      ' Basic CLIB data
      ' Serial data
      serial             = $7fe0
      stdio              = $7fe4
      stdin              = $7fe8
      stdout             = $7fec
      
      ' Malloc data
      memlocknum         = $7ff0
      memfreelist        = $7ff4
      malloclist         = $7ff8
      laststackaddr      = $7ffc
    
      checkword          = $dead1eaf
    
    PUB main
    
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-03-31 12:06
    That's where I use a 2 step approach.

    Part 1 is having a limited list at the end of HUB-RAM. For example having one row per COG. The row then contains the name of the driver loaded (as hash value), the size of the buffer needed for the driver (this can of course also include video-buffer for a VGA driver ...) and the address of that buffer.

    Part 2 are the communication buffers. The RAM below the table of drivers is used for those buffers. The start-address of the buffers are requested from some functions which I called COGdesc (COG descriptor). The full featured COGdesc is for an OS, as it contains functions to manage EEPROM. A stripped down version to be used in applications I called cogOScore and cogOScoreRAM. The first is for loading a driver by name and version number, the other is for reconnecting to already loaded drivers.

    OK, here is what I have. It will maybe be difficult to make it run and maybe it's better to use the code for analysis instead and for discussions here .. can't tell you because it evolved on my gadgetgangster board and I never had to reinstall it. Anyway, here is what I think is needed to be done to install it somewhere:
    Preconditions: 1. SD card and 64kB EEPROM
    1. Use a fresh formatted SD card
    2. Copy OSSWAP.SYS to the card
    3. Use PASMstore to create a SD driver with your pin-settings (I use PINs 0-3,change start_explicit accordingly) and a Full Duplex Serial - guess the saveDriver and saveTable have to be uncommented.
    Then PASMstore should write em to EEPROM.
    4. Run newCogOS
    ... think that's it ...
  • Dave HeinDave Hein Posts: 6,347
    edited 2011-03-31 12:56
    MagIO2,

    I see some similarities in your newCogOS and spinix. However, you have a much more sophisticated way for an app to allocate a driver. The code for spinix is attached below. The files and subdirectories in the root directory should be copied to an SD card, and src\bootsd.binary should be programmed into the EEPROM. Spinix uses the serial port at 57,600 baud, and it will prompt for the four SD pins the first time it is run.

    Spinix also runs under SpinSim using the bootsim.binary file in the root directory. It's executed by typing "spinsim bootsim.binary". Under SpinSim, the RAM size can be increased by running addrom.binary under spinix. This adds 28k of the 32k ROM to the heap, and treats it like RAM instead of ROM.

    Dave
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2011-04-01 06:48
    Just a quick documentation of some of the processes involved in translating object code. This is Kye's code and it implants some variables into cog ram. I asked Kye in a PM whether it would be ok to pass the variables using PAR and to use the screen buffer as the array to pass and he said this would make sense. So, this is the original code:
    PUB PIXEngineStart(pinGroup) '' 7 Stack Longs
    
    '' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    '' // Starts up the PIX driver running on a cog.
    '' //
    '' // Returns true on success and false on failure.
    '' //
    '' // PinGroup - Pin group to use to drive the video circuit. Between 0 and 3.
    '' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
      PIXEngineStop
      if(chipver == 1)
    
        pinGroup := ((pinGroup <# 3) #> 0)
        directionState := ($FF << (8 * pinGroup))
        videoState := ($30_00_00_FF | (pinGroup << 9))
    
        pinGroup := constant((25_175_000 + 1_600) / 4)
        frequencyState := 1
    
        repeat 32
          pinGroup <<= 1
          frequencyState <-= 1
          if(pinGroup => clkfreq)
            pinGroup -= clkfreq
            frequencyState += 1
    
        displayIndicatorAddress := @displayIndicator
        syncIndicatorAddress := @syncIndicator
        cogNumber := cognew(@initialization, @displayBuffer)
        result or= ++cogNumber
    

    and the first step is to search through all those variables and see if any of them come up in the cog code. Using ^F find, there are 5 variables that are passed, so these can go in displaybuffer[0] to displaybuffer[4]

    Here is the new code:
    PUB PIXEngineStart(pinGroup) '' 7 Stack Longs
    
    '' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    '' // Starts up the PIX driver running on a cog.
    '' //
    '' // Returns true on success and false on failure.
    '' //
    '' // PinGroup - Pin group to use to drive the video circuit. Between 0 and 3.
    '' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
      PIXEngineStop
      if(chipver == 1)
    
        pinGroup := ((pinGroup <# 3) #> 0)                  ' pingroup only used in this pub
        displaybuffer[2] := ($FF << (8 * pinGroup))          
        displaybuffer[3] := ($30_00_00_FF | (pinGroup << 9))     
    
        pinGroup := constant((25_175_000 + 1_600) / 4)      ' pingroup recycled as a new var
        displaybuffer[4] := 1                                 
    
        repeat 32
          pinGroup <<= 1
          displaybuffer[4] <-= 1
          if(pinGroup => clkfreq)
            pinGroup -= clkfreq
            displaybuffer[4] += 1                           
    
        displaybuffer[0] := @displayIndicator
        displaybuffer[1] := @syncIndicator
        
        cogNumber := cognew(@initialization, @displayBuffer) ' and displayindicatoraddress/syncindicatoraddress are cog variables
        result or= ++cogNumber
    

    and in the cog code we need to add some lines to extract these out again:
    initialization          mov     i,               par                            ' par is used elsewhere
                            rdlong  displayIndicatorAddress,i                       'read in 5 longs
                            add     i,#4
                            rdlong  syncIndicatorAddress,i
                            add     i,#4
                            rdlong  directionstate,i
                            add     i,#4
                            rdlong  videostate,i
                            add     i,#4
                            rdlong  frequencystate,i
    

    A general observation is that this doesn't seem to add much to the spin code, but it does add to cog code compared with direct implanting. This might become relevant for cog code that is almost full. Fortunately Kye's code has lots of space free.

    Next step is to separate the cog and spin code into two different programs.

    A question. in Kye's code is this:
    DAT
      displayBuffer           long    0[(160 * 120) / 4]                         ' Display buffer.
    

    which is the display buffer.

    Could this also have been declared in a VAR section instead of a DAT section?
    And if so, is the reason you declare in a DAT section so you can also set all the entries to a specific value? (which VAR can't do)?
  • Dave HeinDave Hein Posts: 6,347
    edited 2011-04-01 07:37
    Dr_Acula wrote: »
    A question. in Kye's code is this:
    DAT
      displayBuffer           long    0[(160 * 120) / 4]                         ' Display buffer.
    

    which is the display buffer.

    Could this also have been declared in a VAR section instead of a DAT section?
    And if so, is the reason you declare in a DAT section so you can also set all the entries to a specific value? (which VAR can't do)?
    In a normal program it shouldn't matter whether the buffer is declared in a DAT or VAR section as long the the object and it parent objects are only referenced once. It will contain zeros in either case. Of course, if the object or its parent objects are referenced in more than one object you will have multiple copies of displayBuffer if declared in a VAR section.

    Things are different if load this as a cogject. Presumably, you would only referece the object once, so moving displayBuffer would be preferable since it would make the binary image smaller. Of course, you would have to account for the VAR space when you load the cogject, and your loader needs to zero out the VAR space. How do you know what memory to keep in hub RAM after the cogject has completed starting the cog? Do you keep the whole image in RAM? Some cogjects would require this, and other wouldn't. How do you know the difference?

    Dave
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2011-04-01 16:59
    Thanks Dave. That works!

    I'm slowly getting there. My next challenge is Kye's full color driver. This uses 19200 bytes of hub for a video buffer, so if you also load up 7 cogs at 2k each = 14k, there is no space at all for any program code. So I believe only cogjects can do this.

    Cogjects leave no trace once they have been loaded. What they do need though is the supporting spin, which might save some common hub space. So in a "main" program, declare 19200 bytes of screen buffer in a VAR, then pass this location to the cogject.

    I've got a bit stuck doing this though. I'm trying to move the screen buffer from an object to the main. eg in the main we have
    VAR
      long  screen[160*120/4]       
    

    and we get the location of the screen buffer with @screen, and pass it through to a method
      pix.plotpixel(@screen, 100,30,30)     ' pix is the object
    

    The object's original code used a local array (screen not passed because it was local in the array 'displaybuffer')
    PUB plotPixel(color, xPixel, yPixel) '' 6 Stack Longs
    
    '' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    '' // Plots a one color pixel on screen.
    '' //
    '' // Color - The color of the pixel to display on screen. A color byte (%RR_GG_BB_xx).
    '' // XPixel - The X cartesian pixel coordinate. X between 0 and 159. Y between 0 and 119.
    '' // YPixel - The Y cartesian pixel coordinate. Note that this axis is inverted like on all other graphics drivers.
    '' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
      displaybuffer.byte[((xPixel <# 159) #> 0) + (160 * ((yPixel <# 119) #> 0))] := (color | $3)
    

    and displaybuffer is the local array in the object "pix". I want to change it to something like this:
    PUB plotPixel(screen, color, xPixel, yPixel) '' 6 Stack Longs
    
    '' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    '' // Plots a one color pixel on screen.
    '' //
    '' // Color - The color of the pixel to display on screen. A color byte (%RR_GG_BB_xx).
    '' // XPixel - The X cartesian pixel coordinate. X between 0 and 159. Y between 0 and 119.
    '' // YPixel - The Y cartesian pixel coordinate. Note that this axis is inverted like on all other graphics drivers.
    '' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
      screen.byte[((xPixel <# 159) #> 0) + (160 * ((yPixel <# 119) #> 0))] := (color | $3)
    

    but the last line does not seem to work. Can we turn variables like "screen" into arrays eg screen.byte, or is that not allowed?
  • kuronekokuroneko Posts: 3,623
    edited 2011-04-01 18:25
    PUB plotPixel(screen, color, xPixel, yPixel) '' 6 Stack Longs
    
    '' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    '' // Plots a one color pixel on screen.
    '' //
    '' // Color - The color of the pixel to display on screen. A color byte (%RR_GG_BB_xx).
    '' // XPixel - The X cartesian pixel coordinate. X between 0 and 159. Y between 0 and 119.
    '' // YPixel - The Y cartesian pixel coordinate. Note that this axis is inverted like on all other graphics drivers.
    '' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
      [COLOR="red"]byte[screen][/COLOR][((xPixel <# 159) #> 0) + (160 * ((yPixel <# 119) #> 0))] := (color | $3)
    
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2011-04-01 23:21
    Thanks++ - that works!

    Attached is modified code for Kye's video driver about halfway through the process of being turned into a cogject.

    The video buffer has been moved into the 'main' program (this will be shared with the text video buffer and is 19200 bytes)
    All variables are passed to the cog via the video buffer (5 longs)
    A number of syntax changes - thanks to Kuroneko's very helpful suggestion.

    This is a working standalone program - it runs the same as Kye's program but behind the scenes is set up differently. I'm attaching this as an example of the changes needed to turn normal objects into cogjects.

    VGA graphics driver now added as a cogject.

    Addit: new program - loads sd card driver, keyboard, serial, vga graphics, draws some dots, loads small VGA text, then large vga text. Total cogs loaded = 9. This is beyond what is possible with standard spin as there isn't enough hub memory.

    Addit April 3 - added the external ram (Dracblade) cog driver. Useful for storing pictures, fast load of sprites etc. Now there are 8 cogjects. Maybe we need drivers for other external memory solutions?
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2011-04-09 19:09
    Thanks to some very helpful advice from Kuronko on another thread, we now have a 320x240 4 color driver working. There are now more cogjects (9) than cogs (8) and the demonstration program cycles through each of the cogjects.

    New zip package on the first posting of this thread.
  • Cluso99Cluso99 Posts: 18,069
    edited 2011-04-09 23:11
    Nice work! Especially since the screen buffer will require 19.2KB of hub memory.
  • max72max72 Posts: 1,155
    edited 2011-04-11 00:53
    I saw in the code there is an explanation on the way to create a cogject, but I must confess I find it a little bit difficult to follow. Could you write half a page of instructions/how to?
    Moreover at this point it could save a lot of space to write pasm code in a coprocessor style, and simply load it..
    Thanks for your effots.
    Massimo
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2011-04-11 01:10
    Yes, it can be quite difficult writing cogjects, because every obex object has been written in a different style.

    The hardest part is searching for variables that the original author has shared between hub and pasm. That involves doing an F3 search on every variable in a DAT section to see where those names come up.

    The next hardest part is passing variables vs passing pointers to variables.

    Also it helps to search for 'temporary' vs 'permanent' variables - the aim here is to save hub ram so there is no point reserving space for temporary variables. So any variable list that is passed may have some temporary and some permanent variables.

    I'll try to think of some examples of code. Some of the video cogjects took a while to debug - one of them took a whole day to code! The good news is that only needs to be done once per cogject.

    Have you got an obex object you would like to translate?
  • max72max72 Posts: 1,155
    edited 2011-04-11 01:55
    Nice candidates are graphics, the 4 port serial, maybe the servo32.

    Moreover I would lilke to group some custom functions in a single coprocessor object (say float style). This way some key functions would be blazing fast, on the other hand freeing hub memory.
    Do you think it would be possible to create a "skeleton" coprocessor object, with the possibility to custom fill it?
    I attached an example from Beau Schwabe, where I added some functions, just to have an idea.

    Massimo
  • Toby SeckshundToby Seckshund Posts: 2,027
    edited 2011-04-11 02:28
    Daft question (1501), I assume that there will always be some code/data that must sit down at the bottom of HUB, ans there will be some up at the top i.e. stacks etc but what i was wondering was could I use the middle remains as the ram for a dirrectly bolted on Z80. to be at one end exactly would cut down on any offset maths.
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2011-04-11 02:28
    I'll take a look at these this evening. Graphics and the servo ought to be possible. The 4 port serial has eluded me though - I've had a look at it a few times and it is going to be quite a complicated one.

    That trig function ought to be easy - it passes variables with par already. Also has a loop in pasm to read the variables - at the moment I have some 'unrolled' code in some of the video drivers - doing things in a loop saves cog space.

    A skeleton coprocessor object sounds interesting.
  • max72max72 Posts: 1,155
    edited 2011-04-11 02:35
    Thanks for your time.

    Massimo
  • MacTuxLinMacTuxLin Posts: 821
    edited 2011-04-11 10:32
    This is actually very cool. I have a few questions:

    1. For the FullDuplexSerial, after you minus the spin code, you'll still need to add an empty PUB to compile, right? E.g. PUB Main?

    2. When you read the cog_files to cogarray, the prop automatically runs a cog for it? So:
    fat.readdata(cogarray,fat.filesize-24)
    
    is same as cognew?

    3. When you discard the first 24 byte for each cog_file, why's that? Removing the empty PUB Main statement?

    4. You're using the same address as SD3.01's initialization loading other cog_files? Does it mean once you've finished reading the cog_file, prop moves this into another cog & thus frees up this 494 long in hub ram?

    Sorry for so many questions. :tongue:
  • Dr_AculaDr_Acula Posts: 5,484
    edited 2011-04-11 16:59
    Hi Massimo:

    I have translated your trig.spin file into Spin_Trig.Spin, Cog_Trig.Spin and Trig.Cog. All tested and working - see post #1 of this thread for the latest version (named April 13, my version numbers are getting ahead of the actual date!).

    I used the serial port for debugging and it sends out the Sin at 38400 baud. You can change that baud speed if you want. This is my little bit of test code:
      ReadCogFile(string("Trig.Cog"))             ' loads into cog 6
      trig.start(cogarray)                        ' test the trig object, starts in cog6 as this is the first one free
      serial.dec(trig.sine32(4000))
      cogstop(6)                                  ' stop cog 6
    

    I thought I would document absolutely every singe step, as this was a simple one to do and there were still 20 steps. The routine is fairly much the same for most cogjects.


    1) add a test routine to the main routine to send the Sin of a value to the serial port.
    2) add a new variable command_address to bottom of cog and search for all instances of 'par' in the cog
    3) added mov command_address,par to start of cog code as other cogs might use 'par' too
    4) changed start of cog code to 'entry' and edited start routine
    5) changed start of cog routine to rdlong t1,command_address ' wait for command
    6) search for further instances of 'par'. change wrlong zero,par to wrlong zero, command_address
    7) Copy Trig into Spin_Trig and Cog_Trig
    8) Change the main OBJ routine so it points to Spin_Trig instead of Trig
    9) Open both Spin_Trig and Cog_Trig in the Propeller tool
    10) Open an existing cogject cog file and use that as a template. 2 lines in the Con section and one dummy PUB Main
    11) Remove all Spin code from the Cog_Trig version - the first few lines are now this (same for all cogjects)
    CON

    _clkfreq = 80_000_000 ' 5Mhz Crystal
    _clkmode = xtal1 + pll16x ' x 16

    PUB Main
    coginit(1,@entry,0) ' cog 1, cogstart, dummy value

    DAT
    org

    entry mov command_address,par ' store par as other cogs might use this too

    12) Test this compiles with no errors using F8
    13) Added FIT 496 to the end of the cog code
    14) Recompile with F8 and save the binary as "Trig.Cog"
    15) Copy "Trig.Cog" to the SD card and put card back in the propeller board. Close down Cog_Trig
    16) Now edit the Spin_Trig file. Remove the DAT section with all the cog code
    17) Change 'PUB start : okay' to 'PUB start(cogarray) : okay' so it passes the common cogarray location used to start all cogs
    18) In the same PUB, change 'okay := cog := cognew(@entry, @command) + 1' to ' okay := cog := cognew(cogarray, @command) + 1'
    19) In the main test routine, change trig.start to trig.start(cogarray)
    20) in the main test routine, load in the cog code and put a comment about which cog ReadCogFile(string("Trig.Cog")) ' loads into cog 6
    21) Test it gives the same answer as the original code.


    This is also in the comments section of Spin_Trig.Spin

    For debugging, you may as well copy all the .Cog files in the zip file to your SD card. Some of the other cogjects might be useful too.

    Now there are 10 Cogjects in the library.

    Let me know if you have any problems!

    @MacTuxLin
    1. For the FullDuplexSerial, after you minus the spin code, you'll still need to add an empty PUB to compile, right? E.g. PUB Main?

    yes, the empty pub is always the same and all cogjects start with this
    CON
    
      _clkfreq = 80_000_000                                              ' 5Mhz Crystal
      _clkmode = xtal1 + pll16x                                          ' x 16
    
    PUB Main
        coginit(1,@entry,0)                                           ' cog 1, cogstart, dummy value
    
    DAT
                            org
    
    entry            ... add your code here
    
    When you read the cog_files to cogarray, the prop automatically runs a cog for it?

    Almost. Loading cogs is in two parts - first, read the cog data into the cogarray (and I like to add a comment about which cog number we are up to)
    ReadCogFile(string("VGAGraph.Cog"))                   ' cog 4
    
    and then start the cog
      pix.PIXEngineStart(cogarray, @screen, 2)              ' pin group 2 for VGA
    

    The reason for this is because the start method ends up almost the same as your existing code. The only change is there is one new parameter being passed which is 'cogarray'.
    3. When you discard the first 24 byte for each cog_file, why's that? Removing the empty PUB Main statement?

    I am not sure why there are 24 bytes that are not used. It took a fair bit of experimenting to work out that the cog code starts at byte 24. A clue is if you take some cog code, eg Cog_Keyboard.Spin, and compile it with F8, you will see the binary code at the top has the words "Initialization". If you look really closely at the binary data, you will notice that the bytes do not correspond to the letters of 'Initialization". So the Proptool is putting that text in. This was the first clue. It took a little more work to find out that the code starts half way through the next line, at hex position $18
    4. You're using the same address as SD3.01's initialization loading other cog_files? Does it mean once you've finished reading the cog_file, prop moves this into another cog & thus frees up this 494 long in hub ram?

    Yes, and it so happens that Kye's SD card loader uses exactly 496 longs. So this is the perfect place for the cogarray temporary buffer for loading all the other cogs.

    It is tricks like that which enable the demo program to load up 10 cogjects, have 19200 bytes reserved for video ram, have Kye's SD driver and still have enough space for some demo spin code.

    @Toby, I think you are a few steps ahead of me there, can you please clarify that question?
  • MacTuxLinMacTuxLin Posts: 821
    edited 2011-04-11 17:57
    Dr_Acula wrote: »
    It is tricks like that which enable the demo program to load up 10 cogjects, have 19200 bytes reserved for video ram, have Kye's SD driver and still have enough space for some demo spin code.

    Amazing!!! Thank you for the explanations, Dr Acula. :lol: Might be beyond me :tongue: but I'll try CogJectizing some objects & post it here if it succeeds.
  • max72max72 Posts: 1,155
    edited 2011-04-11 22:59
    Thanks for converting the trig object!
    I'll play with it and try to add or remove functions.
    Thanks again.
    Massimo
Sign In or Register to comment.