MIDI Controlled Sample-Based Synthesizer

Amir Hirsch and Andrew Leiserson


Overview  |  Communication  |  Master Control Unit  |  Sound Generation Unit  |  Mixer

MIDI and PS/2 interfaces:

The MIDI and PS/2 interfaces are split into two modules each. One module receives the serial data from the external hardware and converts it to 8-bit format. These USART modules present a 8-bit rx_hold signal and data_avail and error outputs to the decoder modules. The decoder sends a rx_reset signal to the USART to indicate it has received the data and clear the holding register.

The decoder modules contain internal FSMs to decode the data received. The keyboard FSM must simply track whether flag codes have been received. The flags are the break scancode, F0h, indicating that the next scancode indicates a key being released, and the extended scancode, E0h, indicating the next scancode is for one of the extended keys. Either or both of these flags can be set. When a key is received, the flags are reset. The keyboard decoder passes a bit-vector of event signals to the control FSM using constants defined in key_defs.v.

The MIDI decoder operates in a similar fashion, but must keep track of a more complicated protocol. The MIDI protocol allows a "running status" feature to reduce the amount of data that must be sent. If no status byte is received, the equipment is to assume that the most recent status byte still applies. The MIDI FSM checks whether a byte is a status byte and latches it into a holding register if it is. The FSM also tracks whether it is receiving a 1-byte or 2-byte message and doesn't signal the control FSM until a complete message has been received. The MIDI decoder also signals the control FSM via a bit-vector of event flags, but because the control FSM must look at the actual MIDI data to send signals to the SGU, the MIDI message is made available to the control FSM in a buffer.

Display interfaces:

Although the text output capabilities of the display were not used by the synthesizer user interface, full text output capabilities were implemented and a test message could be displayed by using the keyboard. The display controller is composed of two sections. The smaller section is the interface to the control bus which receives events and modifies the 16-byte memory holding characters to show on the display. This section accepts two messages, a "Disply Message" signal that prints an entire text string from a message ROM on the display and a "Display Character" message that changes a single character on the display, for example to update a numeric output.

The second component of the display interface is the display output module. The display output module operates either from the character memory or from a 64-bit bus that is displayed as 16 hexadecimal characters. The output module uses a font ROM to obtain bitmaps for the display. In character mode, the display character memory selects which character is retrieved from ROM, and the full 128-character set of Agilent Application Brief D-003 is accessibly. In bus mode, four bits of the bus are used to select 0-9 or A-F for the display.

Each character is a 5x7 bitmap, stored as 5 bytes with one bit in each byte unused. The display output FSM controls the loading of each of the 5 bytes from the font ROM and sending them to the display. In addition, upon power up the output module sends a control word to the display to configure the display brightness and take the display out of shutdown mode.

Data is sent to the display in a synchronous-serial fashion. The display uses shift registers internally, so the clock need not be uniform. Sample timing waveforms can be found in the datasheet for the display (Agilent HCMS-2973). Note that the 2973 is composed of two 4-character segments wired together, and the labkit combines two of the displays for a total of four 4-character segments. The onboard display controller ASIC passes outgoing serial data to the next ASIC so that multiple displays may be controlled simply by sending a longer serial data stream.

AC97 Controller:

The AC97 Controller sends and receives audio from the AC97-Compatible LM4550 Audio Codec on the lab kit. The LM4550 contains stereo D/A and A/D converters and an analog mixer. Targetted for computer applications, the AC97 standard minimizes external circuitry by using a serial protocol for data transmission between codec and controller. Data is sent and received from the LM4550 in 256-bit frames. There are 48000 of these frames every second, for a net serial data rate of 12.288 MHz. Outgoing frames contain commands to write to registers on the LM4550 or request a register read. Outgoing frames also contain data for the D/A. Incoming frames return data read from a register and contain data from the A/D.

The serial clock signal is generated by the codec when it is not in reset. However, a SYNC signal is generated by the controller to signal the beginning of a frame. The SYNC signal is high for 16 of the 256 bits in the frame.

After reset, the AC97 controller configures the registers on the AC97 to allow audio input and output. It would be possible to put the AC97 controller on the control bus and update the configuration of the codec during operation, but this is unnecessary. When line-level input signals are used, the A/D gain should not need to change, and the system mixer eliminates entirely the need for D/A gain adjustments. The most likely need for AC97 register updates would be to dynamically select between a microphone and line in recording source.

