<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.8.7">Jekyll</generator><link href="http://byronlathi.com/feed.xml" rel="self" type="application/atom+xml" /><link href="http://byronlathi.com/" rel="alternate" type="text/html" /><updated>2023-11-25T03:08:05-05:00</updated><id>http://byronlathi.com/feed.xml</id><title type="html">Byron Lathi</title><subtitle>My name is Byron Lathi, and I am an FPGA/ASIC Validation Engineer at SpaceX.</subtitle><entry><title type="html">Super6502 Progress</title><link href="http://byronlathi.com/6502+fpga/2023/11/24/super6502-progress.html" rel="alternate" type="text/html" title="Super6502 Progress" /><published>2023-11-24T00:00:00-05:00</published><updated>2023-11-24T00:00:00-05:00</updated><id>http://byronlathi.com/6502+fpga/2023/11/24/super6502-progress</id><content type="html" xml:base="http://byronlathi.com/6502+fpga/2023/11/24/super6502-progress.html">&lt;p&gt;Since my last update, I have made quite a bit of progress on this project. The
biggest upgrade is with simulation. Most of the time spent working on this
project is on the bus to and from work, which means I do not have access to
hardware to test any new features. To solve this, I have created a simulation
environment that lets me test the entire system somewhat accurately.&lt;/p&gt;

&lt;p&gt;The way it works is I create a top level wrapper around the super6502 module
that includes simulated sdram, sd card, uart, and most importantly a CPU.
Testbenchs can then include this module and everything will start running. The
syntax used to load the ROM in the FPGA with the correct image also works in
simulation, and testing with new sd card images is easier as well, since I
don’t need to mess around with a physical SD card. The downside is that it is
much, much slower, but that is to be expected. Running a full chip simulation
up to boot takes nearly 2 hours, where that same process is done in realtime in
this gif:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/6502fpga/rtc/boot.gif&quot; alt=&quot;boot.gif&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Still, it is helpful and lets me get work done at all away from home. I also
have all of these testbenches run whenever I push to gitlab. I also compile the
bootloader and kernel, and generate the fpga image. This helps me if I make
changes to one aspect and forget about others.&lt;/p&gt;

&lt;p&gt;This has gotten me into a little bit of trouble when today, after a few months
of not testing on hardware, the thing didn’t work at all. I traced it back to
this issue &lt;a href=&quot;https://git.byronlathi.com/bslathi19/super6502/-/issues/38&quot;&gt;#38&lt;/a&gt;,
which worked in simulation but doesn’t actually work in hardware. The changes
that I made to the sdram adapter don’t seem to work the same way.&lt;/p&gt;

&lt;p&gt;Either way, simulation did help me implement many other features including the
interrupt controller, memory mapper, and rtc.&lt;/p&gt;

&lt;p&gt;Since one of the goals is to create a multitasking operating system, I followed
what I was tought in ECE391. The earlier checkpoints are not as easy as they
were in that class though since I have to write the hardware as well as the
software. “Initialize the RTC” means that I have to actually make the RTC as
well.&lt;/p&gt;

&lt;p&gt;With the features that I have added, I am making good progress on getting a
working kernel. The biggest things that I added, as mentioned before, are the
interrupt controller, memory mapper, and rtc. The interrupt controller allows
128 different interrupts, with higher priority ones being served first. The
memory mapper is a simple remapper which allows replacing the top 4 bits of the
address with an additional 16 bits. This is how the cpu will be able to acess
all of SDRAM despite its small address space. The RTC can act as both a counter
as well as generate periodic interrupts, which you can see in the gif above as
the repeated ‘A’s.&lt;/p&gt;

&lt;p&gt;I plan to use the RTC interrupts to do preemptive multitasking. My next step is
to add a ‘terminal’ driver. In 391 we use an emulated keyboard, but for this
system we only have a serial interface. It can most likely be used in the same
however.&lt;/p&gt;

&lt;p&gt;After that, we need to support system calls and running programs.&lt;/p&gt;

&lt;p&gt;I also added a README and some documentation, so check it out if you haven’t in
a while: &lt;a href=&quot;https://git.byronlathi.com/bslathi19/super6502&quot;&gt;super6502 on gitlab&lt;/a&gt;&lt;/p&gt;</content><author><name></name></author><summary type="html">Since my last update, I have made quite a bit of progress on this project. The biggest upgrade is with simulation. Most of the time spent working on this project is on the bus to and from work, which means I do not have access to hardware to test any new features. To solve this, I have created a simulation environment that lets me test the entire system somewhat accurately.</summary></entry><entry><title type="html">New 6502 Bootloader</title><link href="http://byronlathi.com/6502+fpga/2023/08/27/New-6502-Bootloader.html" rel="alternate" type="text/html" title="New 6502 Bootloader" /><published>2023-08-27T00:00:00-04:00</published><updated>2023-08-27T00:00:00-04:00</updated><id>http://byronlathi.com/6502+fpga/2023/08/27/New-6502-Bootloader</id><content type="html" xml:base="http://byronlathi.com/6502+fpga/2023/08/27/New-6502-Bootloader.html">&lt;p&gt;Continuing from my last post about SDRAM, there was one more issue that I did
not notice at the time, which is auto refresh. The 6502 bus normally only gives
one cycle for any access, but it is not always possible for devices to have
such a quick turnaround. Specifically, the SDRAM may go into a refresh cycle
and not respond in time&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/6502fpga/efinix/refresh_req.png&quot; alt=&quot;Refresh Request&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There is a signal coming from the SDRAM controller which indicates when an
auto-refresh is occuring. When this happens, we assert RDY, which pauses the
CPU until the refresh is over, at which point we let the CPU keep going.
Without this, the CPU would read garbage from RAM whenever a refresh happened.&lt;/p&gt;

&lt;p&gt;The next big thing that I did was change from using the SD protocol for the SD
card to using SPI. The spec for the SD protocol is not really available for
free, and I was having trouble getting it to work properly. SPI is very easy
though, and once I switched over to it, I had very few problems with the SD
card. I found a tutorial &lt;a href=&quot;https://github.com/ryanj1234/SD_TUTORIAL_PART4&quot;&gt;here&lt;/a&gt;
which was very useful. I first copied it almost directly and tried it out on
the Sapphire SOC, which is the RISCV CPU for Efinix FPGAs. That worked well, so
then I ported it over to the 6502. Originally I copied it over in C, but later
rewrote it in assembly to make it much faster and smaller.&lt;/p&gt;

&lt;p&gt;After this, I rethought how I was booting the system. I knew that I wanted to
be able to use a normal, modern filesystem and I chose FAT32 with LFN, or VFAT.
This means I can plug the SD card into a modern computer and simply drag and
drop files over.&lt;/p&gt;

