Pangram verdict · v3.3
We believe that this document is fully human-written
AI likelihood · overall
HumanArticle text · 1,880 words · 5 segments analyzed
After building a CPU, utilities for handling bus interconnects, several DMAs and memory controllers, I often find my time focused on building interfaces between designs and external peripherals. This seems to be where most of the business has landed for me. Often, these peripherals require a clock output, coming from the design, and so I’d like to spend some time describing how to generate such a “device” clock. Fig 1. A Basic SOC with Peripherals There’s actually two topics that need to be discussed when working with modern high speed peripheral design. One of them is generating the clock to be sent to the peripheral, such as Fig. 1 above illustrates. The second one involves processing a clock returned from the peripheral, as shown in Fig. 2 below. This is a key component of high speed designs such as DDR memories, eMMC, HyperRAM, or even NAND flash protocols. This second topic is one we shall need to come back to at a later date. Fig 2. Data returned with a clock Today, I’d like to discuss how to go about generating a clock to control device interaction. I first came across this problem when building a NOR flash controller, based on first a SPI interface and later a Quad SPI interface. My controller was designed for FPGAs, and so the clock could be built with a single frequency. This design had the added complication that the clock needed to be paused from time to time. Specifically, the clock needed to be turned off when nothing was going on. Likewise, the clock needed to be turned off for one cycle after dropping (i.e. activating) the chip select pin, and for a couple cycles after the transaction was complete but before raising (deactivating) the chip select. I had to deal with a similar problem when controlling a HyperRAM, but … that design failed when I wasn’t (yet) prepared to handle the return clock properly. I did say this deserved an article in its own right, did I not? Processing data on a return clock properly can be a challenge. I then built a similar design for ASIC platforms. Unlike the FPGA, the final clock speed wouldn’t be known until run time. It might be that the design started at a slower clock speed, only to later speed up to the full rate at run time. Unlike an FPGA which can be fixed later, there’s really no room for failure in ASIC work.
At least with an FPGA, if my board didn’t support a particular frequency, I could just rebuild the design for the clock frequency it did support. This doesn’t work, though, for an ASIC–since it tends to be cost prohibitive to rebuild the design at a later time when you decide to connect it to a slower part than the one you designed it for. The next design I worked with was a NAND flash design. NAND flash can be a challenge, since the protocol requires you to start at a slow frequency and only after you bring up the connection are you allowed to change to a faster frequency. This particular design was built for ASIC environments, and so it depended upon an analog component generating all the clocks I needed. This worked great, up until someone wanted to purchase the design to work on an FPGA, then another wanted it to work on an FPGA, and another and so on. Fig 3. Single Data Rate (SDR) vs Dual Data Rate (DDR)SDRDDR Just to add another twist to the problem, many protocols require data transitions on both edges of the clock, a protocol often known as “Dual Data Rate” (DDR). Unlike the other designs above, these often require a clock that is 90 degrees offset from the data–so that each clock transition takes place in the middle of each data valid window, rather than on the edges of the window. This sort of “offset” clock is necessary to guarantee setup and hold times within the slave peripheral. An example of the clock and data relationship required by DDR as opposed to a traditional “single data rate” (SDR) clock is shown in Fig. 3. By the time I got to my SDIO/eMMC controller, I think I finally had the clock division problem handled. An SDIO controller needs bring up the SD card at 400kHz, and then depending upon the card, the PCB, and the controller, the speed may then be raised to 25MHz, 50MHz, 100MHz, or even 200MHz. The clock may also be stopped whenever either there’s nothing to send or receive, or when the SOC can’t load or unload the data to the controller.
For example, you might ask an SD card to read and thus produce many blocks of data, then read the first two of these blocks into your internal buffers only to find that the CPU is slow in draining those buffers. In that case, you would need to stop the interface clock before the external card tries to send you a third block of data that would have nowhere to go. Other devices require user programmable device clock controllers, such as: 10M/100M/1Gb Ethernet controllers While each of these speeds might use a single clock, building a truly trimode controller requires some extra work. (DDR) SDRAM controllers SDRAM controllers from an FPGA standpoint tend to be simple: just produce a clock. However, you can turn the clock off for better power performance. Yes, there are rules … but we won’t get into those here today. I2S We discussed generating an I2S clock at a totally arbitrary frequency some time ago. I2C In general, I2C is too slow to be the focus of this article. There is an I3C protocol that is built on top of I2C. The techniques we discuss today might work well for I3C masters, but I’m not nearly as familiar with those. SPI – not just NOR flash While SPI slaves have a device clock as well, handling these clocks is fundamentally different from what I’m describing today. My focus today will be on generating clock signals for the purpose of controlling external devices–such as an SPI master might need to do. Specifically, today I want to look at and discuss generating a clock with one or more of the following characteristics: Output Signal: We’re talking about interface clocks–those generated by the “master” of the interface. These are digital signals, output from either an FPGA (or ASIC) device. The output may be accomplished via a component like an ODDR or an OSERDES, with or without an additional analog delay following. Discontinuous: The clock may be discontinuous. Many protocols (flash, SDIO/eMMC, etc) allow or even require, the clock to be stopped, or otherwise only toggled when there’s something to send or receive. As mentioned above, stopping the clock may also be useful for pausing a transmission in progress before a source buffer runs dry, or an incoming buffer overflows.
Dynamic Frequency: Often, the outgoing clock needs to change frequency during operation as part of the protocol. For example, the SDIO protocol needs to start at 400kHz, and then increase to 25MHz (or more). Therefore, a good clock generator will need to be able to naturally generate multiple clock frequencies as the protocol requires. Minimum pulse width: Switching between frequencies must be done by rule: clock glitches must be fully disallowed and guaranteed against. Too-short clock pulses cannot be allowed. Clock high and low durations must always be at least a half period of the fastest allowable clock. 90 Degree Offset for DDR Signaling: As shown in Fig 3, many modern protocols require both positive and negative edge signaling (DDR). This drops the required clock frequency by 2x, reducing the bandwidth that must be carried over the PCB for the same data rate. However, the clock signal required to support such DDR signaling often needs to be delayed 90 degrees from the data, so that it transitions in the middle of the data valid period. Faster than the controller’s clock: Just to make matters worse, in my eMMC design, I needed to generate a 200MHz DDR device clock from a 100MHz system clock. All this is to say that our goal today will be to create a divided clock using digital, rather than analog, logic. (Yes, I can hear my analog engineering friends jump in here with the comment that “Everything is analog!” God bless you, my friends.) The Problem The first approach I often see to this problem is the straight forward integer clock division approach. Generally, it looks something like the following: always @(posedge src_clk) if (reset) counter <= 0; else if (!active_clock) counter <= 0; else // if (active_clock) counter <= counter + 1; assign dev_clk = (high_speed) ? (src_clk && active_clock) : counter[user_selected_bit]; In this case, active_clock controls whether or not the clock is stepping, and user_selected_bit controls to what level of clock division we are interested in. As for the src_clk, that can be either the system clock or alternatively whatever is required to generate the fastest clock frequency required by the protocol.
Note that we’ve done nothing to guarantee this clock won’t glitch between speed selections, nor can we necessarily guarantee the minimum of two clock rates. We’ll come back to these requirements later, albeit with a different (better) implementation. The user logic required to use this clock this looks very simple at first: always @(posedge dev_clk or posedge reset) if (reset) begin // Reset logic end else begin pedge_data <= // Logic controlling any flops based on the dev_clk end When a protocol requires data on both edges of the clock, getting the data right for the second edge of the clock is also important. But, how shall we output data on the negative edge of a clock we’ve just created out of thin air? We’ll need to transition on the negative edge to do this. always @(negedge dev_clk or posedge reset) if (reset) begin // Reset logic end else begin nedge_data <= // Logic controlling the negative clock's data end assign output_data = (dev_clk || !ddr_mode) ? pedge_data : nedge_data; This approach leaves us with two problems. The first is that we’re using our clock as a logic signal when we assign dev_clk to possible be the same as our source clock. The second problem is that we are transitioning user logic on this clock. Worse, though, we’re now transitioning our user logic on both edges of the clock. This violates the rules of good digital logic design. These aren’t necessarily issues when building ASIC designs. However, in FPGA design, this clock will need to get onto the clocking network’s backbone somehow, and that’s not automatic. Worse, this new clock is not the same as the original src_clk–even when they are at the same frequency. There will always be a delay between the two clocks–a delay that may not be captured by pre-synthesis simulation, and so it can be a dangerous delay the engineer isn’t expecting when building this logic. This leads to two commercial ASIC design challenges. First, when designing an ASIC IP, you want to be able to test as much of the IP on an FPGA as possible. Non FPGA compatible logic needs to be moved to the periphery of the design and carefully controlled.