Tuesday, 7 July 2015

Experiments with a 6809 and an FPGA

After getting the FPGA test rig up and running, the next stage was to attach it to the 6809 SBC. This was critical if I am to incorporate one into the core of my next computer.

To start with I utilised the 7 segment displays as an output latch. Since I had 16 bits of display (four seven segments) I needed the following connections:
  • 8 data bus lines
  • 1 address bus line
  • 1 read line
  • 1 write line
  • 1 chip select line
  • 1 E clock line
The E clock replaced the oscillator. It was used to drive the multiplexor for the display.

Read and write allow the latch value to be both read and written. While being able to read the value displayed back is not essential to a working output latch, it would prove that the FPGA could be used in other applications where reading a register is required.

For the Chip Select, I simply borrowed an existing line that was used by the (now removed) IO board. The FPGA was at 0x90XX, meaning that, if desired, I had 8 bits of address to use as individual register selects. Since I was implementing a 16 bit output latch, I needed only one address line, to indicate wether the high or low byte was selected.

Obviously the eight bits of data bus are needed to convey a byte to or from the FPGA.

You can see in the following picture the output latch being exercised:


The latch has 0xabcd written to it, which you can just read on the seven segments. The latch is then read back, or rather 0x9000 to 0x900f is read back. On the screen you can just about read the displayed value, which shows 0xabcd across the 16 bytes. The initial write call makes use of a nice feature of my monitor: the ability to write in 16 bit chunks. Of course this boils down to two 8 bit writes, but it is the MPU which handles this, it is not two distinct write instructions as it would be with some other 8 bit MPUs.

VHDL code for the FPGA will be given later, since I expanded on this design but left the output latch intact.

At this point I was quite pleased with myself. I'd successfully incorporated an FPGA in my little 8 bit micro. This meant that it should be possible to use one in the core of a new micro design and implement my ideas for an MMU and a DMAC!

But before moving onto the design for such a micro, I thought I would extend the breadboard a bit more and implement a buzzer sound interface.

The CPLD in the main SBC has a sound interface; a single register sets a period value for the 1 bit tone. A zero in this period register stops the tone. But I thought I could do something better, since I have more resources available in the FPGA, and add another register which sets how long the tone should play for. That way the MPU wouldn't have to silence the sound if all that was needed was to play a tone for a short time. Further, if an interrupt could be generated at the end of the tone, it would be possible to play a stream of notes entirely under interrupt control, since the interrupt handler could itself start the next note playing.

Changes to the circuit to implement this are fairly trivial: another address line is required because the FPGA now needs four addresses, the buzzer needs to be attached to an output pin with a series 1k resistor to ground. An interrupt output with a 1k pull up completes the changes.

The addressees now used (the latch is still present):
  • 0 high byte of output latch
  • 1 low byte of output latch
  • 2 the period of the tone
  • 3 the length of the tone to play
Logically, it's also possible to use the same mechanism as when generating tones to deliberately not generate a tone, since no tone is emitted for a specific duration when the period is zero. This could be utilised too, for instance, queue a delayed interrupt. It is also, of course, similar to how timers are implemented.

The full VHDL for the output latch and sound generator will be on my github account soon, but by way of explanation here is the most interesting entity, the sound generator:

entity soundplayer is
port ( nSOUNDLENGTHSET : in STD_LOGIC;
SOUNDPERIODCLK : in STD_LOGIC;
SOUNDLENGTHCLK : in STD_LOGIC;
SOUNDPERIOD : in STD_LOGIC_VECTOR (7 downto 0);
SOUNDLENGTH : in STD_LOGIC_VECTOR (7 downto 0);
SOUNDER : out STD_LOGIC;
nSOUNDINT : out STD_LOGIC);
end soundplayer;

