View unanswered posts | View active topics It is currently Thu Mar 28, 2024 4:57 pm



Reply to topic  [ 305 posts ]  Go to page Previous  1 ... 9, 10, 11, 12, 13, 14, 15 ... 21  Next
 74xx based CPU (yet another) 
Author Message
User avatar

Joined: Fri Mar 22, 2019 8:03 am
Posts: 328
Location: Girona-Catalonia
BigEd wrote:
Interesting predicament. Would it be enough to set aside a single register for constructing long immediates? That would presumably have minimal effect on register pressure.

That's a possibility, but I guess that with only 8 registers, one of them already in use for the SP and another one potentially used as Frame Pointer, having another dedicated register would possibly be costly in general terms. However, please read my reply to Michael above (specially the last paragraph). Not sure whether I should take my own words seriously...


Tue Aug 27, 2019 11:16 am
Profile

Joined: Wed Jan 09, 2013 6:54 pm
Posts: 1780
Ah yes, fair point: from 8 to 7 is a bigger difference than from 16 to 15, and even more so from 6 to 5. Having said which: how many registers does the compiler tend to use?

But yes, if you move from 8 to 16 registers as described, that opens up other possibilities, at the cost of losing the 3-operand format.


Tue Aug 27, 2019 1:35 pm
Profile
User avatar

Joined: Fri Mar 22, 2019 8:03 am
Posts: 328
Location: Girona-Catalonia
BigEd wrote:
... Having said which: how many registers does the compiler tend to use?

Well, in theory it uses as few as it can, and in practice it also looks like that, but it definitely takes advantage of more registers if they are available if that's beneficial. Code that calls several functions and perform computations or pass them latter to other functions need more registers. In cases where the compiler runs out of physical registers, "spills" are created. A spill is a temporary register storage in the stack, that can be loaded later when needed.

I'm still undecided about whether I should implement the "prefix" immediate register that Michael suggested above. I think it's a great idea, but it requires that interesting instructions have a short immediate field to be fully effective. I don't think that having some instructions with 'prefix' immediates and some others with 'second word' immediates is a good idea though. I think the instruction set should be designed around the one or the other, but not both. So far, I removed all the instructions where the 'second word' immediate required the ALU, such as the instructions of the kind:
Code:
ld.w  [R0, 400L], R1

The immediate field in the above instruction goes to the second word, and this is not available early enough on the clock cycle to have time for the ALU to add it with R0, so this instruction was removed. On the contrary, instructions like:
Code:
mov  400L, R1
are ok because there's no ALU involved in them.

I have given another look at the compiler and made interesting changes on the stack frame behaviour in order to take advantage for as much as possible of the short immediate fields. This is something that I attempted before, but I wasn't quite satisfied of the results.

The compiler now switches automatically from a SP based stack frame to a FP (Frame Pointer, in this case Register R6) based frame, depending on the estimated size of the frame. The following example shows the edge case where the compiler makes the switch and the resulting commented assembly:
Code:
void testStack( int a, int b,  int c, int d, int e, int f )
{
  int localBuff[22];
  doSomethingElse(localBuff, a, b, c, d, e, f);
}


CPU74
Code:
   .globl   testStack
testStack:
   sub   SP, 54, SP       # prepare the local frame, 44 (localBuff has 22 elements) + 10 (doSomethingElse has 5 stack arguments)
   mov   r0, r2           # temporary register for a
   ld.w   [SP, 62], r0   # load f from the caller
   st.w   r0, [SP, 8]    # pass f to the doSomethingElse function
   ld.w   [SP, 60], r0   # load e from the caller
   st.w   r0, [SP, 6]    # pass e to the doSomethingElse function
   ld.w   [SP, 58], r0   # so on...
   st.w   r0, [SP, 4]
   ld.w   [SP, 56], r0
   st.w   r0, [SP, 2]
   st.w   r1, [SP, 0]
   mov   SP, r0
   add   r0, 10, r0       # move localBuff to r0
   mov   r2, r1           # move a to R1
   jsr   doSomethingElse  # call
   add   SP, 54, SP       # restore the caller stack frame
   ret                  # return


Note that the access to "f" is very near the limit of the instruction immediate field, which is 6 bits wide for up to 63 bytes. So, one more argument or one element more in the array would generate a "mov" long immediate followed by a register+register load. Instead, the compiler determines that the offsets to the arguments would be too large, and switches to the "Frame Pointer" mode.

