Sometimes we fail... Thoughts on Pretrigger-buffering

A project log for sdramThingZero - 133MS/s 32-bit Logic Analyzer

Add an old SDRAM DIMM to your SBC for a 133MS/s 32-bit Logic Analyzer, add ADCs for a Scope...

Eric HertzEric Hertz 09/29/2016 at 07:200 Comments

UPDATE4 (9-29): Big Ol' D'OH! Resolved. See bottom.

UPDATE3: Pondering the Side-Kick (sample-memory) And a big ol' D'OH! At the bottom.

UPDATE2: A bit of explanation... At the bottom.

UPDATE: Resolved. See the bottom.

Nah... this just won't quite work... It'd work, at worst, 255/256ths of the time... (or [n-1]/n, where n is the number of columns in the SDRAM, usually 256, 512, or 1024). But, at worst, 1/n times, it'd fail. In that case, you'd lose n pre-trigger samples... Not a *huge* deal, rarely a problem, and even when it is, it's little more than an (easily-missed) annoyance, but not up to my standards.

Ah well.

Also, a little bit more resistor-latch-logic... Turn an AND into a set-reset latch. Wee!

So, alternatives in mind, but much more limited... Most-obvious: tap a couple/few address-lines and do similar. This'd most-likely limit your pre-trigger buffer to something like 1/2, 1/4, 1/8th of the total memory... a little annoying (and takes more circuitry to handle smaller fractions).

I'm well-aware that FPGAs can do this, and much more, and likely quite a bit easier (design-wise, maybe not learning-curve-wise). That Is Not The Point of this project. We also flew a ship to the moon and back with computing-power on the order of 1/1000th of what many carry in their pockets to send emoticons. This project aims to make use of the SDRAM's *own* *inbuilt* circuitry/functionality, wherever possible. Think, again, about what this does with little more than a bit of software, some GPIOs, and (quite-accurately) a handful of gates. And, recall, that once this system is "Free-Running" the computer/uC, etc. is completely unnecessary to do the *fastest* I/O in the system. Maybe think of these handful of gates, and the "Free-Runner"'s software, as a DMA controller? Or, yahknow, think about the Free-Runner *as* an FPGA, of sorts.

This particular solution won't work 100% of the time, but another will... just need to think about it some more.


Simple solution: One more Free-Runner DQ for "Safe-To-Change"...


Funny how I'd been sitting there stumped for hours, and as soon as I wrote up the "fail" portion, the brain started coming up with solutions...

This one's nice for several reasons... Mainly: It fits with the whole sdramThing concept of enabling different modes without having to modify the code *in the freerunner* while free-running. Note that #sdramThing4.5 "Logic Analyzer" had two modes, Sample and Repeat. That worked out nicely because the Read command (Repeat) has a CAS-Latency, whereas the Write command (Sample) does not, so those two commands could be in entirely different columns, and easily selected via a MUX. This case, however, is a bit different, because, as shown above, there could be a case where the Trigger would occur at exactly the right time between the two Row-Activate commands, such that *neither* or *both* would occur. Rare, but possible.

Also nice, we have a few spare DQ's... As I recall, there's something like 19 in-use for the Free-Runner; 16 Address/Bank-Address signals, a handful of command-signals (RAS, CAS...), and the Chip-Select signal... On sT4.5, I muxed two separate chip-selects, one was for Sampling and one for Repeating.

Anyways, there're at least a couple extra DQ's available, so this is a great use of 'em.


So, the basic idea, again, of "Free-Running" is that each "Page" of memory is loaded-up with the necessary commands to begin a read-burst at the next page (or, at least, another page).

Those commands necessary are:

P: Precharge (close the last-used row in the next-to-be-accessed bank)

A: Activate the next-to-be-accessed row in the next-to-be-accessed bank

R: Read (burst) the next-to-be-accessed bank.

I draw that as:

   Page 0:                    Page 1:
 --------------------      ------------------- 
|        | P | A | R |--->|       | P | A | R |---> .....
 --------------------      ------------------- 
Though, the placement of the P and A commands are relatively unimportant, and could just as well be at the beginning of each page. The R[ead] command, obviously, has to be at the end of the page (minus the CAS-latency), so that the next burst-read at the next page will begin immediately upon finishing the burst-read at this page.

So, now, we're talking about having *two* different Activate commands in each page, which are selected via MUX...

ALoop = Activate while in the pre-trigger circular/loop-buffer and

ATriggered = Activate while after a trigger occurs

   Page 0:                    Page 1:
 ---------------------     --------------------- 
| P | AL | AT |...| R |-->| P | AL | AT |...| R |--> .....
 ---------------------     --------------------- 

All memory-devices will *see* all commands and the address-data associated with them, but the /Chip-Selects are stored separately.

Then, take a look again at that grid:

So, B# is Bank-Number... And... each of those boxes in the "grid" is a "page" of memory (usually 256, 512, or 1024 "columns" or, essentially, bytes).

So, now, every page in the circular-pre-buffer will have a *pre-trigger* Activate-command that points to the next one (raster-fashion, left-to-right, top-to-bottom). EXCEPT, at the end of the pre-buffer, which points back to the first page.

Every page in the pre-buffer will *also* have a *post-trigger* Activate-command that pionts to, essentially, the first "page" *after* the pre-buffer. They all point (roughly) to (jump-to) the same place.