&lt;p&gt;The system first boots into the ROM built in to the FPGA. This contains a bunch
of the cc65 runtime, and the code to read from the SD card. It then load the
boot sector from the SD card and jumps to it.&lt;/p&gt;

&lt;p&gt;The bootsector is output as its own file, but is linked along with the ROM so
it has access to all exported functions. I was thinking about having the ROM be
more like a BIOS, which would have a defined interface that wouldn’t change
every time I changed the ROM code. In the end, it was easier just to have them
linked together and update the FPGA image every time the ROM changed.&lt;/p&gt;

&lt;p&gt;I create the filesystem on my laptop, then use &lt;code class=&quot;highlighter-rouge&quot;&gt;dd&lt;/code&gt; to copy over just the parts
of the bootsector that need to change, leaving the BPB which contains all the
information about the drive. Then I can copy over the second stage bootloader
and the kernel as regular files onto the mounted SD card.&lt;/p&gt;

&lt;p&gt;The bootsector will look through the root directory of the SD card and find a
file called “BOOT2   BIN”, a.k.a. “boot2.bin”. We ignore LFN since its not
neccessary for now. After loading it into memory we jump straight into it.&lt;/p&gt;

&lt;p&gt;The second stage bootloader will then look through the root folder for a file
called “KERNEL  O65”. It will then load it into memory and parse it. The reason
for the second stage bootloader is that it needs to be able to load multiple
sectors into memory and parse the FAT and o65 file, which the first stage
bootloader would be to small to do. the o65 format allows for relocation, but
we do not relocate the kernel. We still have to parse through all the
relocation tables since the file header does not contain offsets into the file
for each section, you have to parse through it to figure out when the segents
end.&lt;/p&gt;

&lt;p&gt;After we parse through the kernel file and copy the text and data segments to
the corresponding place in memory, we jump to the kernel.&lt;/p&gt;

&lt;p&gt;And thats where I stand right now. I still need to add in the memory mapper, a
clock, and the ability to load user programs.&lt;/p&gt;

&lt;p&gt;It’s been a while since my last update, but I’ve been a little busy finishing
school, going on vacation, and then starting my new job. I do think that I have
been making some good progress here, and by the end of the year I’ll have
something worth showing.&lt;/p&gt;

&lt;p&gt;As always, code is on my &lt;a href=&quot;https://git.byronlathi.com/bslathi19/super6502&quot;&gt;gitlab&lt;/a&gt;&lt;/p&gt;</content><author><name></name></author><summary type="html">Continuing from my last post about SDRAM, there was one more issue that I did not notice at the time, which is auto refresh. The 6502 bus normally only gives one cycle for any access, but it is not always possible for devices to have such a quick turnaround. Specifically, the SDRAM may go into a refresh cycle and not respond in time</summary></entry><entry><title type="html">Efinix SDRAM Adapter for 6502</title><link href="http://byronlathi.com/6502+fpga/2022/12/23/Efinix-SDRAM.html" rel="alternate" type="text/html" title="Efinix SDRAM Adapter for 6502" /><published>2022-12-23T00:00:00-05:00</published><updated>2022-12-23T00:00:00-05:00</updated><id>http://byronlathi.com/6502+fpga/2022/12/23/Efinix-SDRAM</id><content type="html" xml:base="http://byronlathi.com/6502+fpga/2022/12/23/Efinix-SDRAM.html">&lt;p&gt;This post will be similar to the last post on SDRAM
&lt;a href=&quot;/6502+fpga/2022/03/17/SDRAM-for-6502.html&quot;&gt;here&lt;/a&gt;, but there are some differences.&lt;/p&gt;

&lt;p&gt;For one, the Intel SDRAM controller uses the avalon bus, which seems a little
simpler than the AXI4 bus that the Efinix one uses. I say that, but I never
tried to make it work so I could have just been making it harder on myself for
no reason.&lt;/p&gt;

&lt;p&gt;Instead of using AXI4, I am using what they call the “native” interface, which
has very little overhead but I guess the timings are more important (again
maybe AXI4 would have been easier, I just wanted less signals to deal with)&lt;/p&gt;

&lt;p&gt;Like the last one, there is an adapter in between the 6502 bus and the memory
controller that consists of a state machine to convert the slow and drawn out
memory accesses of the 6502 to the quick pulses of the SDRAM.&lt;/p&gt;

&lt;p&gt;Here is a picture showing this in action. Note in this picture that it is
supposed to only be a write, but due to the timing of the absolute memory
accesses it appears to do a read and then a write.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/6502fpga/efinix/combo_timing.png&quot; alt=&quot;combo_timing&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here is an easier to read timing diagram:
&lt;img src=&quot;/assets/6502fpga/efinix/sdram_native_read.svg&quot; alt=&quot;sdram_native_read&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Starting with the read, a read is triggered whenever the cpu clock is high, the
chip select is high, and rwb is high. The address for the 6502 is valid from
negative edge to negative edge, and the data is expected before that second
falling edge, so that middle rising edge is the best place to read. We hold the
read enable pin high until the controller acknowledges it by raising the
rd_ack signal. Once we receive that we stop asserting read enable and wait for
the rd_valid signal, which means the data coming out of the controller is
valid. In this case, the value is 0xFF. This also happens to be the data that
we are writing, since it is what was written to that address the previous time.&lt;/p&gt;

&lt;p&gt;Here is an easier to read timing diagram:
&lt;img src=&quot;/assets/6502fpga/efinix/sdram_native_write.svg&quot; alt=&quot;sdram_native_write&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The write is very similar, except that the data should be written at the second
falling edge, not on the positive edge. Again this absoulte write is a bad
example, but just focus during the time when rwb is low and consider that one
cycle. normally, data_in would be valid at that rising edge, but often it will
be a little bit after which means we can’t latch the data at that edge, and we
need to wait until the end of the cycle for it to be valid. When the negative
edge occurs, we register the address and the data to write, since the write
could overlap the next cycle by a little bit.&lt;/p&gt;

&lt;p&gt;Just like the write we raise the write enable signal and wait for the wr_ack
signal from the controller. Once we receive that signal we are done though.
Note that the “last” signal is rasied at the same time as the read or write
enable pins, since we are not doing bursts.&lt;/p&gt;

&lt;p&gt;After each access, the state machine goes into a wait state until the chip
select is high and the cpu clock is high. At that point, we are in the next cpu
cycle ready for a read or write again.&lt;/p&gt;

&lt;p&gt;Here is what a write immediately followed by a read would look like.
&lt;img src=&quot;/assets/6502fpga/efinix/sdram_native_sequence.svg&quot; alt=&quot;sdram_native_sequence&quot; /&gt;
In reality, all of the sdram signals are much shorter, and the read data comes
well before the falling edge as seen in the above logic analyzer screenshot.&lt;/p&gt;

&lt;p&gt;The documentation for the sdram controller is okay, but to be honest just
copying the reference design is much easier in terms of the interface designer
settings and port declarations.&lt;/p&gt;

