Showing posts with label Apple. Show all posts
Showing posts with label Apple. Show all posts

Tuesday, November 28, 2023

Inside the Macintosh: the memory.

Let's start from this post with a series of posts with technical "dissection" of the different areas of the Macintosh mainboard. It's very important to understand how the hardware works.

Of course, a software emulator works differently from the real hardware, and many times it's a question of implementing tricks, especially when the emulator must run on a limited system such as a microcontroller.

So I will take my time in studying components, I will read the datasheets, and I will include also the link to download them.

I usually consider the memory of the computer first. ROM and RAM are absolutely necessary so let's take a look at the motherboard.

The amount of RAM present in the Macintosh 128K is... 128KB!!! WOW, what a surprise. The amount of ROM in total is 64KB. The ROM is made of two 32KB modules, together they make a 16-bit 32K words memory bank.

The geometry of the RAM instead is different. There are sixteen modules of 64K bits of dynamic RAM. These modules store a matrix of one column by 64K rows of single bits. Eight of these modules are addressed together to store a single byte, one bit for each module. Like the ROM there are two blocks of eight modules, that together make a 16-bit 64K words memory bank.


It's easy to understand how this RAM works. Take a look at the pinout of this module. 

 

Basically, the access to the internal storage happens in two main steps, keeping the line "WriteEnable" low for writing one bit, or high for reading one bit. These steps are needed to set the address of the bit in the range 16-bit wide. So RAS and CAS are asserted low to latch the high and low bytes of the address. According to the state of the WE line a bit is put on data pin Q (read), or the bit on data pin D (write) is written in the memory.

This access mechanism is not needed in a software emulator of course, but I am here to understand how this Mac works. But if you are interested in going deeper with access timings, here's the DRAM MT4264 datasheet.

In my emulator, this kind  of memory can be implemented easily with arrays, I think something like:  
uint16_t ram[65536];
const uint16_t rom[32768] = {
   /* Dump of the ROM */
};

But I will be back on the implementation later.

Sunday, November 19, 2023

An experiment just for fun.

I knew that "Digital" would enchant me. Especially for the fact that it gives me the opportunity to virtually replicate different areas of the Macintosh circuit to perform tests and understand how it works.

Now I would like to do an experiment. In truth, I don't need to go so low level for the emulator I have to write, but I'm curious to see how far this circuit simulator goes.

I want to try to implement the 64k-bit MT4264 DRAM memory chips present in the 128K Macintosh. So I found the datasheet, extracted the text of the description, and asked ChatGPT to synthesize the chip in Verilog naturally following the specifications.

What happened is interesting.

Hi, I want to build a DRAM module in Verilog. The DRAM size is 64k bits organized in 64k * 1. This is the description of the component in the datasheet. Can you write the Verilog module for me?

"The MT4264 is a randomly accessed solid-state memory containing 65,536 bits organized in a x1 configuration. During READ or WRITE cycles, each bit is uniquely ad- dressed through the 16 address bits, which are entered 8 bits (A0-A7) at a time. RAS is used to latch the first 8 bits and CAS the latter 8 bits. A READ or WRITE cycle is selected with the WE input. A logic HIGH on WE dictates READ mode while a logic LOW on WE dictates WRITE mode. During a WRITE cycle, data in (D) is latched by the falling edge of WE or CAS, whichever occurs last. If WE goes LOW prior to CAS going LOW, the output pin(s) remain open (High-Z) until the next CAS cycle. If WE goes LOW after data reaches the output pin(s), data out (Q) is activated and retains the selected cell data as long as CAS remains LOW (regardless of WE or RAS). This late WE pulse results in a READ-WRITE cycle. PAGE MODE operations allow faster data operations (READ, WRITE or READ-MODIFY-WRITE) within a row address (A0-A7) defined page boundary. The PAGE MODE cycle is always initiated with a row address strobed-in by RAS followed by a column address strobed-in by CAS. CAS may be toggled-in by holding RAS LOW and strobing- in different column addresses, thus executing faster memory cycles. Returning RAS HIGH terminates the PAGE MODE operation. Returning RAS and CAS HIGH terminates a memory cycle and decreases chip current to a reduced standby level. Also, the chip is preconditioned for the next cycle during the RAS HIGH time. Memory cell data is retained in its correct state by maintaining power and executing any RAS cycle (READ, WRITE, RAS-ONLY or HIDDEN RE- FRESH) so that all 256 combinations of RAS addresses (A0- A7) are executed at least every 4ms, regardless of sequence"