That's a bit of a simplification... One cannot start a burst-read in *another* page within the same bank. SO, instead, I waste a few pages... it'll happen. As can be seen in the grid on the right. Say the trigger occurs in the second "page" (that's supposed to be a pulse, but also happens to look a bit like '11', which just happens to be the number in the box, as well).

So, after the trigger occurs AND at the *end* of that page-burst, the next page jumped-to will be in Bank2 *after* the circular-pre-buffer. Bank0 and Bank1 will be sacrificed... (Think it's OK to sacrifice up to 1024*3 of sample-memory, when we're talking about literally 1000 times that, available?).

So, then, when *reading back* the data, we have to do some reconstruction of the information...

First, read-back all the pages in order, starting at the upper-left... we don't know *where* the trigger occurred, yet, but we'll look for it. First page, no trigger. OK. Second page.... there's that trigger (in the sample-data)... so we know that page is where the trigger occurred. Then, since the pre-buffer is circular, we can sort the pre-buffer data "back in time" from that trigger... And, we know where that trigger jumped to ("12") and from there-on everything's sequential.

So... there may be a chance (about 1/256 - 1/1024) where the trigger occurred *exactly* during the "not safe to trigger" period... The only result would be that the actual jump out of the pretrigger-buffer will occur *one page later*... so we'll loose the oldest page of sample-memory from the pretrigger-buffer, but does that really matter...? Again, we're talking the oldest of hundreds, if not thousands stored *before* the trigger.


I'm renaming PAPAPA-refresh to APAPAP-refresh...

That concept: Who knows how long we'll be sitting around waiting for a trigger...? Could be quite some time... And, technically, the *entire* memory is supposed to be refreshed pretty regularly.

The trick is: Whenever you Activate a page, then Precharge (close) that page, it performs a refresh *on* that page. SO: Say you're in Bank0... performing a read-burst from bank0. You can fill up whatever unused "columns" with whatever you wish (including commands)... so how about filling them with Activate and Precharge commands, cycling through all the pages...? Refresh all the pages with your unused columns. Only thing is, you can only do banks1-3 from within bank0, otherwise you'd interfere with the read-burst occuring *at* bank0. (Oh, and, you have to *explicitly* address *every* page, so that'll take a lot of commands/columns, but we have *a lot* of unused columns in the pre-buffer).

So, that's another thing.


Right... but the above doesn't take into account the different commands sent to the "Side-Kick" (where the logic-analyzed samples are to be stored)... So, this is a bit more realistic...

        \ Page n-1 (assuming 256 cols/page)
DQ Bits : 0 :  1  :  2  :...:253:254:255:                 
Cmd/Addr: P : ALn : ATn :...: R :   : W :--> .....
/CSloop :___:_____:¯¯¯¯¯:XXX:___:¯¯¯:¯¯¯:
/CStrig :___:¯¯¯¯¯:_____:XXX:___:¯¯¯:¯¯¯:
/CSsk   :___:!!!!! d'oh!  ..:¯¯¯:¯¯¯:___:
So, as-implemented (above) the side-kick needs to keep the same address-scheme as the free-runner...

See the problem?

Yeahp, the Side-Kick needs to use the output of the Trigger-Handler for AL and AT, but NOT for R nor W

Hahahaha... dunno how I didn't see this coming.

Technically, the Side-Kick and Free-Runner don't *have* to use the same address-scheme... In fact, it might be entirely possible to implement a circular-buffer in the *sidekick* (similar to above) while still iterating through *all* of the Free-Runner's pages (rather than looping through the first few).

Hmmm... more pondering to do, Maybe another DQ.


Haha, simple-solution to the side-kick's /CS problem: Don't worry about it!

We'll have a separate /CS for the side-kick, as explained previously... within the loop, that /CS will go along-side the "Activate [Looped]" commands. Outside the loop, that /CS will go alongside the "Activate [Triggered]" commands (which, really, are the only ones necessary *outside the loop* anyhow).

There'll be *one* page after the trigger which will be "misdirected" such that the free-runner and the side-kick are actually bursting entirely different pages.

Immediately after the trigger is recognized (safely), the Free-Runner will begin Read-Bursting the first page *outside* the loop. But, since the Side-Kick's /CS is always-associated with the "Activate[Looped]" commands (when looping), that means the Side-Kick will Write-Burst the next page *within the loop* *after the trigger is recognized*. So, again, after the trigger is recognized, the side-kick's next write-burst will still be the next page *within* the loop, but the free-runner's next read-burst (occurring simultaneously) will be *outside* the loop.

Then, since there's no reason to have "Activate[Looped]" commands *outside* the loop, the next Activate command that the SideKick will see will be that output by the Free-Runner, which is to the *second* page, outside the loop.


Maybe, again, loss of yet another page's worth of sample-memory, but again we're talking a maximum of 1024 samples out of millions... (and, I might even be able to whittle that down to something more like 8? By shuffling the locations of the PAR commands in the first memory-locations outside the loop? Plausibly beginning a second read-burst in the same page?) And, again, it's not that *samples* are being lost, the actual sampled *data* will be stored. We're just sacrificing a few memory-locations that could've otherwise been used to store *more* samples.

I'm OK with that. It's kinda like having char string[80] = "This ain't 80 characters...";

So,what memory-space is this sacrificing...? It'll reduce the pre-trigger sample-space by one page. Which, of course, could be software-extended by another page, but that, of course, would reduce the post-trigger sample-space by one page. Either way, hardly worth worrying about a few KB when you're talking about a 256MB DIMM...