&lt;p&gt;I did run into a few problems. This first was the &lt;code class=&quot;highlighter-rouge&quot;&gt;sdr_init_done&lt;/code&gt; signal. You
have to wait for it to be high before you try to use the memory controller. At
first I had the memory controller reset on the same line as the cpu resset. The
6502 takes 7 cycles after reset to start, which I thought would be enough, but
is not. Also, the cpu may make garbage memory accesses which could try to
access SDRAM when it is not ready. Now when I press the reset button it waits
for the memory controller to initialize before letting the cpu start.&lt;/p&gt;

&lt;p&gt;The second problem I had is a stupid one but it still stumped me for a bit. The
output of a pll in interface designer can be used as an internal design signal
or can be brought out to a pin. I had made a block for the SDRAM clock, but
never actually set it to be the output from the PLL. I kept looking at the
signals and seeing that they were the exact same as the ones in the manual, but
it was not working. Turns out, the SDRAM chip needs a clock signal to work. I
suppose the ‘S’ in ‘SDRAM’ is not there for nothing.&lt;/p&gt;

&lt;p&gt;As always, the code can be found on my
&lt;a href=&quot;https://git.byronlathi.com/bslathi19/super6502/-/tree/master/hw/efinix_fpga&quot;&gt;gitlab&lt;/a&gt;.&lt;/p&gt;</content><author><name></name></author><summary type="html">This post will be similar to the last post on SDRAM here, but there are some differences.</summary></entry><entry><title type="html">Efinix Trion FPGA with 6502</title><link href="http://byronlathi.com/6502+fpga/2022/12/20/Efinix-FPGA-6502.html" rel="alternate" type="text/html" title="Efinix Trion FPGA with 6502" /><published>2022-12-20T00:00:00-05:00</published><updated>2022-12-20T00:00:00-05:00</updated><id>http://byronlathi.com/6502+fpga/2022/12/20/Efinix-FPGA-6502</id><content type="html" xml:base="http://byronlathi.com/6502+fpga/2022/12/20/Efinix-FPGA-6502.html">&lt;p&gt;For the previous posts in this series, I have been using a DE10-Nano as the
fpga board and the IO board that was given to us in ECE385 as the IO board. At
some point though, I would like to make this a self contained circuit board to
give me some experience designing an FPGA board. Unfortunately, due to chip
shortages there are essentially no MAX10 FPGAs for sale, anywhere. Even if
there were some in stock, they are actually somewhat expensive.&lt;/p&gt;

&lt;p&gt;I considered using Xilinx instead, but I wasn’t a huge fan of their software,
and I definitely was not a fan of its install size. I also took a look at a few
of the smaller brands like microsemi and lattice, but again the software didn’t
look great and I also wanted to have free IP, especially a memory controller.&lt;/p&gt;

&lt;p&gt;In the end, I decided on a new brand called Efinix. It was kind of a gamble
since you can’t actually try their software without buying a dev kit, and the
cheap dev kits were not in stock, but it is actually pretty nice. Definitely
lighter weight than the Quartus or Vivado, but not as terrible and text based
as lattice diamond. That said, I did run into issues with libraries on linux
that took a while to diagnose, and also the automatic PLL calculator doesn’t
work anymore after I switched to i3 as my window manager from GNOME. Not sure
why.&lt;/p&gt;

&lt;p&gt;The downside of this is that I can’t use the IO board, and I also had to make a
new CPU adapter board. The CPU adpter board was pretty easy and I whipped it up
in KiCad and got it a few weeks later. At this point, I still have not made the
IO board though, so any SD card action is going to have to wait until later.
The T20F256 dev kit that I am using has many more headers that the DE10, but
thats pretty much all it has. There are 3 buttons and 10 LEDs, but no switches
or 7 segment displays, and no arduino compatible headers.&lt;/p&gt;

&lt;p&gt;That said, it is still enough to get the CPU working. As before, the CPU is
essentially wired directly to the FPGA pins with nothing in between. On the
FPGA is a boot rom which contains an infinite loop, &lt;code class=&quot;highlighter-rouge&quot;&gt;EA 4C 01 00&lt;/code&gt;. the &lt;code class=&quot;highlighter-rouge&quot;&gt;EA&lt;/code&gt; at
the beginning is to show that the loop is running correctly, as the reset
vector will run the NOP while the loop will not.&lt;/p&gt;

&lt;p&gt;That is pretty much it, here is the view from the logic analyzer, as well as a
more stylized timing diagram.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/6502fpga/efinix/la.png&quot; alt=&quot;la&quot; /&gt;
&lt;img src=&quot;/assets/6502fpga/efinix/wavedrom.svg&quot; alt=&quot;wavedrom&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Getting the block ram to work was kind of a pain. Efinity, the name of the
Efinix IDE, has a built in IP browser and editor, but the bram IP is in beta
and is distributed on their website as a zip file that you have to copy into
your project, then run a few python scripts to configure it. Hopefully it will
be more convenient to use in the future, but it works well enough for now.&lt;/p&gt;

&lt;p&gt;Next I will work on getting the SDRAM working, and after that I can work on the
IO board. Some of the things I want on the IO board are SDCard, USB, VGA/HDMI,
Ethernet, PS/2, and USB Serial. That may be ambitious, but I want to build in
as much functionality as I can.&lt;/p&gt;</content><author><name></name></author><summary type="html">For the previous posts in this series, I have been using a DE10-Nano as the fpga board and the IO board that was given to us in ECE385 as the IO board. At some point though, I would like to make this a self contained circuit board to give me some experience designing an FPGA board. Unfortunately, due to chip shortages there are essentially no MAX10 FPGAs for sale, anywhere. Even if there were some in stock, they are actually somewhat expensive.</summary></entry><entry><title type="html">Executing Programs</title><link href="http://byronlathi.com/6502+fpga/2022/04/16/Executing-Programs.html" rel="alternate" type="text/html" title="Executing Programs" /><published>2022-04-16T00:00:00-04:00</published><updated>2022-04-16T00:00:00-04:00</updated><id>http://byronlathi.com/6502+fpga/2022/04/16/Executing-Programs</id><content type="html" xml:base="http://byronlathi.com/6502+fpga/2022/04/16/Executing-Programs.html">&lt;p&gt;At long last I have finally succeeded in loading a program off of an external
device and executing it.&lt;/p&gt;

&lt;p&gt;I mentioned in my last post that I was having trouble reading multiple blocks
from the SD card. As it turns out, this was because I was resetting the bit
count to 0 instead of 7. Since the SD card transmits MSB first, it was
reading bit 0 and then moving on the the next byte, which screwed everything
up. Changing it to 7 means that it reads the first byte properly. The reason it
worked the first time was because I reset it properly during an actualy reset,
but not after it is done reading. Anyway, onto the file system.&lt;/p&gt;