This is what happens if now I replace localBuff[22] by localBuff[23] in the C code above, the result is this:

CPU74
Code:
   .globl   testStack
testStack:
   push   r6             # save the frame pointer, r6 is the FP
   mov   SP, r6           # the frame pointer now points to the base of the frame
   sub   SP, 56, SP       # prepare the local frame, 46 (localBuff has 23 elements) + 10 (doSomethingElse has 5 stack arguments)
   mov   r0, r2           # temporary register for a
   ld.w   [r6, 10], r0   # load f from the caller (with reference to the FP)
   st.w   r0, [SP, 8]    # pass f to the doSomethingElse function
   ld.w   [r6, 8], r0    # so on...
   st.w   r0, [SP, 6]
   ld.w   [r6, 6], r0
   st.w   r0, [SP, 4]
   ld.w   [r6, 4], r0
   st.w   r0, [SP, 2]
   st.w   r1, [SP, 0]
   mov   SP, r0
   add   r0, 10, r0
   mov   r2, r1
   jsr   doSomethingElse
   add   SP, 56, SP       
   pop   r6
   ret

The arguments are now closely accessed with reference to R6, which is acting as the frame base pointer, instead of long offsets from SP

- On this particular example, the second case is slightly more expensive (3 more instruction cycles) than the first one, however in the general case we must consider that function arguments tend to be used more than once in functions that will benefit from that. Still considering the example above, one more element added to the array will result in a regression of only 1 clock cycle with respect case one. From that on, the second case will beat more and more the first case as the array size gets increased. By accessing arguments relative to the frame pointer through small immediates instead of long ones from the stack pointer, we save 2 clock cycles and 2 instruction words per argument access. In addition, a smaller number of intermediate registers may be required because we do not need to form the long immediate offsets with mov instructions. So I'm happy with this.

- Now, there's yet another source of function frame related improvement. The optimisation above considers the entire frame size to decide whether to switch to Frame pointer related offsets, but it still reserves the entire local frame for the called functions arguments. In effect, the 5 stack slots (10 bytes) required to pass arguments to 'doSomethingElse', are reserved immediately at the entry of the 'testStack'. This is in itself an optimisation, however with larger stack frames, this can turn out counter-productive. This is because the local variables may get too far from the stack pointer. With only positive short immediate offsets for this architecture (same as ARM-Thumb) it is not possible to access local vars through negative offsets from the FP without incurring in additional costs. The solution is to defer the SP adjustment until just the start and the end of the call sequence code. This should keep the SP as close as possible to the interesting stack objects at any given time, thus preventing long offsets in most cases. I think the ARM-Thumb does this. I will try to investigate it in the next couple of days.

Joan


Thu Aug 29, 2019 10:20 pm
Profile

Joined: Wed Jan 09, 2013 6:54 pm
Posts: 1780
It's an interesting phenomenon that a CPU design generally is frozen at some point whereas compiler development can continue. Over time, the compiler gets better. It might eventually be the case that some tradeoffs made in the CPU design are no longer optimal because the compiler now makes better code - or could do, if only the CPU weren't frozen.

In the worst case, some decisions are frozen into the CPU and the compiler is never quite good enough to make them the right decisions.

But back to the case in hand: for a given state of play with the compiler, it seems the tradeoff is
- keep a register spare to construct long immediates cheaply, but suffer more spills
- let the compiler use that register so there are fewer spills, but long immediates now are more costly.

Very interesting. There's also the possibility of changing the CPU so long immediates are cheaper, without using a register, and again that's a tradeoff against other instructions, or against clock speed, or against the difficulty of verifying a more complex design, or against the cost of the machinery.


Fri Aug 30, 2019 6:16 am
Profile
User avatar

Joined: Fri Mar 22, 2019 8:03 am
Posts: 328
Location: Girona-Catalonia
Hi Ed, Thanks for your interest. I guess tradeoffs are at the core of every engineering project and I like exploring them. As this is a hobby and I have no deadlines, I suppose I can just keep trying things as long as I enjoy ii (or at least up to a certain point). I admit that I have a bias towards working on compilers because that would have been my dream job if I would have had the opportunity when I was young (or if I was born in the right country and/or language, I suppose).

Anyway, getting back on the main subject, from the point of view of the CPU, the current approach/idea on long immediates is having them available in the data bus as part of the pre-decoding. the idea is:

(1) The PC is always one word ahead of the currently executing instruction, the immediate word is therefore available from the program memory during the current cycle (as it is pointed by the PC). The immediate is not available right at the beginning of the cycle but at some time before the end of it. This implies that the long immediate value can be used for data transfer purposes as long as it is not feed into the ALU. For example such immediate can go directly to a register, or to the Memory Address Register, or to the Memory Data Register, or even to the PC (although the latter two cases are not available on any existing instruction).

Instructions that benefit from that are direct moves "mov 400L, R0", direct loads or stores "ld.w [&address], R0", "st.w, R0, [&address]", and direct calls "call &address". These instructions take 2 words and 2 cycles because the PC must be incremented twice in order to keep the pre-fetching working. The 'call' takes an additional cycle because of the push pc stuff. The 'mov' instruction is idle during the second cycle, but the load/store use the second cycle to actually perform the memory access.

(2) The Instruction Register is prefetched as part of the execution of the previous instruction (provided it's not a jump, call or ret), so the instruction embedded fields are available early in the cycle, and can participate in ALU operations.

The following instructions "ld.w [SP, 4], R0" or "st.w R0, [SP, 4]" are both encoded in 1 word and take 2 cycles. During the first fist cycle the MAR and MDR are prepared, and the actual memory read or store is performed during the second cycle. The PC is only incremented once as these are 1 word instructions.

(3) For long immediates, the compiler only incurs in register overhead in the case of stores. Loads do not really need an additional register because the same register can be used for both the immediate and the result.

For example the inexistent instruction:
Code:
ld.w [SP, 400L], R0
can be encoded with the following instruction sequence
Code:
mov 400L, R0
ld.w [SP, R0], R0
Note that no temporary register is required for that, as the destination register is used to get the immediate before the load operation. The sequence above takes 3 words and 4 cycles.

If the same was done with "Prefix immediates" as per the Michael suggestion, the equivalent sequence would take 2 words and 3 cycles, so 1 less word and 1 less cycle. It could look like this:
Code:
Pfix 6               #  6 is the 10 higher bits of 400L
ld.w [SP, 16], R0    #  16 is the 6 lower bits of 400L  (the actual immediate is 6<<6 | 16)

This would be beneficial in this case, but it would not so for the loads and stores of case (1). They would have to be re-formulated with the incorporation of a short immediate to which apply the "prefix". The resulting 'prefixed' sequence would require 3 cycles instead of just 2, with the same number of encoding words. In the case of the 'call', the already expensive 3 cycle instruction would require 4 (!) cycles for the prefixed sequence.

So the "Prefix immediates" are beneficial for code density, and generally good for performance but not in all cases. They are also great for an eventual pipelined design because they turn the entire instruction set into one with only single word instructions.

Joan


Fri Aug 30, 2019 8:47 am
Profile
User avatar

Joined: Fri Mar 22, 2019 8:03 am
Posts: 328
Location: Girona-Catalonia
I finally pushed the Compiler to the github repo. It's in the following directory:

https://github.com/John-Lluch/CPU74/tree/master/Compiler/CPU74

There's still the need for some code cleaning, more commenting, and removal of outdated or unused code. (I tend to leave a lot of old code commented out in my sources that I usually leave alone until I'm confident that it won't be longer required). So what I posted today is not 'clean', but I decided to push it anyway because the code may remain unchanged for some time.

The compiler took me a lot of time to get it done and it required a lot more code than I anticipated. The LLVM code on which it has been based, is really a monster platform, and this just reflects it. But the results are really fantastic, and I learned about the LLVM compiler infrastructure and compilers in general beyond what I would have ever expected. In case there's anybody interested in having a look, I think the files that are more readable are the ones with the ".td" extension. In particular the files "CPU74RegisterInfo.td", "CPU74InstrFormats.td" and "CPU74InstrInfo.td". On these files the instruction set can be identified and related with similarly named files of the Assembler and Simulator, as well as the instruction set document in the Docs folder. Files with that extension within LLVM contain a form of scripted language that a tool called 'tablegen' is able to parse and convert into c++ code files, and this is designed to save repetitive coding. The remaining files are all c++ code, which are mostly subclassing of existing LLVM classes.


Sat Aug 31, 2019 7:10 pm
Profile
User avatar

Joined: Fri Mar 22, 2019 8:03 am
Posts: 328
Location: Girona-Catalonia
Just as a matter of information, I just thought of posting here a brief explanation of the Stack Frame models that are in use for the CPU74 backend

Code:
Stack frame models

  Stack Frame (SP based frame)          Stack Frame (FP based frame)

       |              |                     |              |
       |--------------|                     |--------------|
       |              |                     |              |
       |  Arguments   |                     |  Arguments   |
       |              |                     |              |
       |--------------|                     |--------------|
       | Return addr  |                     | Return addr  |
SP(*)  |--------------|              SP(*)  |--------------|
       |              |                     |  Saved FP    |
       |  Saved Regs  |              FP(1)  |--------------|
       |--------------|                     |              |
       |              |                     |  Saved Regs  |
       |  Local Vars  |                     |--------------|
       |              |                     |              |
       |--------------|                     |  Local Vars  |
       |              |                     |              |
       |   Spills     |                     |--------------|
SP(1') |--------------|                     |              |
       |              |                     |   Spills     |
       |  Call Args   |      SP(1'), BP(1)  |--------------|
       |              |                     |              |
SP(1)  |--------------|                     |  Call Args   |
                                            |              |
                                     SP(1)  |--------------|


The above represents the possible Stack frame models

SP(*) : Address pointed by SP at the function entry

With reserved call frame:

SP(1) : Address pointed by SP at function prologue completion
FP(1) : Address pointed by FP at function prologue completion

Without reserved call frame:

SP(1') : Address pointed by SP at function prologue completion
FP(1) : Address pointed by FP at function prologue completion

With Base Pointer

BP(1) : Address pointed by the Base Pointer

For CPU74 we do not use a separate register as the BP. Instead, we
chose a role for Register R6 on any given function, either as FP or BP depending
on the following rules:

- If the estimated stack size fits in the immediate load/store offset, use simple SP stack
based frame, R6 remains free for general purpose use. Otherwise use FP based frame.

- If the max size of the Call Args, plus the size of the arguments is above the immediate
load/store offset, use FP based frame with Register R6 as the BP.

- Functions with variable sized object always use the FP model

- Currently, the FP based frame is always associated with R6 as a BP, so
there's no actual FP in this mode


The above is just a copy/paste of a comment that I have in one of the compiler source files. The summary is that the compiler makes an estimate of the best mode for any given function and applies it accordingly.

As a side note, it's interesting to comment that some backends (very notoriously the ARM), can go that far as to use 3 dedicated registers pointing at different sections of the stack for a given function. A register used as FP would point to the base of the stack frame for close access to function arguments, another register used as BP will point to the base address of the spills as shown above for close access to spills and local variables, and of course the SP is made to always point to the top (I mean lower address as the stack grows down) of the stack. That's very convenient to ensure long immediates will rarely be used, but of course it consumes one additional register, which I suppose the designers of the ARM backend didn't regard as a problem because the ARM has plenty of them!

In my case, I chose to never use more than 2 stack pointing registers. Other than the SP, I will only use register R6 if necessary, which will be either pointing at the base of the stack (used as FP) , or at the base of the spills (used as BP), depending on what's estimated to be more convenient for the function, as described above.


Sun Sep 01, 2019 2:49 pm
Profile

Joined: Wed Jan 09, 2013 6:54 pm
Posts: 1780
Great details, a fine achievement, and thanks for sharing your work!


Sun Sep 01, 2019 7:35 pm
Profile
User avatar

Joined: Fri Mar 22, 2019 8:03 am
Posts: 328
Location: Girona-Catalonia
Hi, Ed, thanks for your comments!

The subject is that since Michael mentioned the "pfix" instructions, I have been given the idea a lot of thought. I looked at what I think are the pros-and cons and concluded that I should have that. So I reworked again the instruction set and I think that I got a new set that improves the previous one in many ways. I'm really a bit embarrassed of proposing once again a new instruction set, but somehow I'm convinced this one should be the final one. Or at least for this project.

So here is a picture of the overall design:

Attachment:
CPU74InstrSetV8.png
CPU74InstrSetV8.png [ 109.32 KiB | Viewed 3341 times ]


It's more RISCy than ever, with only 5 different instruction patterns (if we exclude the 'prefix' instruction) and more adjusted-to-reality immediate offset sizes. However, the main differences are the 'prefix' instruction which allows for all fixed length instructions. What I did basically is to remove ALL instructions with operands in the second word, and replaced them by ‘prefixed’ instructions.

Prefixed instructions are transparent to the user, I mean users do not need to worry (or even know) about prefixes, as the compiler and assembler will take care of them and insert them automatically when needed. From the point of view of the cpu, a prefix is just like any regular instruction. It works by storing the upper bits of long immediates in a dedicated internal register, which is then used in combination with the short immediate in the following (prefixed) instruction to form a word sized immediate. This is overall faster and shorter than previously using a 2 words mov instruction to load a long immediate on a general purpose register.

Prefixes have the following advantages:

- No need to use temporary registers for loading long immediates. Just prefix instructions that would require long immediates. It saves 1 register, 1 word in program memory and 1 clock cycle compared with loading the immediate to a register.
- No longer need for temporary registers during frame index replacement. Thanks to prefixes, all instructions are able to reach the entire address range. So things get easier both for the compiler and the user.
- A number of previous core instructions have been removed because they simply can be executed by prefixing other instructions.
- All core instructions are now exactly 1 word long, including the prefix instruction. The cpu just sees 16 bit instructions and it just executes 16 bit instructions all the time.
- Since all instructions are 1 word long, there's no longer the need to account for double PC increments for 2 word instructions, which complicated the decoding.
- Pipelining should be a lot easier to implement, if decided to be incorporated in the future.

The instruction set encoding detail looks like that:

Attachment:
CPU74InstrSetV8b.png
CPU74InstrSetV8b.png [ 190.54 KiB | Viewed 3341 times ]


Compared with the previous set, on this one has the following differences:

- Immediate fields have been reduced/optimized to make room to the prefix instruction. The seemingly more affected one is the 5 bit offset of the load/store with immediate offset, which has been reduced from the previous 6 bits. However, this has been compensated by the incorporation of a small set of dedicated SP instructions that use 8 bit offsets. The SP is by far the register that most often makes use of immediate offsets, so having now 8 bits instead of 6 should be greatly advantageous. Load/store offsets for general purpose registers are used mostly for struct member access which are expected to be much smaller. Therefore the reduction from 6 to 5 should not be that expensive. In any case, the prefix instruction is always there to the rescue at the cost of 1 program word and 1 cycle, which is still 1 word and 1 cycle less than what was required before.

As a matter of comparison, the ARM Thumb uses exactly the same offset lengths, that is: 5 bits for general registers, 8 bits for SP.
Also, incidentally, the Thumb incorporates an instruction that almost behaves like the prefix instruction. It's the so called "long branch with link" instruction. This is in reality a single instruction that is encoded in two halves, as two separated physical instructions that the cpu executes in order. The first half instruction loads 11 bits from an embedded immediate field and stores them as the higher bits of an address, the second half instruction loads the remaining 11 bits and uses them to complete the lower bits of the required address, then the address is used. So basically some sort of behaviour.

So I am now going to update the compiler and assembler, which will delay the project a bit more, but I guess I wouldn't be happy if I don’t apply something I regard as an improvement.
The full instruction set can be found labeled as 'version 8' in the usual gitHub repo.


Wed Sep 04, 2019 10:18 pm
Profile

Joined: Wed Apr 24, 2013 9:40 pm
Posts: 213
Location: Huntsville, AL
joanlluch wrote:
this one should be the final one.
I continue to make changes, reverse changes, and make more changes. The fun is the freedom to make changes to my cores and their instruction sets. It does make rework necessary, but all that is also part of the fun in defining and implementing your own processor. :)

I am glad that I've been able to make a small contribution to your project. The Inmos transputer is one of my favorite processors; I have a Transputer Development System (TDS) on the shelf in the upstairs loft. Its variable length instructions pose some problems for the assembler. Your implementation of the concept of the prefix instruction should not exhibit those problems, and the fixed length of all of your instructions should simplify the execution engine and enable a simpler pipeline.

_________________
Michael A.


Thu Sep 05, 2019 2:46 am
Profile

Joined: Wed Jan 09, 2013 6:54 pm
Posts: 1780
A nice development! Just one (or two) things to note: if and when you have interrupts, you'll probably need to arrange that the prefix instruction is joined up to the following (usually non-prefix) instruction. And, maybe, there's the question of what might happen with a run of consecutive prefix instructions. Perhaps undefined behaviour would be OK.

Interesting to see that you end up with the similarities with Thumb.

And thanks for versioning your instruction sets!


Thu Sep 05, 2019 4:24 am
Profile
User avatar

Joined: Fri Mar 22, 2019 8:03 am
Posts: 328
Location: Girona-Catalonia
MichaelM wrote:
joanlluch wrote:
I have a Transputer Development System (TDS) on the shelf in the upstairs loft.
I didn't know about that processor before, and reading about it made some kind of switch on my mind, particularly regarding how to deal with immediates. In relation to its approach to the complete instruction set I suspect that more modern processors have an advantage on terms of achievable hardware performance, but nonetheless I regard the transputer instruction set as one that would be easily suitable for a software based interpreted processor. It kind of looks to me that decoding and executing that, is just a matter of implementing a couple of jump tables... Maybe such great conceptual simplicity is what the original designers looked at.


Last edited by joanlluch on Thu Sep 05, 2019 7:36 am, edited 1 time in total.



Thu Sep 05, 2019 6:49 am
Profile
User avatar

Joined: Fri Mar 22, 2019 8:03 am
Posts: 328
Location: Girona-Catalonia
BigEd wrote:
Just one (or two) things to note: if and when you have interrupts, you'll probably need to arrange that the prefix instruction is joined up to the following (usually non-prefix) instruction. And, maybe, there's the question of what might happen with a run of consecutive prefix instructions. Perhaps undefined behaviour would be OK.!

Hi Ed,
I must confess that I totally overlooked the subject of interrupts. You are totally, right. From my limited experience I can see that prefixed instructions will indeed cause processor malfunction if interrupted in the middle. But, I have currently no idea about how hard or difficult is to solve that. I suspect it will be a matter of explicitly detecting such instructions and avoid interrupts around them. I just hope this doesn't complicate hardware too much. I have still to learn everything about this particular subject, so that's why I naively introduced changes without even thinking on it. Any pointers would be appreciated.

On the other hand, I wonder what the arm-thumb does with the "long branch with link". It can theoretically be interrupted in the middle of the two halves, I suppose. But this instruction seems to use the "Link register" (LR) as the temporary storage for the address formation, so maybe the instruction can be interrupted just fine without issues because that's one of the interrupt saved registers(?). That's a question for which I do not currently have a clear answer.

So maybe, instead of arranging for explicitly avoiding interrupts after the prefix instruction, another approach would be to save (in microcode) the Prefix Register as part of the interruption routine? Would that be excessive overheat?

About your second question on consecutive prefix instructions. With the available width of the immediate field, there will be no need for chaining them by the compiler. However, (excluding maybe interrupt issues) the presence of several consecutive ones would just cause the cpu to execute them in a row with effects only from the last one. That is, storing the 6 bit shifted immediate field in the Prefix Register is independent of any previous prefixes, with every new prefix instruction just overwriting and thus cancelling the effects of the previous one. I mean, the way I plan to implement it, the prefix instruction does not have a cumulative effect on the Prefix Register, it just sets the register with the shifted value of the immediate field.


Thu Sep 05, 2019 7:34 am
Profile

Joined: Sat Feb 02, 2013 9:40 am
Posts: 2095
Location: Canada
One way to deal with interrupts is to have an explicit interrupt polling instruction, which is basically a conditional jump on interrupt. That way one doesn’t have to worry about prefixes being interrupted. A modern OS will use polling. The cpu clock is so much faster than I/O that a poll isn’t required that often in the instruction stream. The compiler may need to be aware of the polling operation. For instance, it should output polling inside of long loops.

_________________
Robert Finch http://www.finitron.ca


Thu Sep 05, 2019 9:49 am
Profile WWW
User avatar

Joined: Fri Mar 22, 2019 8:03 am
Posts: 328
Location: Girona-Catalonia
Hi Rob, thanks for your input. One use I plan for interrupts is updating video memory during the vertical retrace time. I still have to put my head completely on this as I honestly still don't know what's required, or how fast the display can be actually updated in this way. At first glance, it seems to me that this should require a very consistent and reproducible interrupt system, I mean, not depending on explicit instructions inserted on main programs, so that's what I would aim for in principle.


Thu Sep 05, 2019 10:28 am
Profile
Display posts from previous:  Sort by  
Reply to topic   [ 305 posts ]  Go to page Previous  1 ... 9, 10, 11, 12, 13, 14, 15 ... 21  Next

Who is online

Users browsing this forum: AhrefsBot and 8 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron
Powered by phpBB® Forum Software © phpBB Group
Designed by ST Software