Introduction
In this blog Add Pynq-Z2 board to Vivado Jan refers to a Johnson counter. I struggled to remember what a Johnson counter actually was, but, for some reason, the number 4017 came to mind. Sure enough, the CD4017 CMOS logic device turned out to be a decade counter/decoder implemented as a 5-bit Johnson counter with ten output decodes. The datasheet [1] even had an equivalent circuit for me to look at.
I also found a description of the counter in Designing with TTL Integrated Circuits [2], which says it's also known as a 'twisted-ring' counter or a 'Mobius counter': we'll see why in a moment.
I thought I'd have a go at implementing one, on my recently-acquired VIDOR 4000 board, driving some LEDs with the decoded outputs, as it's a bit more interesting than a straight 'blinky'. I'm not going to go into every small detail, but it will give an impression of how logic design is done with programmable logic, and one or two peculiarities of trying to work with the 4000 for the first time.
The Development Tools
I'm going to do this in VHDL. That's simply because I only know VHDL [what I can remember of it] and not Verilog. Of course, it doesn't really matter which I use - once Quartus has done its work, it all looks the same: a configuration bitstream that's then used by the Arduino side of things. [Do note, though, that all Arduino's own stuff has been done with Verilog so, if you're new to this and want to take their projects and adapt them, you might be better off learning Verilog.]
For the FPGA development, I'm using Quartus Prime Lite. That's the free (as in cost) version. I installed it and tried it out before buying the VIDOR, just to be sure that I wouldn't be wasting my money on a board I couldn't use. It's a moderately large download (a couple of GB, depending on the device packages you include), but wasn't too painful on a standard ADSL line. I'm running it on an old i3 laptop with Windows 8.1 as the o/s, which was the reason for trying it first. I haven't seen any problems yet, other than that I couldn't run the package update from a menu within the IDE when I realised that I hadn't included Cyclone 10 parts in the original download and had to add them later. (The solution to that problem turned out to be to download the needed device package and then run the install .exe again from outside the IDE.)
Quartus turned out to be very similar to other such development environments I've used (Xilinx ISE Webpack and Lattice Diamond) and, for the most part, I managed without any recourse to the help. I don't know how it compares with something like Vivado as I've never used that. Creating projects and editing files is just like any IDE would do it. Things that vary a bit between manufacturers are the setting up of a project (as is fairly usual, this one has a project 'wizard' to guide you through the process); the assignments, particularly how the physical package pins link to the names in your code (this one has a graphical drag-and-drop tool to make it easy); how you run the tasks, the synthesis, fitting, and so on (in this case you can just double-click on the item in the task list); and how IP is handled (with this one there's a pane on the right of the default layout showing the IP available, so you don't have to search too hard for it).
For programming the FPGA, I'm going to use the method of working via the Arduino IDE and the bootloader on the board. The board does have a footprint for a JTAG connector, so in theory I could use it with my USB Blaster clone to directly program the FPGA or the configuration memory, but it would need an adapter [the footprint is smaller than the standard 0.1" pitch], so that's for another day.
The Johnson Counter
First I need to do a bit of design. With experience, it would be possible to launch straight in with coding, particularly if we use the higher level of abstraction for building a state machine that exists in VHDL and weren't too particular with how the software implemented what we'd asked for, but in this case I'm focussing on something low-level and I'm going to guide the synthesis to [hopefully] give me what I want without constraining it or forcing it in any way other than the form of the code, so I need to consider it a little.
It turned out, after I'd refreshed my ancient memories, that a Johnson counter is a simple form of shift register counter, where the output is fed back inverted to the input. It's the output being sent back inverted that leads to the names 'twisted ring' or 'Mobius'. The inversion means that, if we start it off all '0's, it will cycle through 2n states before it returns to all '0's again. So a 5-bit Johnson counter, made from a shift register consisting of five flip-flops, will give us 10 states that we could decode. That immediately alerts us to a potential problem: with 5 flip-flops, we have 2^5 (32) possible output states, and since we're only going to be using 10 of those states, we have to give some consideration to the others. What happens if the circuit starts in one of the 'illegal' states? What happens if some chance event corrupts one of the bits? Will it self-correct? (ie do the illegal states find their way to the legal sequence, or do they sit in a closed loop?) To sort out those questions I'm going to draw a simple state diagram.
Not very tidy, but it does the job.
The states outlined in green are the legal ones. The orange is where I've noticed that each of the illegal cycles contains at least one term of the form 010xx, so if I detect that and reset the flip-flops, that will guarantee it always running back into the main sequence. [The 4017 uses a different scheme, where one of the flip-flop inputs gets manipulated if the sequence is wrong, but I didn't have the patience to sit down and work out how that functioned.]
Something that's a bit less obvious, when looking at the legal sequence, is that it's possible to decode it to give 10 different outputs using only a 2-input gate for each output. What's more, only one of the two inputs will be changing at any one clock edge, so the output decode should be glitch-free even outside the clocked process (similar sort of principle to a Grey code).
Rather than draw the decode here as logic gates, I'll present the VHDL, because I'm going to do the decode with boolean logic statements which should be readily understandable as traditional gate functions.
The VHDL Code
Here's the code I came up with. I would point out I'm just an engineer who has used FPGA for logic replacement and I'm self-taught, so my coding style probably isn't very good and I know I have bad habits that would be frowned upon by a professional. Another, more important, problem with how I work is that it wouldn't necessarily scale well - it's fine for quickly 'knocking out' a design that uses some counters, some glue logic, and a bit of buffer memory, but you need to grapple with more abstraction to go bigger [and partitioning, and common standards, and all the kind of stuff you need so that teams can work together]. So this may not be a particularly good example to you if you're a beginner, but it's what works for me.
I'm also going to get told off for using logic_unsigned for the counter decrement, but never mind. I have the luxury of doing it how I want. If you're a student or are working, do it the approved way with numeric and casting.
--------------------------------------------------------------- --- Filename: johnson.vhd --- --- Target device: 16CL016YU256C8G --- --- --- --- 5-bit Johnson counter with decade decode --- --- Prescaler to slow count to twice a second --- --- --- --- Jon Clift 5th July 2021 --- --- --- --------------------------------------------------------------- --- Rev Date Comments --- --- 1.0.0 05-Jul-21 --- --------------------------------------------------------------- library IEEE; use IEEE.std_logic_1164.all; ---use IEEE.numeric_std.all; use ieee.std_logic_arith.all; use ieee.std_logic_unsigned.all; entity johnson_counter_top is port( --- system signals clki: in std_logic; RESn: in std_logic; --- MKR PINS MKR_A0: out std_logic; MKR_D: out std_logic_vector(9 downto 0); --- SDRAM SDRAM_CK: out std_logic; SDRAM_CSn: out std_logic; SDRAM_CKE: out std_logic; --- NINA module WM10_RESn: out std_logic ); end johnson_counter_top; architecture arch_johnson_counter_top of johnson_counter_top is signal prescaler: std_logic_vector(24 downto 0); signal johnson_en: std_logic; signal johnson_state: std_logic_vector(4 downto 0); begin --- everything that runs from the (buffered) 48MHz clock input on clki clocked_stuff: process (clki) begin if (clki'event and clki='1') then --- prescaler down to 0.5s period if (prescaler(24 downto 0) = "0000000000000000000000000") then --- if reached 0 prescaler(24 downto 0) <= "1011011100011010111111111"; --- preset to 24 million less 1 johnson_en <= '1'; --- and set enable for one cycle else --- else count down prescaler <= prescaler - 1; johnson_en <= '0'; --- keeping enable low rest of time end if; ---the Johnson counter shift register if (johnson_en = '1') then if(johnson_state(4 downto 2) = "010") then --- rescue us from illegal state johnson_state(4 downto 0) <= "00000"; else --- else rotate s/r left johnson_state(4 downto 1) <= johnson_state(3 downto 0); johnson_state(0) <= not johnson_state(4); --- with inversion from last out to first in end if; end if; end if; end process clocked_stuff; decode_stuff: process (johnson_state) begin --- output decodes (not clocked) --- shouldn't glitch because only one of the two states used... --- ...changes at any clock edge (like a grey code) MKR_D(0) <= (not johnson_state(4)) and (not johnson_state(0)); MKR_D(1) <= (not johnson_state(1)) and johnson_state(0); MKR_D(2) <= (not johnson_state(2)) and johnson_state(1); MKR_D(3) <= (not johnson_state(3)) and johnson_state(2); MKR_D(4) <= (not johnson_state(4)) and johnson_state(3); MKR_D(5) <= johnson_state(4) and johnson_state(0); MKR_D(6) <= johnson_state(1) and (not johnson_state(0)); MKR_D(7) <= johnson_state(2) and (not johnson_state(1)); MKR_D(8) <= johnson_state(3) and (not johnson_state(2)); MKR_D(9) <= johnson_state(4) and (not johnson_state(3)); end process decode_stuff; --- for looking at clock jitter MKR_A0 <= clki; --- hold some of the hardware components in inactive state SDRAM_CK <= clki; SDRAM_CSn <= '1'; SDRAM_CKE <= '1'; WM10_RESn <= '0'; end arch_johnson_counter_top;
I won't go through it line-by-line. If anyone wants an explanation of anything, ask in the comments.
Converting the Bitstream
After running the 'compilation' (synthesis, fitting, assembly into a bitstream), you'd normally run the programmer task which would bring up a programming dialog, you'd select your hardware programmer, USB Blaster, or whatever, and then directly program your board.
For the Arduino route, though, we need to convert the .sof file that the programmer would normally work with into a text format (.ttf), and then use a utility program to get it into a form suitable for the Arduino bootloader. We include it in the Arduino project as a .h file (all the stuff I've seen refers to app.h, so I stuck with that as the file name). The linker will combine it with the program for the microcontroller on the VIDOR, but it doesn't form part of what's programmed onto that microcontroller because the addresses sit outside the address space of the part, instead the bootloader at the board end will recognise the addresses being out of range, realise that it's content for the FPGA, and instead push it through the FPGA to program the configuration memory attached to the FPGA. It's basically a trick to get it all into one payload for programming from the Arduino IDE, though it's very confusing when you first try to understand what's going on [as in, "how can a microcontroller, with only 256K of Flash memory, take and store an FPGA image that's larger than that?"] I still don't entirely understand what happens with the boot process, but it does seem to work. Once I had sorted out an approach that worked [by reading what others had done on the internet], I was more interested in trying out the FPGA than attempting to puzzle out Arduino's 'simple' approach to FPGA.
Converting to a text format is done within Quartus, using a utility on the Files menu.
The output .ttf file then needs further processing to give something suitable for the Arduino bootloader. Arduino provide a utility to do that. It has a .do extension and I had to look up what it was and how I might go about running it [Windows just asked me what I wanted to run it with]. Turns out it's a 'Java Servlet file'. Whilst pondering what to do with it, I searched around some more and discovered that people other than Arduino have done projects with the VIDOR and they skipped the hash stuff [I think the reason Arduino went with the Java thing is that it has an SHA256 library they could use] and simply did the bit-reversal part. I found one using Python and one using C, both very simple. I chose the C one here:
https://github.com/wd5gnr/VidorFPGA/blob/master/C/vidorcvt.c
and compiled it with VC++ 2010 Express, which was already on the laptop - it's a very simple command line program that could be compiled with practically any C compiler.
The signature stuff seems to be used by Arduino for their provided images, presumably so that code on the microcontroller can check that it's running with the correct image. There's also seems to be a scheme for seeing what functional blocks an image contains, but all that's unnecessary for a simple user project like I'm doing here.
The Sketch
For the sketch, I used the one provided here:
https://github.com/chelmich/vidor_template/tree/master/arduino/vidor_template
Although there seems to be a lot of code there, the jtag parts seem to be devoted to getting the bitstream programmed into the configuration memory via the FPGA. The main routine, which is what will be running when my FPGA code is driving the LEDs, is an endless loop doing nothing.
The bitstream is going to be programmed into the user space of the configuration memory. The 2MByte memory is apparently divided into 3 areas. At the bottom is the boot image. Half a meg up is the alternative user image. 1 Meg and up is user data space [it sounds like, from reading their forums, that Arduino had some idea of developing a kind of filesystem to hold and retrieve data from that space]. I think the bootload image goes first, then when it's not needed reboots into the user image. If so, that'll make for a very slow start up.
The Counter Working
Here I've wired an LED to each decoder output so that the LED lights when that output is high. The LEDs are a bit dim (I've limited the current to around 2mA with the resistor to ground), but it enables us to see the outputs and it looks like I got the sequence right.
Conclusions
I hope that was of some interest.
I need to revise my VHDL. I wouldn't pass any exams, the way it is at the moment.
The VIDOR works ok, but it's not all that easy to get to grips with. Initially, I felt almost like I was reverse engineering the thing to even get to the point where I could load a bitstream.
I'm not sure it was the wisest thing to spend my money on: the form-factor is awkward and limiting, some of the design decisions they've made are a bit quirky, I don't really need all the bits that allow them to sell it as an IoT device, but, in favour, it's not too bad a price for a dev board with a reasonably capable Cyclone 10 part on it, the Quartus software is fine to work with, and it was available next-day from a reputable supplier, so I'm not too unhappy.
References
[1] RCA COS/MOS Integrated Circuits. RCA Corporation. 1974. [2] Designing with TTL Integrated Circuits. Texas Instruments Incorporated. 1971. |
Top Comments