&lt;h2 id=&quot;file-system&quot;&gt;File System&lt;/h2&gt;

&lt;p&gt;I chose to use FAT32 since it is simple. I have experience working with ext2
from my previous project, but FAT is simpler and is the preferred choice for SD
cards anyway. Additionally, FAT32 has 512b sectors which maps to the 512b sd
card blocks.&lt;/p&gt;

&lt;p&gt;The first sector is the boot sector. The boot sector is identified by the
signature in the last two bytes, which is &lt;code class=&quot;highlighter-rouge&quot;&gt;0x55 0xAA&lt;/code&gt;. The first 3 bytes of the
boot sector are cpu instructions, usually a jump, which is followed by a bunch
of information about the drive, and then the rest of the code that the first
instruction jumps too.&lt;/p&gt;

&lt;p&gt;Since I am not booting from the SD card, I don’t care about any of the code
that is on there. The things that we do care about are the number of reserved
sectors, the number of file allocation tables, and the number of sectors per
file allocation table. If we add up the number of reserved sectors with the
number of FAT sectors, we get the offset of the data region. The start of the
data region will be the root directory.&lt;/p&gt;

&lt;p&gt;Here you can see the result of reading the boot sector:
&lt;img src=&quot;/assets/6502fpga/exec/read_boot_sector_2.png&quot; alt=&quot;read_boot_sector_2.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the root directory there are a series of directory listings. Since we are
using VFAT, each entry has 2 or more directory entries: 1 or more VFAT entry
and 1 DOS entry. the VFAT entries store the longer file name and the DOS entry
stores the actual location and information about the file.&lt;/p&gt;

&lt;p&gt;Here is the result of reading the directory entry:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/6502fpga/exec/read_directory_entry_2.png&quot; alt=&quot;read_directory_entry_2.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The file location that we read from the directory entry is the cluster number
in the data region. Cluster size is given in the boot sector, which we earlier
read as 8. We know that the first two clusters don’t exist, so we take the
cluster number, subtract two from it, then multiply by 8 to get the sector
offset. We can then add that to the data region offset we calculated earlier to
get the sector that the file is in.&lt;/p&gt;

&lt;p&gt;We assume that the first file we come across is the file that we want, and then
parse it as if it is an o65 file. I may write a post about how o65 files work
but until then &lt;a href=&quot;http://www.6502.org/users/andre/o65/fileformat.html&quot;&gt;here&lt;/a&gt; is
the official spec.&lt;/p&gt;

&lt;p&gt;First we print the header of the file, which is shown here:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/6502fpga/exec/parsed_o65_2.png&quot; alt=&quot;parsed_o65_2.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here you can see the text and data segments as well as where they were
orignally linked. the o65 format allows for relocation by giving a table of
every address that would have to be changed, but we can ignore that for now by
just copying the code and data to where it wanted to be originally.&lt;/p&gt;

&lt;p&gt;Next we print out the code, so we can verify it is what we want, then copy it
to the locations specified earlier. Then we print it out again to make sure it
was copied correctly. Finally, we call the copied code as if it were a function
and then print the return value.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/6502fpga/exec/executed_o65_2.png&quot; alt=&quot;executed_o65_2.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It says the return value is 0x41, which seems correct, given the code literally
returns 0x41 and does nothing else.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;0x41&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;and there it is! We compiled a program, wrote it to the SD card, read it off
the the SD card and then executed it!&lt;/p&gt;

&lt;p&gt;If you want to check it out, here is a link to my &lt;a href=&quot;https://git.byronlathi.com/bslathi19/super6502&quot;&gt;gitlab&lt;/a&gt;.&lt;/p&gt;</content><author><name></name></author><summary type="html">At long last I have finally succeeded in loading a program off of an external device and executing it.</summary></entry><entry><title type="html">SD Card for 6502</title><link href="http://byronlathi.com/6502+fpga/2022/04/11/SD-Card-for-6502.html" rel="alternate" type="text/html" title="SD Card for 6502" /><published>2022-04-11T00:00:00-04:00</published><updated>2022-04-11T00:00:00-04:00</updated><id>http://byronlathi.com/6502+fpga/2022/04/11/SD-Card-for-6502</id><content type="html" xml:base="http://byronlathi.com/6502+fpga/2022/04/11/SD-Card-for-6502.html">&lt;p&gt;The one thing that has always stopped me when making these 6502 systems is
figuring out how to read data from storage. The first time, I tried using an
original Apple IWM with a floppy drive emulator but could not get that to work.
The second time, last year, I tried using a USB microcontroller from FTDI, but
also could not get that to work. Even earlier with this project I looked to see
how much work it would be to use the MAX3421E to read from a USB drive, but
didn’t go through with it.&lt;/p&gt;

&lt;p&gt;I have finally made some progress using an SD card though. The I/O breakout
board that they give us for ECE 385 has a micro SD card slot that can be
accessed through SPI.&lt;/p&gt;

&lt;p&gt;The Shield uses every single pin on the arduino expansion header though, so I
had to make a few modifications so that I could keep using the UART. The UART
uses the traditional pins 0 and 1, which are sed for an LED and for I2S audio
data. The I2S did not end up being a problem, but the LED pin did mess things
up. I fixed this by removing the current limiting resistor on one end, so that
I could put it back later. I also soldered headers on top of the board so that
I could plug the wires back in. Here is a picture of the current setup. The
resistors on the breadboard are to convert the data from the computer to 3.3v,
since the cable I am using is a 5v one.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/6502fpga/sdcard/setup.jpeg&quot; alt=&quot;setup&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As I mentioned, the SD card slot is intended to be used with the SPI protocol,
but I decided to use the 1 bit SD protocol instead, since it uses the same
pins. It would have been nice to use the 4 bit protocol, it would have been
nearly 4 times as fast, but there simply aren’t enough pins available.&lt;/p&gt;

&lt;p&gt;The SD protocol uses bidirectional lines for both commands and data, which are
handled by using pull up resistors and only driving the lines low. This can be
configured inside the FPGA so no external resistors are neccesary (If you are
looking in pin planner you need to enable that column).&lt;/p&gt;

&lt;p&gt;Commands in the SD protocol are formed as 48 bit words. the first bit is a
start bit, and is always 0. The second bit is the transmitter bit, which is
always 1. The next six bits are the command number. Following the command are
32 bits for the argument, and finally a CRC7 checksum and a 1 to end the
transaction.&lt;/p&gt;

&lt;p&gt;The CRC7 is kinda weird, but is pretty easy to calculate with an FPGA, so I
made a module that does just that. It does not use any fancy parallel
computation, so it takes 40 steps to calculate. Luckily it comes after 40 bits
are transmitted so it doesn’t matter.&lt;/p&gt;

