Today, let’s take a look at how to create some simple AutoFPGA component files that you can then use within a basic AutoFPGA design. Specifically, let’s look at how to connect wishbone components having only a single data register to an AutoFPGA project.
To do this, we’ll start out by taking a look at AutoFPGA’s configuration file format. You’ll need that to understand the basics of how to read the configuration files themselves. We’ll then discuss briefly the two Verilog files AutoFPGA generates, toplevel.v and main.v, and then how the various AutoFPGA tags get placed within that structure. This will then lead us to the point where we can create the AutoFPGA configuration files necessary to read and write single registers from a peripheral.
AutoFPGA’s Configuration File Format
Before we start discussing the details of how to write particular AutoFPGA cofiguration files, it only makes sense to pause to comment on the file structure in general. Indeed, AutoFPGA’s configuration file structure is actually fairly simple, with its focus primarily on staying out of the way.
Key value pairs
configuration files consist of a series key value pairs. Keys start at the
beginning of a line with an
@ sign and end with an
= sign. Hence, to
define the key
KEY to have a value
VALUE, you’d write:
VALUE fits within a single line, spaces will be trimmed from the ends
Value’s can also take up multiple lines, as in,
Any white space found within multiple line
VALUEs will be left alone.
allows line based comments. Comment lines begin with either a
# and a
space, or two
##s. Other comment characters, such as
/* ... */
will create comments within the result files that the
VALUE gets pasted into.
Keys within an
configuration file are unordered, with the only exception being the
@PREFIX key that separates components within a given configuration file
(more on this key in a moment).
What this means is that you can place keys for the top level module before
those for the
after the main module
keys, or interspersed between the two.
VALUEs from one
KEY may be substituted into
VALUEs from another, by
referencing the value by its keys name, as in
For example, if you define a key
You can later reference this key within another key’s value by using
@$(DEVID to reference it. Hence,
will get expanded into
KEYs that start with
@$ instead of
@ define integer valued expressions.
AutoFPGA contains a simple expression
evaluator, allowing things like:
In this case, if you’ve defined
AutoFPGA would calculate the
@BAUDCLOCKS frequency to be 100 clocks. If
@$(BAUDCLOCKS) is used in
an string context, it will evaluate to the string
"100". (The format
is adjustable via the
@BAUDCLOCKS.FORMAT tag, but we’ll get to that later.)
The @PREFIX key
AutoFPGA components begin with a
@PREFIX key. This key defines both the beginning of the component’s keys
within a configuration file, as well as the end of any prior component that
may also be defined within the file. The
@PREFIX tag also creates a
sort of local variable namespace, since keys defined following a
key are quietly prefixed in the global key structure by the
While there are a couple of other details to AutoFPGA’s configuration files, those other details can wait until we need them later.
AutoFPGA’s RTL File Structure
In this structure, there is a top level file. Within this top level file are any references to vendor specific components. For example, MMCMs, PLLs, ISERDESE2 and OSERDESE2 components would be placed either in the top level itself, or in the vendor specific files beneath it. As an example, the Xilinx Memory Interface Generator (MIG) code is vendor specific, so it would belong at this top level. Another example might be the vendor specific components necessary for a HDMI receiver. The actual line of demarkation is made by examining what Verilator can simulate. Any components that Verilator can components can be placed in main.v and below, anything else goes into the top level or the vendor specific component set that it references.
Critical components of your design that don’t meet the criteria to be placed into main.v may need external (C++ software) co-simulators. We’ll come back to the concept of simulation later.
Also within the top level file is an instantiation of the main module. The main module consists of one (or more) bus masters controlling a set of peripherals. It can also contain items that are neither bus masters nor peripherals, but these items won’t get attached to any bus.
However, AutoFPGA has no knowledge of the vendor specific or vendor independent component files. All it knows how to do is to copy and paste items into these two files to create your logic. You tell it how to interface with the other components, but that’s it.
Well, this isn’t quite true. AutoFPGA supports some Makefile based tags which can be used to create a make include file. You can use these tags to place a list of component items into a list of Verilog files included in your project. Some synthesizers, such as yosys need this information in order to build the project. Other synthesizers, isuch as Verilator, just need to be told where to look. We’ll come back to this topic later.
What I”m trying to say is that after AutoFPGA has created any top level file or main module, you’ll still need to synthesize your design from these module files. AutoFPGA doesn’t create make files, and it doesn’t build projects. These capabilities need to be part of the project that uses AutoFPGA.
Internal RTL File Structure
AutoFPGA is primarily a copy and paste utility, with some additional capabilities for composing and connecting bus structures and interrupts. What this means when it comes to the two RTL files that it generates is that AutoFPGA will primarily copy snippets of Verilog code, provided by your configuration file, into their proper places within these two files.
The majority of this work is done within main.v so let’s look at that first.
Fig 2 shows the structure of the main module.
The main module,
as shown in Fig 2, has several parts to it. It starts like all
generated file with a legal notice, copied from the file whose name is
associated with the
@LEGAL key. After that, there’s a section where
components can define
ACCESS keys. These are pre-processor macros, that
will be used to handle component dependencies if necessary (
Then comes a series of paste’d components from any
then any parameter definitions captured in
@MAIN.PARAM keys, and then any
declarations associated with
@MAIN.IODECL keys. The file continues,
defining any interrupt wires, wires defined within
interrupt vectors, and bus interaction wire components.
The big point to know here is that these keys are pasted into this file in
the order shown. I’ve written the keys in all capitols in the positions
where their values will be pasted in within Fig 2, so you can see what goes
The primary module for the component, where the actual bus interaction and
any I/O wire adjustment takes place, is placed within the value of the
@MAIN.INSERT tag for the component. We’ll discuss many of the other details
as we go along.
The top-level module has a similar, although simpler, structure as shown in Fig 3.
Unlike the main module,
KEYs impact the
There’s the list of I/O ports for the module, their declarations, and any
other definitions you might wish to have. After that, there’s the
defining wires that need to be passed to the
@TOP.MAIN, and any RTL code that needs to be
inserted into the
module via the
Not all components will need these top-level keys. If no top-level keys are given, then any keys describing ports for the main.v, file will automatically be assumed to be toplevel, ports as well, so they need not be defined twice if they are the same between both levels.
So, let’s take a look at what it takes to put a configuration file together that will specify how to impact the top level, or main module. We’ll start with some simple examples, and then move to some more complex examples.
Remember, as you go through these, that AutoFPGA is primarily a copy/paste utility, with some bus aggregation facilities added into it.
Reading a simple ad-hoc value
For our first example, let’s read a constant value from our bus. The value
itself is arbitrary for this example, so we’ll just set it to
32'h20170926. You can see the full configuration file for this example
The first step is to declare this item as a peripheral–as its own entity, and
to give it a name that will distinguish it from other peripherals within our
design. This is done via the
@PREFIX key. Let’s give this fixed data
component the name
fixdata, since that’s what we’ll be reading from it: fixed
@PREFIX key is required for all components.
It defines the beginning of the component, and ends any component definitions
prior to it. (Yes, two components can be placed into a single configuration
file. A good example of this would be the flash
that has not only read-only flash memory, but also a set of program control
registers to control the erase circuitry, and any write-enable.)
We may also want an all-caps name for some of the contexts this peripheral
might be in. This isn’t required, like the
@PREFIX key is, but it makes
a nice illustration. For this, we’ll create the key
DEVID, and give it a
This is a classic example of a key that AutoFPGA knows nothing about. It will have only the meaning we give it below.
If you want this peripheral to have a place on the bus, then AutoFPGA needs to know how many addresses to assign to it. Let’s take just one bus address (four bytes for a 32-bit data bus–the default), since we’re only going to be returning a single, pre-determined, value.
Most of the example’s we’ll deal with today have only one address.
This particular peripheral is going to be a slave peripheral to the
main wishbone bus,
wb within our design.
Once a component is declared to be a bus slave, a series of wires will be defined for it within main.v as referenced in Fig 2 above. For our component, these wires are given by,
although I’ll tend to reference these as
etc., since these descriptions are more generic and can be used regardless
of what the peripherals name, given by the
@PREFIX key, actually is. The
first three of these are the slave return wires required by the
wishbone bus. The third wire,
@$(PREFIX)_sel will be true any time the address on the bus references this
Because our value is already fixed, it won’t take any clocks to calculate
it–it’s already known. Therefore this slave is of type
SINGLE, since it
can return a result in a
SINGLE clock cycle.
SINGLE peripherals don’t
need to define their wishbone acknowledgement wires,
their stall lines,
@$(PREFIX)_stall, since they never stall and the ack
is true the same clock the peripheral is accessed.
Other peripheral types are
peripherals require a single clock to calculate their value, yet never
MEMORY peripherals are more generic wishbone
peripherals–but we’ll discuss these more later.
It’s now time to return our value to the bus.
Normally a wishbone slave also
needs to assign values to a
However, these are trivially defined for a
SINGLE peripheral type, so we
ignore them here. (The AutoFPGA generated
file ignores these lines for
SINGLE type peripherals as well.)
The last step in our peripheral definition is to give a name for this
peripheral to be used when accessing it via the debugging interface. This is
where we’ll use the
@DEVID key we defined earlier. The first of the lines
below just specifies that this peripheral has only one named register. The
second line says that, at an offset of zero words from the beginning of this
peripheral’s address is a register named
R_@$(DEVID) with a user name of
@$(DEVID). These will be turned into
While we haven’t discussed it, these two files,
are part of the host-based software interface library specification shown in
Fig 4. They are marked with red stars, since they are created by
AutoFPGA, vice the
created by the
to read from the peripheral–either in simulation or on our actual hardware.
Reading a Fixed Version number
What if we want our number to be defined by an include file?
Many of my designs contain an include file,
builddate.v, that is created
by a simple perl
defines a value
DATESTAMP containing the date when
was last run. I use this as a version number within my design–primarily
to make certain I’m not accidentally running an older design when I think
I’ve built and loaded a newer design.
Creating a register containing this version
is almost identical to the last exercise. First, we declare our bus interface
as before, although this time with a different
At this point, though, we want to instruct our
to include the
This is done through the
@MAIN.DEFNS key, whose value then gets pasted at
the top of the
Remember, AutoFPGA is primarily a copy and paste utility. You can place any Verilog you want into your main module file at this same location using this tag, although not all things you could paste would make sense at the top of a module file and before the module declaration.
Our bus interaction code within the
given by the
@MAIN.INSERT key, is going to change only slightly. This
time, instead of returning
32'h20170926, we’ll return our
The rest is just like the last example.
You can see the full configuration file for reading this version information here.
One thing to note is that
only creates some files. It doesn’t create an entire project. Hence, the
creation of the
builddate.v file is done by the main
program–both of which are outside of the configuration files that
looks at and
Read access to an internal (changing) register
Let’s create another single register within our design using AutoFPGA. Suppose we wanted to know how many clock ticks had taken place since we first initialized our design, and whether the count had (eventually) rolled over. To get this value, we’d need to initialize a register and then count clocks with it.
The pwrcount.txt configuration file describes just how to build one of such a register. We’ll walk through this file below.
Since this is a single value, again, the bus access declaration is the same as
before but with a new name,
Note that I didn’t use the
DEVID this time.
doesn’t know that key, and so it won’t miss it when it isn’t there. It’s just
an example of a key you can use if it helps you, or ignore if not.
At this point, I could just copy and paste a module reference into my main module,
assuming I had a module named
We’ll use that approach with a later example in this post for the
special purpose I/O peripheral,
Instead of doing things that way, though, let’s define a register,
r_pwrcount_data. This register declaration will get placed at the top of the
before any logic, so that it can be referenced by any logic below. Before
doing so, though, we’ll replace
pwrcount within that name by our
key, so as to continue the perception of variables with a local scope.
Now we can create the logic that defines this value.
Note that when we we are done, we set
@$(PREFIX)_data using an assign
statement rather than using
r_@$(PREFIX)_data. This is simply because
defines the bus variables for you as though you were going to create and
return them via a module–as wires, not registers.
Getting the address of the last bus error
In a similar fashion, we can place just about any logic we want within the
@MAIN.INSERT tag. Remember,
is primarily a copy and paste utility. All you need to do is to tell it how
to wire your code
to the rest of the
This works based upon the fact that there are several wires defined for each
bus. These wires are all prefixed by the name of the bus and an underscore.
Hence, for the
wb bus, these wires will all have a
Since this is a
wishbone B4 pipelined bus, all of these
bus master wires will be defined as:
AW is the address width of the bus, and
DW is the data width.
needs to be able to support multiple busses, however, of multiple types.
For this reason, the
@SLAVE.BUS key is expanded into a series of keys:
This allows us to reference the bus clock using
or even the
wb prefix of the bus as
@$(SLAVE.BUS.NAME). This means that
the address width of this slave is given by
@$(SLAVE.BUS.AWID) and the
bus data width is given by
If you find these long key names tedious, you can rename them within your
component. For example, we could have created a key
@CLK and assigned
it to hold the value of
@$(SLAVE.BUS.CLOCK.WIRE), another key
assigned it to hold the value of
@$(SLAVE.BUS.AWID), and other keys
@DW as in,
Had we done so, our
@MAIN.INSERT tag might have been
What this particular configuration file does different from our previous
examples is to reference information about the bus using the keys defined
for any bus. This allows the bus error address register, defined above, to
only have as many bits defined as necessary
(one for each address line, or
@$(AW)). Not only will this register size
be adjusted if the width of the address bus is adjusted, but it will also
be adjusted if the width of the data bus is adjusted–as illustrated by the
@$(PREFIX)_data’s dependence upon both
@$(AW). Had we worked
a little harder, we might’ve also set up the trailing two zero bits to
expand as necessary with any changing data width.
Write support for the same value
Now that you know how to read a value from the bus, what about writing to registers on the bus?
The raw register configuration file shows how this might be done. This example peripheral does nothing but set a value you give to it upon any write, and then allow you to read that value back later.
In many ways, this peripheral is almost identical to the power counter component we discussed earlier. The difference is that this register can be set via a bus write. For this reason, the boiler plate setup is almost identical.
The difference is found within the
@MAIN.INSERT tag describing how this
peripheral deals with the bus. In this case, for any bus access,
that is also a write,
wb_we, and that references this peripheral,
@$(PREFIX)_sel, we update our register. (Remember how a generic wishbone
slave interacts with the bus?)
We could have made this module more generic by replacing the
wb prefixes with
@$(SLAVE.BUS.NAME). Had we done that, we would have then
been able to use this across busses with different names. For now, because
of how we’ve defined this, this component will always only ever read
it’s values from the bus with a
Reading values from external ports
Let’s look at one more example along this same theme. Suppose we wanted to add to our design a register allowing us to:
Read from the buttons in our design
Read from any switches on our board
Set any output LEDs
Unlike our previous examples, this example will require access to I/O ports within our design, so we’ll have to deal with this when we get there.
We’ll start with the keys we’ve already defined above.
Now let’s walk through the new keys.
The first of these new keys is an
@ACCESS key. It’s an optional key, as you
can tell from the fact none of our other components above had this key.
@ACCESS key, if present, will create a
define line at the beginning
The RTL Makefile then includes instructions to copy this line from the main.v file into a design.h file, allowing any associated C++ files to know whether or not this component is a part of our design.
This key is unique in that the items within it are separated by commas, but that the last item has no comma following it. This allows AutoFPGA to put a comma between items, or not, as is necessary to compose multiple component I/Os into a design.
Immediately following the main module declaration is an opportunity to place parameters. Let’s identify that this particular design has five buttons, 8 LEDs, and 8 switches.
@MAIN.PARAM values, you can declare your I/O values.
As with so many other keys, these values will simply be pasted into the main.v file following any key-value substitutions.
In a like manner to the way we’ve defined variables before, we’ll define a five valued logic vector to hold the inputs from each of our button values.
This will get placed with all of the wire and register declarations at the top of the main.v file.
The next required piece is the
@MAIN.INSERT key, telling
how we are going to interact with the bus. In this case, we’ll compose
our five buttons into a quick vector, and then reference a special purpose
I/O module I call
This module handles bus writes and bus reads, and is a generic
Since this is a generic peripheral, it needs to return
@$(PREFIX)_stall values–even though we’ll ignore
them since this is peripheral has a
SINGLE bus interaction type.
The new key that we haven’t discussed yet is the
@MAIN.ALT key. This
key defines the logic that will be used in the case the
SPIO_ACCESS is not defined. Items within this keyword
are used for defining values external ports, such as
o_led, should be
given should the primary logic for this component not be used.
This component contains one last tag,
This tag identifies that
spio_int is a single wire, and that it may be
assigned to an interrupt vector as the interrupt generated by this peripheral.
More to come!
The goal of AutoFPGA was to be able to have a simple command line interface–simply run AutoFPGA with a list of peripherals, and get output files. You might wish to test this by removing any of the above files from the AutoFPGA command line to see how well we did, or even add some configuration files of your own.
If you choose to try this, you’ll notice that the AutoFPGA demo project contains more configuration files than the ones we’ve looked at today. These are the block RAM configuration file, the clock file that defines the clock and the reset wires, the debugging bus configuration file, and a global configuration file. Of these, only the block RAM configuration file may be easily removed. The debugging bus configuration file defines our bus master, and a bus doesn’t work very well without a master, so it would make more sense to replace that one than to remove it if you wish to change it. The clock file and global configuration file are not as easily replaced.
So, let’s come back and discuss the block RAM configuration file at a later time. Specifically, you’ll love how easy it is to change the size of the block RAM, and how well that size information is propagated throughout a AutoFPGA-based design.
Until that time, our demonstration project plus this article should be enough to build and control most beginning FPGA tasks. Even better, and unlike most proprietary solutions, all of the code AutoFPGA builds is availble for your inspection and learning pleasure.
He answered and said, Whether he be a sinner or no, I know not: one thing I know, that, whereas I was blind, now I see