architecture behavioral of soundplayer is
-- Period (inverse frequency) of note
signal PERIODCOUNTER : STD_LOGIC_VECTOR (7 downto 0) := x"00";
-- Length of note
signal LENGTHCOUNTER : STD_LOGIC_VECTOR (7 downto 0) := x"00";
signal LOCALSOUNDER : STD_LOGIC := '0'; -- Toggling buzzer output
signal SOUNDPLAYING : STD_LOGIC := '0'; -- State indicating sound should play
begin
process (nSOUNDLENGTHSET, SOUNDLENGTHCLK, SOUNDPERIODCLK, SOUNDPERIOD)
begin
-- If we have just written to the sound length register, then clear the length counter
-- and set the playing state
if (nSOUNDLENGTHSET = '0') then
LENGTHCOUNTER <= x"00";
SOUNDPLAYING <= '1';
-- Otherwise, if the length clock ticked, then increment the length counter
elsif (SOUNDLENGTHCLK'Event and SOUNDLENGTHCLK = '0') then
LENGTHCOUNTER <= LENGTHCOUNTER + 1;
-- Also, if we have reached the end of the note, then stop playing
if (LENGTHCOUNTER = SOUNDLENGTH) then
SOUNDPLAYING <= '0';
end if;
end if;

-- If the period clock has ticked over, we need to increment the period counter
if (SOUNDPERIODCLK'Event and SOUNDPERIODCLK = '0') then
PERIODCOUNTER <= PERIODCOUNTER + 1;
-- If we are end of the period for this note frequency, then toggle the buzzer
if (PERIODCOUNTER = SOUNDPERIOD) then
LOCALSOUNDER <= not LOCALSOUNDER;
PERIODCOUNTER <= x"00";
end if;
end if;
end process;

-- Pass the buzzer state out only if we are playing a non-zero period node
SOUNDER <= LOCALSOUNDER when (SOUNDPLAYING = '1' and SOUNDPERIOD /= x"00") else '0';
-- And flag an interrupt only if we are not playing and the length is non zero
nSOUNDINT <= '0' when (SOUNDPLAYING = '0' and SOUNDLENGTH /= x"00") else '1';

end behavioral;

Two clocks, SOUNDPERIODCLK and SOUNDLENGTHCLK drive the two counters, PERIODCOUNTER and LENGTHCOUNTER. These clocks are of course derived from the global E clock. The PERIODCOUNTER will toggle the buzzer output when it reaches the value held in the SOUNDPERIOD register. This sets the frequency of the output. Similarly, the LENGTHCOUNTER controls wether the sound should be playing (SOUNDPLAYING is 1).

A difference to how this was implemented in the CPLD exists because the sound should only play once, when the SOUNDLENGTH register is written too. This is achieved by nLENGTHSET input. This is 0 only on the cycle that the register is updated. When this happens, the SOUNDPLAYING state signal is set to 1 and the LENGTHCOUNTER is reset. An interest quirk of this behaviour is that the LENGTHCOUNTER counts up regardless of wether a sound is being played, but it will always count from 0 when the sound is playing.

Interrupts are generated (nSOUNDINT becomes 0) when the SOUNDPLAYING signal turns 0 and a length is set. Otherwise the interrupt line is high. Thus to clear the interrupt, either another note should be played (setting SOUNDPLAYING to 1) or a zero note length should be set. The current design does not include niceties like a configuration register to enable or disable interrupts, but since the CPLD design includes interrupt routing facilities, this is not a problem.

Inside the MPU, operating the buzzer is nice and trivial. Here is the assembly code for the initialisation routine, and a simple interrupt handler:

buzzerinit:     clr BUZZERTONE          ; clear the buzzer frequency...
                clr BUZZERDURATION      ; and duration registers
                clr buzzerpointer       ; clear the "next note" pointer
                clr buzzerpointer+1

                ldx #buzzerhandler      ; get the handler address
                stx handlebuzzer        ; and save it in the vector table

                ; interrupt register
                lda IRQFILTER           ; get current value
                ora #IRQBUZZER          ; or in the buzzer irq line
                sta IRQFILTER           ; for the buzzer interrupt

                rts

buzzerhandler:  pshs a,b,x              ; save our registers!
                ldx buzzerpointer       ; load the current pointer
                lda ,x+                 ; get the period
                sta BUZZERTONE          ; save it in the register
                lda ,x+                 ; get the length
                sta BUZZERDURATION      ; save it in the register
                ; a zero here will cause no next note to play
                stx buzzerpointer       ; save the moved along pointer
buzzerhandlero: puls a,b,x              ; restore the registers
                rts

To set the sequence playing the buzzerpointer global needs to be set to the address of the first note, and that note played, by writing the period and length to the registers in the FPGA so that at the end of the first note, an interrupt will fire, causing the interrupt routine to be entered and the next note played. This will continue until a zero length value is "played".

If I was musically talented I would have written out a nice sequence of notes that play a tune. I would then be able to play the tune and, crucially, keep using the monitor whist the tune played, since the notes are played under interrupt control. Unfortunately I'm not even slightly musically talented. So instead of a nice video of this feature in action, you will have to settle for... a picture.

As a simple illustration of the sound generator in operation here is a grab of the logic analyser showing the buzzer output and the interrupt pin. I set this in motion by loading memory with the following period and length sequence:

01 01 02 01 04 01 08 01 10 01 20 01 00 00



If you look closely you will see a small glitch around the end of the forth note. It appears that the buzzer output is forced to zero when the interrupt line goes low. This requires some investigation but, in general, the VHDL is doing as intended.

I think I've pretty much reached the limits of what can be done with the old SBC board coupled to my FPGA development breadboard. The next thing I will start on will be the new FPGA-incorporating main board. And, indeed, I need to also decide what kind of computer board I want to produce. I have several main choices:
  • Repeat what was done before and have a main board and an IO daughter board
  • Some kind of backplane arrangement
  • A single board incorporating all the parts
But before this can start I need to flesh out the designs for the DMA controller and MMU, something I'll do in my next post...

No comments:

Post a Comment