&lt;p&gt;The interface to the cpu consists of 4 bytes for the command argument, and 1
byte for the command number. Writing the command number also triggers the
controller to send that command with the current argument.&lt;/p&gt;

&lt;p&gt;What commands do we send though?&lt;/p&gt;

&lt;p&gt;Well, the first command that we send is CMD0, which resets every card to the
idle state. Next is CMD8, where we tell the card the interface conditions. In
our case we tell it the voltage range. This will return a response code, also
on the cmd line, that tells us what the card supports. We hope that it supports
the same things. We also give it a bit pattern that it will return, so we can
confirm that it is reading our commands correctly.&lt;/p&gt;

&lt;p&gt;From there we send it ACMD41. To send ACMD commands we first send CMD55, then
the command we want, so in this case we send CMD55, then CMD41. CMD41 tells us
things like if the card is an SDHC card and what voltages it supports (again?).
This command also tells us if the card is busy or ready.&lt;/p&gt;

&lt;p&gt;We can also send CMD2, which will tell us some information about the card. I’m
using a cheap no name card though, so the name of the product is “SD   “, and
the serial and revision numbers are all 0.&lt;/p&gt;

&lt;p&gt;If we want to talk to the card, we use CMD3, which tells the card to send its
RCA, which we can use to identify the card. Following this we do CMD7 and send
the RCA to select the card.&lt;/p&gt;

&lt;p&gt;With the card selected, we then read data by sending CMD17 with the address of
the data we want to read. With SDHC cards, each read always read 512 bytes, and
the address is the address of this block. That is how they read more than 4Gb
with only a 32 bit address.&lt;/p&gt;

&lt;p&gt;One important thing to note about the protocol &lt;a href=&quot;http://my-cool-projects.blogspot.com/2013/02/success-talking-to-sd-card.html&quot;&gt;(Thanks matthew!)&lt;/a&gt;
is that the data on the lines needs to be latched on the falling edges, so that
it will be valid on rising edges. This is done by halving the sd card clock
compared to the internal clock.&lt;/p&gt;

&lt;p&gt;The SD card controller is kind of a mess if I am being honest. It is based
around a state machine and a bunch of counters, you can check it out if you
want on my &lt;a href=&quot;https://git.byronlathi.com/bslathi19/super6502&quot;&gt;gitlab&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So what next? Well there are some issues that I am having reading multiple
blocks that I need to solve, but following that I can work on getting file
system support and eventually loading and executing programs off of the SD
card.&lt;/p&gt;</content><author><name></name></author><summary type="html">The one thing that has always stopped me when making these 6502 systems is figuring out how to read data from storage. The first time, I tried using an original Apple IWM with a floppy drive emulator but could not get that to work. The second time, last year, I tried using a USB microcontroller from FTDI, but also could not get that to work. Even earlier with this project I looked to see how much work it would be to use the MAX3421E to read from a USB drive, but didn’t go through with it.</summary></entry><entry><title type="html">SDRAM for 6502</title><link href="http://byronlathi.com/6502+fpga/2022/03/17/SDRAM-for-6502.html" rel="alternate" type="text/html" title="SDRAM for 6502" /><published>2022-03-17T00:00:00-04:00</published><updated>2022-03-17T00:00:00-04:00</updated><id>http://byronlathi.com/6502+fpga/2022/03/17/SDRAM-for-6502</id><content type="html" xml:base="http://byronlathi.com/6502+fpga/2022/03/17/SDRAM-for-6502.html">&lt;p&gt;While I think it is fine to keep the boot rom in the FPGA, the main system
memory does not belong in there. The DE10-Lite has 64MB of SDRAM on the board,
which is what I will be adding.&lt;/p&gt;

&lt;p&gt;Intel includes a basic SDRAM controller with Quartus and Platform Designer, but
unlike some other IP, you can only use it in Platform Designer. Something else
that is included in Platform Designer is an “External Bus to Avalon Bridge”,
which allows an external peripheral to act as an Avalon master which is just
what I want.&lt;/p&gt;

&lt;p&gt;The SDRAM controller works as a generic memory interface, you read and write
until it says its ready, and then you are good. Well, thats what I thought at
least.&lt;/p&gt;

&lt;p&gt;The main clock for the FPGA is 50MHz, and that is also what I am running the
SDRAM at (through a 1ns delay PLL). The 6502 runs at 500KHz though, which is
100 times slower. As it turns out, the memory controller does not like holding
the chip select low for that long.&lt;/p&gt;

&lt;p&gt;What I ended up doing was setting up a simple state machine the latches the
values at first, then waits until the next access before asserting them again.&lt;/p&gt;

&lt;p&gt;Here is what the code looks like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-verilog&quot; data-lang=&quot;verilog&quot;&gt;&lt;span class=&quot;k&quot;&gt;always_comb&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;begin&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;next_teststate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;teststate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;write&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mb&quot;&gt;'0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sdram_cs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mb&quot;&gt;'0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;teststate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;S_0:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;begin&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;write&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sdram_cs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cpu_rwb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cpu_phi2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sdram_cs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sdram_cs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cpu_phi2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sdram_cs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cpu_rwb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;next_teststate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;S_1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;S_1:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;begin&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sdram_cs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cpu_rwb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;next_teststate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;S_0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;endcase&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;the &lt;code class=&quot;highlighter-rouge&quot;&gt;write&lt;/code&gt; signal comes from the chip select (which comes from the address)
and the write signal as well as the clock signal. It is important to wait for
the rising edge of the cpu clock is because the cpu asserts the address on the
falling edge of the clock, but the write data is not asserted until the rising
edge.&lt;/p&gt;

&lt;p&gt;Stage &lt;code class=&quot;highlighter-rouge&quot;&gt;S_0&lt;/code&gt; is the default state where memory accesses are allowed. Once a
memory access has occured and is completed, indicated by the ack signal, it
transitions to state &lt;code class=&quot;highlighter-rouge&quot;&gt;S_1&lt;/code&gt;, where memory accesses are not allowed until the cpu
stops trying to access memory.&lt;/p&gt;

&lt;p&gt;This works fine using SDRAM as data storage, but it could run into issues
trying to execute code or using it as zero page, since sequential memory
accesses are possible in those regions. For now though this will work as a
start.&lt;/p&gt;</content><author><name></name></author><summary type="html">While I think it is fine to keep the boot rom in the FPGA, the main system memory does not belong in there. The DE10-Lite has 64MB of SDRAM on the board, which is what I will be adding.</summary></entry><entry><title type="html">Adding Serial to 6502</title><link href="http://byronlathi.com/6502+fpga/2022/03/14/Adding-Serial-to-6502.html" rel="alternate" type="text/html" title="Adding Serial to 6502" /><published>2022-03-14T00:00:00-04:00</published><updated>2022-03-14T00:00:00-04:00</updated><id>http://byronlathi.com/6502+fpga/2022/03/14/Adding-Serial-to-6502</id><content type="html" xml:base="http://byronlathi.com/6502+fpga/2022/03/14/Adding-Serial-to-6502.html">&lt;p&gt;The simplest way to get output from the computer is over serial, but since I
want to have all peripherals inside the FPGA I decided to write a UART myself.&lt;/p&gt;

