- 8 data bus lines
- 1 address bus line
- 1 read line
- 1 write line
- 1 chip select line
- 1 E clock line
- 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
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;
— 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:
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…