Creating a Verilog module for a DRAM such as the MT4264 involves simulating the behavior of the DRAM as described in your datasheet. In this simulation, you'll model the address latching with RAS and CAS, the READ and WRITE operations controlled by the WE signal, the data input and output handling, PAGE MODE operations, and the necessary timing for refresh cycles.

Here's a basic structure for such a module. This code is a simplified representation and may need adjustments based on the specific requirements of your system, such as timing constraints and interface details.

module MT4264 (
    input wire clk,        // System clock
    input wire rst,        // System reset
    input wire [7:0] addr, // 8-bit address input
    input wire ras,        // Row Address Strobe
    input wire cas,        // Column Address Strobe
    input wire we,         // Write Enable
    inout wire data        // Data line (bidirectional)
);

    // Memory array
    reg [0:0] memory [0:65535];
    reg [15:0] full_address;
    reg data_out;
    wire data_in;

    // Bidirectional data line
    assign data = (!we && !cas) ? data_out : 1'bz;
    assign data_in = data;

    // Latching addresses
    always @(posedge clk or posedge rst) begin
        if (rst) begin
            full_address <= 16'd0;
        end else begin
            if (!ras) full_address[15:8] <= addr; // Latch row address
            if (!cas) full_address[7:0] <= addr;  // Latch column address
        end
    end

    // Read/Write operations
    always @(posedge clk) begin
        if (!cas && !we) begin
            // Write operation
            memory[full_address] <= data_in;
        end else if (!cas && we) begin
            // Read operation
            data_out <= memory[full_address];
        end
    end

    // Refresh logic (simplified)
    // In real applications, refresh logic must cycle through all row addresses
    // within the required refresh interval (e.g., every 4ms).

endmodule

This module covers the basic address latching with RAS and CAS, and the READ/WRITE operations based on the WE signal. The "memory" array represents the DRAM cells. The actual refresh logic and PAGE MODE operations are not implemented in this example and would need to be added based on your specific requirements.

Remember that this is a simplified model and may not accurately represent all nuances of the real DRAM behavior, especially with regard to timing constraints and electrical characteristics. It's suitable for simulation purposes but might need adjustments for use in real hardware or more detailed simulations.

The data signal is not bidirectional. Can you split it into signal D as input data, and Q as output data.

Certainly, splitting the data signal into separate input and output lines makes sense for clarity and alignment with typical DRAM module designs. Here's the revised Verilog module with "D" as the input data signal and "Q" as the output data signal:

module MT4264 (
    input wire clk,        // System clock
    input wire rst,        // System reset
    input wire [7:0] addr, // 8-bit address input
    input wire ras,        // Row Address Strobe
    input wire cas,        // Column Address Strobe
    input wire we,         // Write Enable
    input wire d,          // Data input
    output reg q           // Data output
);

    // Memory array
    reg [0:0] memory [0:65535];
    reg [15:0] full_address;

    // Latching addresses
    always @(posedge clk or posedge rst) begin
        if (rst) begin
            full_address <= 16'd0;
        end else begin
            if (!ras) full_address[15:8] <= addr; // Latch row address
            if (!cas) full_address[7:0] <= addr;  // Latch column address
        end
    end

    // Read/Write operations
    always @(posedge clk) begin
        if (!cas && !we) begin
            // Write operation
            memory[full_address] <= d;
        end else if (!cas && we) begin
            // Read operation
            q <= memory[full_address];
        end
    end

    // Initialize the output
    initial begin
        q = 1'b0;
    end

    // Refresh logic (simplified)
    // In real applications, refresh logic must cycle through all row addresses
    // within the required refresh interval (e.g., every 4ms).

endmodule

In this version, "D" is used to write data into the memory array during write operations, and "Q" is used to hold the data read from the memory array during read operations. This separation aligns better with the actual DRAM interface, where data input and output are typically on separate lines.

Again, keep in mind that this is a basic model intended for simulation purposes and does not include all aspects of a real DRAM, such as detailed timing behavior, PAGE MODE operations, and comprehensive refresh logic.

by the way, signals RAS, CAS and WE are active low.

Thank you for clarifying that RAS, CAS, and WE are active-low signals. In Verilog, this means we should treat these signals as being active or 'asserted' when they are at a logical low level ("0"). I'll modify the code to reflect this behavior:

module MT4264 (
    input wire clk,        // System clock
    input wire rst,        // System reset
    input wire [7:0] addr, // 8-bit address input
    input wire ras_n,      // Row Address Strobe (active low)
    input wire cas_n,      // Column Address Strobe (active low)
    input wire we_n,       // Write Enable (active low)
    input wire d,          // Data input
    output reg q           // Data output
);

    // Memory array
    reg [0:0] memory [0:65535];
    reg [15:0] full_address;

    // Latching addresses
    always @(posedge clk or posedge rst) begin
        if (rst) begin
            full_address <= 16'd0;
        end else begin
            if (ras_n == 1'b0) full_address[15:8] <= addr; // Latch row address
            if (cas_n == 1'b0) full_address[7:0] <= addr;  // Latch column address
        end
    end

    // Read/Write operations
    always @(posedge clk) begin
        if (cas_n == 1'b0 && we_n == 1'b0) begin
            // Write operation
            memory[full_address] <= d;
        end else if (cas_n == 1'b0 && we_n == 1'b1) begin
            // Read operation
            q <= memory[full_address];
        end
    end

    // Initialize the output-
    initial begin
        q = 1'b0;
    end

    // Refresh logic (simplified)
    // In real applications, refresh logic must cycle through all row addresses
    // within the required refresh interval (e.g., every 4ms).

endmodule

In this revised module, "ras_n", "cas_n", and "we_n" are treated as active-low signals. The code checks for "0" (low) to determine when these signals are active. This approach better matches the behavior described in the datasheet and is common in hardware design where active-low control signals are used.

Remember, this is still a basic simulation model. For practical hardware implementation, you'll need to consider the specific timing requirements and characteristics of the DRAM, which are not fully captured in this simple model.

Another thing, this DRAM is asynchronous so the clock signal is not necessary.

You're absolutely correct. Asynchronous DRAMs, like the one described in your datasheet, do not use a system clock signal for their operation. Their actions are controlled entirely by the RAS, CAS, and WE signals, along with the address inputs. I'll revise the Verilog module to reflect this asynchronous behavior:

module MT4264 (
    input wire rst,        // System reset
    input wire [7:0] addr, // 8-bit address input
    input wire ras_n,      // Row Address Strobe (active low)
    input wire cas_n,      // Column Address Strobe (active low)
    input wire we_n,       // Write Enable (active low)
    input wire d,          // Data input
    output reg q           // Data output
);

    // Memory array
    reg [0:0] memory [0:65535];
    reg [15:0] full_address;
    reg [7:0] row_address;
    reg [7:0] col_address;

    // Latching addresses on the falling edge of RAS and CAS
    always @(negedge ras_n or negedge cas_n) begin
        if (!ras_n) row_address <= addr; // Latch row address
        if (!cas_n) col_address <= addr; // Latch column address
    end

    // Combine row and column addresses
    always @(*) begin
        full_address = {row_address, col_address};
    end

    // Read/Write operations
    always @(*) begin
        if (!cas_n) begin
            if (!we_n) begin
                // Write operation
                memory[full_address] <= d;
            end else begin
                // Read operation
                q <= memory[full_address];
            end
        end
    end

    // Initialize the output
    initial begin
        q = 1'b0;
    end

    // Refresh logic (simplified)
    // In real applications, refresh logic must cycle through all row addresses
    // within the required refresh interval (e.g., every 4ms).

endmodule

In this version, the "clk" input has been removed, and the operations are triggered by changes in the RAS and CAS signals (specifically their falling edges, which is typical for active-low control signals in asynchronous memory modules).

Since there's no clock signal, the operations are now handled by combinational logic ("always @(*)"). However, do note that this might lead to more power consumption in simulation as changes to the address or control lines would immediately reflect in the logic.

As always, this module is a simplified model and does not include specific timing behavior, detailed PAGE MODE operations, or comprehensive refresh logic, which are important in a real-world implementation.

the last thing, the original component doesn't have the reset signal.

Understood. If the original DRAM component, such as the MT4264, does not include a reset signal, we should remove it from the Verilog module to accurately reflect its functionality. Here's the revised code without the reset signal:

module MT4264 (
    input wire [7:0] addr, // 8-bit address input
    input wire ras_n,      // Row Address Strobe (active low)
    input wire cas_n,      // Column Address Strobe (active low)
    input wire we_n,       // Write Enable (active low)
    input wire d,          // Data input
    output reg q           // Data output
);

    // Memory array
    reg [0:0] memory [0:65535];
    reg [7:0] row_address;
    reg [7:0] col_address;

    // Latching addresses on the falling edge of RAS and CAS
    always @(negedge ras_n or negedge cas_n) begin
        if (!ras_n) row_address <= addr; // Latch row address
        if (!cas_n) col_address <= addr; // Latch column address
    end

    // Combine row and column addresses
    always @(*) begin
        // Combine the latched row and column addresses to form the full address
        full_address = {row_address, col_address};
    end

    // Read/Write operations
    always @(*) begin
        if (!cas_n) begin
            if (!we_n) begin
                // Write operation
                memory[full_address] <= d;
            end else begin
                // Read operation
                q <= memory[full_address];
            end
        end
    end

    // Initialize the output
    initial begin
        q = 1'b0;
    end

    // Refresh logic (simplified)
    // In real applications, refresh logic must cycle through all row addresses
    // within the required refresh interval (e.g., every 4ms).

endmodule

In this version, the module no longer includes a "rst" (reset) input. The operations are controlled entirely by the address, RAS, CAS, and WE inputs. The memory is latched based on the state of the RAS and CAS signals, and read/write operations are performed accordingly.

Keep in mind that this is a basic model for simulation and conceptual understanding. Real-world DRAM behavior, especially concerning timing and refresh cycles, is more complex and would require additional logic and considerations in an accurate simulation or hardware implementation.

Uhm, I think you missed the full_address register

You're absolutely right. I apologize for that oversight. The "full_address" register is crucial for combining the row and column addresses. Let me correct that in the Verilog code:

module MT4264 (
    input wire [7:0] addr, // 8-bit address input
    input wire ras_n,      // Row Address Strobe (active low)
    input wire cas_n,      // Column Address Strobe (active low)
    input wire we_n,       // Write Enable (active low)
    input wire d,          // Data input
    output reg q           // Data output
);

    // Memory array
    reg [0:0] memory [0:65535];
    reg [7:0] row_address;
    reg [7:0] col_address;
    reg [15:0] full_address; // Combined row and column address

    // Latching addresses on the falling edge of RAS and CAS
    always @(negedge ras_n or negedge cas_n) begin
        if (!ras_n) row_address <= addr; // Latch row address
        if (!cas_n) col_address <= addr; // Latch column address
    end

    // Combine row and column addresses
    always @(*) begin
        full_address = {row_address, col_address};
    end

    // Read/Write operations
    always @(*) begin
        if (!cas_n) begin
            if (!we_n) begin
                // Write operation
                memory[full_address] <= d;
            end else begin
                // Read operation
                q <= memory[full_address];
            end
        end
    end

    // Initialize the output
    initial begin
        q = 1'b0;
    end

    // Refresh logic (simplified)
    // In real applications, refresh logic must cycle through all row addresses
    // within the required refresh interval (e.g., every 4ms).

endmodule

In this corrected version, "full_address" is correctly declared and used to combine the row and column addresses. This register is essential for addressing the correct memory cell within the 64K-bit array. Thank you for pointing out the mistake!

Remember that the A.I. it's not infallible, it's a risk to ask and copy/paste. The best intelligence is the one between the two ears!

Anyway, I tested the component in Verilog.

Unfortunately, by creating an array of 16 MT4264 DRAMs, the application struggles a bit in the initial compilation of the component. 

So, since it is not necessary at the moment, I will create two 64K*8bit memories using the native components which simulate a typical access of a dynamic RAM. 

Saturday, November 18, 2023

Bricks for this project, really?

I don't know if it's happened to you too, but when I study electronic diagrams it makes me want to "reconstruct" the circuit to see it work. It's as if your head starts to go faster in imagining that you already have all the components at hand, ready to be inserted into their respective sockets on the PCB.

It is also frustrating to note that the transition from a scheme to a prototype is not so immediate, especially in my current condition, where handling components, wires, and soldering iron has become a  little difficult. 

The "luck" is that I don't want to build such a complex prototype because I will use a development board on which an emulator will run.

Now I'm "only" studying how the components interact, so a digital electronic circuit simulator might be enough. But what do bricks have to do with it? Well, logic gates are the building blocks of computers, and having a tool that allows you to grow assemblies of logic gates and see them in action can be essential for reverse engineering.

After several days of searching, I found him, "Digital", fantastic. The author's page reads: "Digital is an easy-to-use digital logic designer and circuit simulator designed for educational purposes." Educational says. In my opinion, it is much, much more!


In addition to being simple and intuitive to use, it is very powerful and feature-rich. The thing I love is the ability to create custom components using VHDL or Verilog, but not only that, but you can also see them in action in the simulation. 

 But what struck me most? What if I say VGA? What? But of course, there is a component that is able to function as a VGA monitor, not in real time (yet) but manages to hook HSync, VSync, and RGB signals with pixel clocks available in many resolutions.


A fire lit up inside me, I forgot for a while my never-ending headache, and like a rocket I began to design the video circuit of the Macintosh, modifying the standard 640*480 VGA signal to display the resolution of 512*342, using only 16 pixels in repetition.

Crazy!

Needless to say, I will use it a lot, and I will reveal many surprises to you! Check this out!

https://github.com/hneemann/Digital

Thursday, November 2, 2023

Code sharing, such a great idea.

Difficulties can't stop me, maybe they delay my results but my strong will helps me to find out a way to get things done.

I had to stop for a couple of days, I had to go through several things, but I got an idea for this blog, and some enthusiasm made me get back on this track.

What if I can show you the working source code or the entire emulator? Wouldn't it be great to show directly inside a post a working part of the code? That's a real possibility with Replit.

For now, as an example, I opened a public Replit project where I will save demos shown here. Why Replit? I discovered that supports the use of the SDL library. That means GRAPHICS!! Moreover, it's possible to embed a project inside an <iframe> tag.

Check the "Code development" page!

Sunday, October 29, 2023

Getting the best basis of knowledge.

After a few days of searching for information about the video signal generated by the Macintosh 128K, I found a PDF of a book about this computer, aimed at developers.

Actually, I have found several books, but only this one showed me the complete timings of a video frame generated by the Macintosh.

This book is what I need for my adventure into coding this emulator. On page 25 of the PDF, in the section "III-18 Over-view of the Hardware", I have found the description of the video interface. 

Here it is explained exactly how a video frame is composed, and the timings of the video signal.

Apple "Inside Macintosh Volume III"

Now, since I can ask ChatGPT to read a PDF for me and then I can ask him what I need, I activate the plugin "AskYourPDF" within a new chat v.4.x

Then I uploaded that PDF in my personal FTP space so that I have full control of the access restrictions.

I am ready now to give the link to ChatGPT:

Here's the PDF about the Macintosh 128k: http://www.x.com/chatgpt/Inside_Macintosh_Volume_III_1985.pdf

The document "Inside Macintosh Volume III" has been successfully uploaded. It appears to be a detailed manual from Apple, published in 1985, covering various aspects of the Macintosh systems at the time. The manual seems to be quite comprehensive, with a total of 290 pages, and it likely contains technical information, guidelines, and instructions related to the Macintosh computers of that era.

You can now ask specific questions about the content of this manual, or if you need information on particular topics within the document, feel free to ask!

Now ChatGPT should use this PDF to answer me.

Please, show me the timings of the video signal generated by the Mac.

The Macintosh 128K uses a specific video scanning pattern to generate its display. Here's a detailed breakdown of the video signal timings:

1. Full-Screen Display: The entire screen display consists of 342 horizontal scan lines. Each full screen takes approximately 15.37 milliseconds (ms) or 15367.65 microseconds (µs) to be scanned. This duration includes the drawing of all horizontal lines across the screen.

2. Vertical Blanking Interval: After completing the last horizontal scan line, there's a short period when all scanning stops to allow the electron beam to return to the top of the screen and start the new screen scan cycle. This interval is called the "vertical blanking interval," taking up additional time equivalent to 28 scan lines, which is approximately 1.26 ms or 1258.17 µs.

3. Full-Screen Refresh Rate: Considering the time taken for both the full-screen display and the vertical blanking interval, the entire screen is redisplayed once every 16.6 ms or 16625.8 µs. This calculation sets the vertical scan rate (the frequency of full-screen refresh) at approximately 60.15 Hertz.

4. Video Memory Usage: The video generator utilizes 21,888 bytes of RAM to compose a bit-mapped video image that is 512 pixels wide by 342 pixels tall. Each bit in this memory range corresponds to a single pixel on the screen, with a 0 representing a white pixel and a 1 representing a black pixel.

5. Screen Buffers: There are two main screen buffers in the memory from which the video circuitry reads information to create the screen display. The starting addresses of these buffers depend on the total memory available in the Macintosh system.

The detailed timing and organization of the video signal are crucial for the correct rendering of the screen display, ensuring that the visuals appear as intended and that the system synchronizes the screen refresh cycles with the internal operations, providing a stable and consistent user experience.

For any modifications or specific operations related to the video display, developers and users were advised to retrieve the video base address and bit mappings directly from the system to ensure compatibility and stability, especially considering future system upgrades or variations in Macintosh models.

This is INSANE! Absolutely amazing.