&lt;p&gt;The first thing I did was add an interrupt system so that the CPU can tell
where interrupts came from. All interrupts will go through the FPGA so it can
keep track.&lt;/p&gt;

&lt;p&gt;Here is what the interrupt system looks like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-verilog&quot; data-lang=&quot;verilog&quot;&gt;&lt;span class=&quot;k&quot;&gt;always_ff&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;posedge&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clk_50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;begin&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rst&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;irq_data_out&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;mb&quot;&gt;'0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;irq_cs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cpu_rwb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;irq_data_out&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;irq_data_out&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cpu_data_in&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;begin&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;button_1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;irq_data_out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;mb&quot;&gt;'1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uart_irq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;irq_data_out&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;mb&quot;&gt;'1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;button_1&lt;/code&gt; is a button on the board that I was using to test interrupts.&lt;/p&gt;

&lt;p&gt;At reset, the interrupt status register is cleared. The cpu can also write to
the cpu status register, but it can only clear bits, not set them. During
normal operation, if one of the interrupt sources is triggered then it sets the
appropriate bit in the irq status register &lt;code class=&quot;highlighter-rouge&quot;&gt;irq_data_out&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The cpu interrupt line is asserted whenever the interrupt status register is
not 0:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-verilog&quot; data-lang=&quot;verilog&quot;&gt;&lt;span class=&quot;k&quot;&gt;assign&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cpu_irqb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;irq_data_out&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now for the UART. I begin with the transmitter, since it was easier. The logic
for the transmitter lies in a finite state machine as follows:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/6502fpga/uart/uart_tx.drawio.png&quot; alt=&quot;uart_tx.drawio.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the IDLE state, the transmitter waits for the cpu to write to the data
transmit buffer. When data is written to that address, it sets &lt;code class=&quot;highlighter-rouge&quot;&gt;tx_flag&lt;/code&gt;. At
the next cycle, the transmitter goes into the START state. The START state
simply outputs the start bit, which is &lt;code class=&quot;highlighter-rouge&quot;&gt;0&lt;/code&gt;. It also resets the data bit count
to 0.&lt;/p&gt;

&lt;p&gt;The DATA state will output the &lt;code class=&quot;highlighter-rouge&quot;&gt;count&lt;/code&gt; bit of the data transmit buffer, then
increments count. In this way it will output the byte LSB first. It is
hardwired to output 8 bits, but this can be changed in the future. Once in the
STOP state, it outputs the stop bit &lt;code class=&quot;highlighter-rouge&quot;&gt;1&lt;/code&gt; for 1 clock cycle, before returning to
the IDLE state. The STOP state also resets &lt;code class=&quot;highlighter-rouge&quot;&gt;tx_flag&lt;/code&gt; to 0, indicating that the
transmitter is ready to transmit another byte.&lt;/p&gt;

&lt;p&gt;All of these state transitions are driven by the a clock with the desired baud
rate, which in this case is 9600. This is done by dividing the 50MHz clock with
a constant. This constant can be changed to select different baud rates, but it
is currently hard coded.&lt;/p&gt;

&lt;p&gt;The receiver is similar to the transmitter, in fact it has the same state
diagram, but operates in a slightly different way due to the fact that it has
to synchronize the clocks when it receives a data packet.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/6502fpga/uart/uart_rx.drawio.png&quot; alt=&quot;uart_rx.drawio.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the IDLE state, the receiver samples RXD to check for a start bit.
Supposedly you are supposed to sample at 16x the baud rate, but I just sample
it at 50MHz. If this causes problems then I can change it. If a start bit is
detected, we go to the START state. The counter for the clock divider is set to
half of the constant so that the receive phase is offset from the transmit
phase by 90 degrees. This ensures that the data is sampled after it has
stabilized. The Start state just goes to the DATA state.&lt;/p&gt;

&lt;p&gt;The DATA state works like in the transmitter, where it counts up to 8 before
moving to the STOP state. At each cycle in the DATA state, the data in RXD is
read into the receive buffer LSB first. The STOP state raises the rx_flag,
indicating to the CPU that data is ready to be read. This also raises a UART
interrupt. The STOP state then goes back to the IDLE state.&lt;/p&gt;

&lt;p&gt;The cpu code is very straightforward:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;_uart_txb:
        sta UART_TXB        ; Just write value, don't wait
        rts

_uart_txb_block:
        sta UART_TXB        ; Write value
@1:     lda UART_STATUS     ; Wait for status[0] to be 0
        bit #$01
        bne @1
        rts

_uart_rxb:
        lda UART_RXB        ; Read value
        ldx #$00
        rts

_uart_status:
        lda UART_STATUS
        ldx #$00
        rts
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The first function writes the data to the transmit buffer and returns. The
second functiion does the same, but then waits for the data to be transmitted
before returning. This is the one that I am using.&lt;/p&gt;

&lt;p&gt;the receive function reads the data from the receive buffer then returns.&lt;/p&gt;

&lt;p&gt;The code can be found on my
&lt;a href=&quot;https://git.byronlathi.com/bslathi19/super6502/&quot;&gt;GitLab&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also host a mirror on &lt;a href=&quot;https://github.com/bman12three4/super6502&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;</content><author><name></name></author><summary type="html">The simplest way to get output from the computer is over serial, but since I want to have all peripherals inside the FPGA I decided to write a UART myself.</summary></entry><entry><title type="html">6502 + FPGA</title><link href="http://byronlathi.com/6502+fpga/2022/03/12/6502-plus-fpga.html" rel="alternate" type="text/html" title="6502 + FPGA" /><published>2022-03-12T00:00:00-05:00</published><updated>2022-03-12T00:00:00-05:00</updated><id>http://byronlathi.com/6502+fpga/2022/03/12/6502-plus-fpga</id><content type="html" xml:base="http://byronlathi.com/6502+fpga/2022/03/12/6502-plus-fpga.html">&lt;p&gt;Well, I’ve gone and made another 6502 project.&lt;/p&gt;

&lt;p&gt;Instead of doing it on a breadboard or making a custom circuit board, I decided
this time I would utilize an FPGA for everything except the actual CPU. Having
everything in the FPGA means that things like IO and the boot ROM can be
changed all in the computer.&lt;/p&gt;

&lt;p&gt;To hook up the processor to the FPGA, I put together a little adapter board
which consists of a 40 pin socket and a 40 pin female pin header. This then
plugs directly into the expansion header of the DE10-lite, although the headers
are the same on my DE0 so it should work there too.&lt;/p&gt;