ZBT SRAM Interface:

The ZBT SRAM is very easy to use once the clocks are configured properly. Because of the time it takes for clock signals to be driven from the FPGA to the RAM, it is possible to violate hold time constraints. To fix this, Xilinx DCMs are used to deskew the clock. The DCM uses a feedback to see the clock as seen by the SRAM. The DLL sends a clock ouput to the SRAM that is slightly ahead of the system clock. The clock edge will reach the SRAM at the same time that the system clock rises on the FPGA.

The code used to instantiate the DCM is shown below. The "clk" signal is used to clock internal modules. Two versions of the code are shown. The first version is slightly more complicated because the DCMs are configured to divide the clock by 5. The second version runs the memory at the full 27 MHz system clock. Schematics for both connections are available by clicking on the respective links: Clock divison and Full speed.

----------------------------------------------------------------------
wire clock_27mhz_dcm;
IBUFG clk27_ibufg(.I(clock_27mhz), .O(clock_27mhz_dcm));
BUFG clk_bufg(.I(clk_dcm), .O(clk));
wire dcm1_clk0;
DCM dcm1(
	.CLKIN(clock_27mhz_dcm),
	.CLKDV(clk_dcm),
	.CLK0(dcm1_clk0),
	.CLKFB(dcm1_clk0)
);
// synthesis attribute CLKDV_DIVIDE of dcm1 is 5;

wire ram0_clk_dcm, clock_feedback_in_dcm, ram0_clk_fb_out_dcm;
OBUF_F_8 clk_feedback_obuf(
	.I(ram0_clk_fb_out_dcm),
	.O(clock_feedback_out)
);
OBUF_F_8 ram0_clk_obuf(.I(ram0_clk_dcm), .O(ram0_clk));
IBUFG clock_feedback_ibufg(.I(clock_feedback_in), 
		.O(clock_feedback_in_dcm));

DCM dcm2(
	.CLKIN(clock_27mhz_dcm),
	.CLK0(ram0_clk_fb_out_dcm),
	.CLKDV(ram0_clk_dcm),
	.CLKFB(clock_feedback_in_dcm)
);
// synthesis attribute CLKDV_DIVIDE of dcm2 is 5;
----------------------------------------------------------------------

** DCM Instantiation, Full Clock Speed

----------------------------------------------------------------------
wire clock_27mhz_dcm;
IBUFG clk27_ibufg(.I(clock_27mhz), .O(clock_27mhz_dcm));
BUFG clk_bufg(.I(clk_dcm), .O(clk));
wire dcm1_clk0;
DCM dcm1(
	.CLKIN(clock_27mhz_dcm),
	.CLK0(clk_dcm),
	.CLKFB(clk)
);

wire ram0_clk_dcm, clock_feedback_in_dcm, ram0_clk_fb_out_dcm;
OBUF_F_8 clk_feedback_obuf(
	.I(ram0_clk_dcm),
	.O(clock_feedback_out)
);
OBUF_F_8 ram0_clk_obuf(.I(ram0_clk_dcm), .O(ram0_clk));
IBUFG clock_feedback_ibufg(.I(clock_feedback_in), 
		.O(clock_feedback_in_dcm));

DCM dcm2(
	.CLKIN(clock_27mhz_dcm),
	.CLK0(ram0_clk_dcm),
	.CLKFB(clock_feedback_in_dcm)
);
----------------------------------------------------------------------
Master FSM:

The master FSM receives events and sends control signals to the reset of the system. Keyboard events are processed as they occur. Typically in response to a keyboard event the master control FSM updates some internal value such as mixer level, filter parameters, or record sample selector. The master FSM may also trigger the record/play controller in response to a keyboard event.

MIDI events can also update internal values. MIDI can control filter cutoff and resonance and mixer levels. In addition, the master FSM must respond to MIDI note events. MIDI events are handled only after the sample_heartbeat signal from the AC97 indicates the sample period has elapsed, to ensure that the SGU is ready for any messages it will be sent. Most of the Master FSM states are used by sending messages to the SGU or display controller. After any sample period, the master controller will send the SGU the DUMP AUDIO command to request SGU output to the audio bus. After monitoring the status of the audio dump via the audio_busy signal, the master FSM will send NOTE ON or NOTE OFF messages to the SGU as applicable. Finally, the controller will send a START PROCESSING message to the SGU to indicate that all control messages have been sent and the SGU should begin computing a new audio sample.