&lt;p&gt;The basic setup is as usual, 32k of ram in the bottom half, and 32k of rom in
the upper half. There is one quirk though, and that is that the 6502 is
designed for non-synchronous memory, while the memory inside the fpga is
synchrnous. The way around this was to just half the speed of the cpu clock
compared to the memory clock. The 6502 asserts addresses/data on the falling
edge, then reads data on the next falling edge. By doubling the memory clock,
the falling edge of the cpu clock is a rising edge of the memory clock, so the
memory will get two rising edges before the cpu will move on.&lt;/p&gt;

&lt;p&gt;The biggest advantage of this project for me is the IO though, the worst part
of building the previous systems was address decoding and trying to add new
devices. With the FPGA though, address decoding is only a single line of code.&lt;/p&gt;

&lt;p&gt;The board includes 6 7-segment displays, which were the first things that I
added. The verilog code is pretty simple:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-verilog&quot; data-lang=&quot;verilog&quot;&gt;&lt;span class=&quot;k&quot;&gt;assign&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hex_cs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;addr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;16'h7ff0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;addr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mh&quot;&gt;16'h7ff4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is the address decoding logic, as you can see it is super simple.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-verilog&quot; data-lang=&quot;verilog&quot;&gt;&lt;span class=&quot;kt&quot;&gt;logic&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;always_ff&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;posedge&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;begin&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rst&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mb&quot;&gt;'0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rw&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;addr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;


&lt;span class=&quot;kt&quot;&gt;logic&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hex_4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;assign&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hex_4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hex_4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;assign&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hex_4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hex_4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;assign&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hex_4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hex_4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;logic&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HEX0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HEX1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HEX2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HEX3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HEX4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HEX5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;HexDriver&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hex_drivers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hex_4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HEX5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HEX4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HEX3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HEX2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HEX1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HEX0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;assign&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HEX0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HEX0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{~&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;assign&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HEX1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HEX1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{~&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;assign&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HEX2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HEX2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{~&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;assign&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HEX3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HEX3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{~&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;assign&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HEX4&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HEX4&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{~&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;assign&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HEX5&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HEX5&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;{~&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;}}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The  code here is also pretty simple. &lt;code class=&quot;highlighter-rouge&quot;&gt;_data&lt;/code&gt; are the internal registers, of
which there are 4 of. The first 3 represent the 3 pairs of digits, and the
fourth one is a mask. If we try and write to the registers, it writes to the
correct one.&lt;/p&gt;

&lt;p&gt;We then create the hex drivers using the hex values, and then mask them. Note
the the mas is using bitwise or, and the mask bit is inverted since the hex
digits are active low, so we want to set all of the bits to high to turn it
off.&lt;/p&gt;

&lt;p&gt;The software to drive them is written in assembly, since it will be faster than
C.&lt;/p&gt;

&lt;p&gt;The first function takes in a byte to display and the index to display at:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hex_set_8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;idx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;_hex_set_8:
        phx
        cmp #$3         ; If idx &amp;gt;= 3 then fail
        bcc @1
        plx
        lda #$1
        rts
@1:     tax             ; Move idx into x
        jsr popa        ; put val into a
        sta SEVEN_SEG,x ; write to val
        lda #$0
        plx
        rts
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As I discussed in my previous post about &lt;a href=&quot;/6502/2020/04/24/C-Programming-with-cc65.html&quot;&gt;cc65 calling conventions&lt;/a&gt;, the rightmost argument is
present in the primary register, which would be &lt;code class=&quot;highlighter-rouge&quot;&gt;A&lt;/code&gt; for an 8 bit value, and the
rest of the arguments are on the parameter stack. The first thing is we check
to see if the index is greater than or equal to 3, which would be an invalid
index. If it is valid, then we write the value to SEVEN_SEG,x which would be
the address of the display plus the index.&lt;/p&gt;

&lt;p&gt;The next one is even simpler:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hex_set_16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uint16_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;_hex_set_16:
        sta SEVEN_SEG
        stx SEVEN_SEG+1
        lda #$0
        rts
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;16 bit values are passed with the low byte in &lt;code class=&quot;highlighter-rouge&quot;&gt;A&lt;/code&gt; and the high byte in &lt;code class=&quot;highlighter-rouge&quot;&gt;X&lt;/code&gt;, so
we just write the values in A and X to the display.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hex_set_24&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uint32_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;_hex_set_24:
        sta SEVEN_SEG
        stx SEVEN_SEG+1
        lda sreg
        sta SEVEN_SEG+2
        lda #$0
        rts
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This one is pretty similar, but shows off how 32 bit arguments are passed. The
low 16 bits are present in &lt;code class=&quot;highlighter-rouge&quot;&gt;A/X&lt;/code&gt; as before, but the high 16 bits are present in
a zero-page location called &lt;code class=&quot;highlighter-rouge&quot;&gt;sreg&lt;/code&gt;. Since there are only 6 digits, we don’t
care about the high 8 bits which is why we don’t read &lt;code class=&quot;highlighter-rouge&quot;&gt;sreg+1&lt;/code&gt; as you might
otherwise.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-c&quot; data-lang=&quot;c&quot;&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;hex_enable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;uint8_t&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;_hex_enable:
        sta SEVEN_SEG+3
        rts
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And finally the simplest of them all, the mask function just writes the value
in &lt;code class=&quot;highlighter-rouge&quot;&gt;A&lt;/code&gt;, no nonsense.&lt;/p&gt;

&lt;p&gt;That is all that I have done so far, but I hope to eventually add USB through
the MAX3421E that we got for 385. I have a bunch of code for it, but it needs
to be rewritten in the style that cc65 expects (that is, not C99).&lt;/p&gt;

&lt;p&gt;I am having a lot of fun going back to my roots though.&lt;/p&gt;

&lt;p&gt;View on my &lt;a href=&quot;https://git.byronlathi.com/bslathi19/super6502&quot;&gt;GitLab&lt;/a&gt;&lt;/p&gt;</content><author><name></name></author><summary type="html">Well, I’ve gone and made another 6502 project.</summary></entry><entry><title type="html">Gitlab CI with Quartus and Modelsim</title><link href="http://byronlathi.com/general/2022/03/03/quartus-gitlab-runners.html" rel="alternate" type="text/html" title="Gitlab CI with Quartus and Modelsim" /><published>2022-03-03T00:00:00-05:00</published><updated>2022-03-03T00:00:00-05:00</updated><id>http://byronlathi.com/general/2022/03/03/quartus-gitlab-runners</id><content type="html" xml:base="http://byronlathi.com/general/2022/03/03/quartus-gitlab-runners.html">&lt;p&gt;I recently reinstalled my gitlab server and decided to try and take advantage
of some of its features. One of these is the built in CI/CD, which I had never
messed with before.&lt;/p&gt;

&lt;p&gt;Since I am the only one who uses the server, I decided to set up a shared
runner for all of my projects to use. Since all of my projects would be
using it, I made it a docker-executor so that it can be used in different
setups.&lt;/p&gt;

&lt;p&gt;I followed the tutorial &lt;a href=&quot;https://docs.gitlab.com/ee/ci/docker/using_docker_images.html&quot;&gt;here&lt;/a&gt;
making a few modifications to the config. I wanted the runner to be generic,
so I took out the services stuff and just left the&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	[[runners]]
	[runners.docker]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;part, which is what tells gitlab that this is a docker-executor. From there,
I registered the runner using the same command, substituting in my url, token,
description, and default image. I chose gcc as the default image.
As I found out later, one thing I should have added &lt;code class=&quot;highlighter-rouge&quot;&gt;network_mode = &quot;host&quot;&lt;/code&gt;,
but I’ll talk about that later.&lt;/p&gt;

&lt;p&gt;Once that was run, the runner showed up and I could use it for a basic
C project. What I really wanted to do, however, was run it on my SystemVerilog
code from ECE 411. You see, the point of this was to automate testing, and I
am too lazy to write tests. My code for 411 already has tests however.&lt;/p&gt;

&lt;p&gt;The first step was creating a suitable docker image which would have both
modelsim and quartus installed. I chose to create two images, one with
18.1 Standard and the other with 18.1 Lite. Standard is what we use in class,
but requires a license. Lite does not require a license, but will not be able
to synthesize code for the Arria II. It will synthesize for the MAX 10 though,
which is the FPGA that I actually have. Both versions of Modelsim are the same
and you don’t need a license for that.&lt;/p&gt;

&lt;p&gt;The dockerfiles can be found &lt;a href=&quot;https://git.byronlathi.com/bslathi19/modelsimdocker/-/tree/master&quot;&gt;here&lt;/a&gt;.
These are also a good source for the direct download links since &lt;del&gt;altera&lt;/del&gt; intel
keeps trying to hide them. The images are quite large at 12 and 15 gigabytes,
so hopefully they will only get downloaded once.&lt;/p&gt;

&lt;p&gt;If you look through the dockerfiles you will see that there are a few hacks
needed to get Quartus and Modelsim installed. The first of which is libpng,
for which an older version is needed that is not available anymore. A link to
the older version is included. Next are all of the 32 bit packages that
are needed. I add the i386 architecture and then add all of them.&lt;/p&gt;

&lt;p&gt;After this, the software can be installed. If all the install files are in the
same directory, running the Quartus installer will install Quartus, Modelsim,
and any device files that are downloaded. Once that is complete, there is one
more hack.&lt;/p&gt;

&lt;p&gt;For some reason, Modelsim seems to only like Red Hat linux, so to use it on any
other linux system you need to either edit the &lt;code class=&quot;highlighter-rouge&quot;&gt;vsim&lt;/code&gt; file to use linux, or in
this case, create a symlink so that Modelsim sees the &lt;code class=&quot;highlighter-rouge&quot;&gt;linux_rh60&lt;/code&gt; folder that
it is looking for.&lt;/p&gt;

&lt;p&gt;From there, the images are built and pushed to dockerhub, where they can be
found &lt;a href=&quot;https://hub.docker.com/r/bslathi19/modelsim_18.1&quot;&gt;here&lt;/a&gt;. If you are
going to use these then make sure you specify a tag, there is no latest. You
have to choose lite or std.&lt;/p&gt;

&lt;p&gt;Now onto using the images. The .gitlab-ci.yml file for the project looks like
this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;na&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bslathi19/modelsim_18.1:std&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;cacheline_adaptor&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cd cacheline_adaptor&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;vsim -do run.do&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;cam&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cd cam&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;vsim -do run.do&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;fifo&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cd fifo&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;vsim -do run.do&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;multiplier&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cd multiplier&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;vsim -do run.do&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The tags are to ensure that the job gets run on the docker-executor, since
there is also a shell-executor which this would not work on.&lt;/p&gt;

&lt;p&gt;For each stage, it goes into that directory and runs the provided Modelsim
script. Unfortunately since this is class code I can’t make it public. This
kind of worked, but I ran into a problem that I wasn’t expecting. The result
(pass/fail) of the job is based on the return value of the script, but vsim
only returns a failing code if the script itself fails, not if the testbench
does. To fix this, I wrote a little patch to the end of the &lt;code class=&quot;highlighter-rouge&quot;&gt;.do&lt;/code&gt; files to see
if there were any errors during the simulation.&lt;/p&gt;

&lt;p&gt;The end of the script looks like this:&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;onfinish stop
run -all

if { [coverage attribute -name TESTSTATUS -concise] == &quot;1&quot;} {
    echo Warning
    quit -f -code 0
}

quit -code [coverage attribute -name TESTSTATUS -concise]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;the run -all is replaced with this block. The onfinish stop stops modelsim from
exiting at the end of the simulation. The `coverage atribute -name TESTSTATUS
-concise returns the highest level of error encountered during the simulation.
0 for no errors, 1 for warning, 2 for error, and 3 for fatal. Since there are
bound to be warnings, I return with 0 even if the test status is 1. If the test
status is 2 or 3, indicating an error or fatal error, then gitlab will show
the job as failed, which is what I want.&lt;/p&gt;

&lt;p&gt;Next on the list was to try and synthesize the project in quartus as well.
The quartus project I decided to use was my combination of 385 lab 6 and
411 MP2. Basically, a RISC-V Processor running on a MAX-10 FPGA. I wish I could
show you it but again it is still class code.&lt;/p&gt;

&lt;p&gt;The code for this was even simpler:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yml&quot; data-lang=&quot;yml&quot;&gt;&lt;span class=&quot;na&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bslathi19/modelsim_18.1:lite&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docker&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;build1&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;build&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;quartus_map mp2 -c mp2&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This is where I ran into the problem I mentioned before. Apparently for
licensing reasons, Quartus needs access to the computers network card.
By default I guess Docker containers don’t have a MAC address so the runner
needs to be given that config to ensure that it will. The best part about
this problem is that you do not get an error code indicating this, you get
&lt;code class=&quot;highlighter-rouge&quot;&gt;realloc(): invalid pointer&lt;/code&gt;. Very helpful. Googling Quartus in Docker
container pointed me in the right direction though.&lt;/p&gt;

&lt;p&gt;With that sorted,
it worked like a charm. The first time I ran it though it did take 15 minutes
to download the docker image.&lt;/p&gt;

&lt;p&gt;And with that, I called it a success. I purposely broke the projects
and fixed them again to see if the jobs would reflect the changes, and
they did.&lt;/p&gt;</content><author><name></name></author><summary type="html">I recently reinstalled my gitlab server and decided to try and take advantage of some of its features. One of these is the built in CI/CD, which I had never messed with before.</summary></entry></feed>