URL
https://opencores.org/ocsvn/mcs-4/mcs-4/trunk
Subversion Repositories mcs-4
Compare Revisions
- This comparison shows the changes necessary to convert path
/mcs-4/trunk/rtl/verilog/i4002
- from Rev 5 to Rev 6
- ↔ Reverse comparison
Rev 5 → Rev 6
/i4002.v
0,0 → 1,266
`timescale 1ns / 1ps |
`default_nettype none |
//////////////////////////////////////////////////////////////////////// |
// |
// MCS-4 i40021 RAM and Output Port module |
// |
// This module emulates the Intel 4002 RAM and Output Port chip. The |
// RAM storage is implemented using an "i4002_ram" module, which is |
// dependent on the availability of dual-port distributed RAM. |
// |
// This file is part of the MCS-4 project hosted at OpenCores: |
// http://www.opencores.org/cores/mcs-4/ |
// |
// Copyright © 2021 by Reece Pollack <rrpollack@opencores.org> |
// |
// These materials are provided under the Creative Commons |
// "Attribution-NonCommercial-ShareAlike" (CC BY-NC-SA) Public License. |
// They are NOT "public domain", and are protected by copyright. |
// |
// This work based on materials provided by Intel Corporation and |
// others under the same license. See the file doc/License for |
// details of this license. |
// |
//////////////////////////////////////////////////////////////////////// |
|
module i4002 #( |
parameter CHIP_NUMBER = 0, // Mask and P0 config |
parameter RAM_ARRAY_SIZE = 32 // Size of the RAM array |
) ( |
input wire sysclk, // 20 MHz oscillator input |
// MCS-4 system bus interface |
input wire clk1, // MCS-4 Phase 1 clock |
input wire clk2, // MCS-4 Phase 2 clock |
input wire sync, // MCS-4 Phase synchronization |
input wire reset, // MCS-4 Synchronous reset |
input wire cm, // MCS-4 Command Line (bank select) |
inout tri [3:0] data, // MCS-4 bidirectional data bus |
|
output reg [3:0] oport, // i4002 Output port |
|
// Ram array dual-port interface |
input wire [4:0] ram0_addr2, |
output wire [3:0] ram0_data2_out, |
input wire [4:0] ram1_addr2, |
output wire [3:0] ram1_data2_out, |
input wire [4:0] ram2_addr2, |
output wire [3:0] ram2_data2_out, |
input wire [4:0] ram3_addr2, |
output wire [3:0] ram3_data2_out |
); |
|
// |
// Recover the cycle timing |
// |
wire a12, m12, m22, x22, x32; |
timing_recovery timing_recovery ( |
.sysclk (sysclk), |
.clk1 (clk1), |
.clk2 (clk2), |
.sync (sync), |
.a12 (a12), |
.m12 (m12), |
.m22 (m22), |
.x22 (x22), |
.x32 (x32) |
); |
|
// Capture OPA during the M2 subcycle |
reg io; |
reg [3:0] opa; |
always @(posedge sysclk) begin |
if (reset) begin |
io = 1'b0; |
opa = 4'b0000; |
end |
else begin |
if (clk2 & m22) begin |
io = cm; |
opa = data; |
end |
end |
end |
|
// Decode I/O type operations |
wire wrm = io & (opa == 4'b0000); |
wire wmp = io & (opa == 4'b0001); |
wire wrx = io & (opa[3:2] == 2'b01); |
wire rdm = io & (opa[3:2] == 2'b10) & (opa[1:0] != 2'b10); |
wire rdx = io & (opa[3:2] == 2'b11); |
|
// Capture the SRC address during the X22/X32 subcycles |
reg ram_sel = 1'b0; |
reg src_ram_sel = 1'b0; |
reg [1:0] reg_num = 2'b00; |
reg [3:0] char_num = 4'b0000; |
always @(posedge sysclk) begin |
if (reset) begin |
ram_sel = 1'b0; |
src_ram_sel = 1'b0; |
reg_num = 2'b00; |
char_num = 4'b0000; |
end |
else begin |
if (cm & x22 & clk2) begin |
ram_sel = (data[3:2] == CHIP_NUMBER); |
src_ram_sel = ram_sel; |
reg_num = data[1:0]; |
end |
if (clk2 & x32 & src_ram_sel) begin |
char_num = data; |
end |
if (a12) begin |
src_ram_sel = 1'b0; |
end |
end |
end |
|
// Decode the register address |
wire [4:0] reg_addr = opa[2] ? {3'b100, opa[1:0]} : |
{1'b0, char_num}; |
wire reg_write = ram_sel & clk2 & x22 & (wrm | wrx); |
wire reg0_write = reg_write & (reg_num == 2'b00); |
wire reg1_write = reg_write & (reg_num == 2'b01); |
wire reg2_write = reg_write & (reg_num == 2'b10); |
wire reg3_write = reg_write & (reg_num == 2'b11); |
|
// Latch the output port value |
always @(posedge sysclk) begin |
if (reset) begin |
oport = 4'b0000; |
end |
else if (ram_sel) begin |
if (clk2 & x22 & wmp) |
oport = data; |
end |
end |
|
// |
// In a real i4002, the RAM array is refreshed as follows: |
// 1) During the M11 subcycle, CLK1 causes all column sense |
// lines to be precharged to a "high" state. |
// 2) During the M12 subcycle, the refresh row counter selects |
// a row to be refreshed. CLK2 causes the selected row to |
// be read onto the column sense lines. |
// 3) During the M22 subcycle, the selected row is rewritten |
// with the data read during the M12 subcycle. |
// |
// The refresh row counter is 5 bits wide, and counts from 0x1f |
// down to 0x00 before rolling over. The upper bit determines |
// whether a "main memory" or "status" row is selected. Since |
// there are 16 "main memory" rows but only four status rows, |
// the status rows are refreshed four times per refresh cycle. |
// |
// The RAM array is written using a similar sequence: |
// 1) During the X11 subcycle, CLK1 causes all column sense |
// lines to be precharged to a "high" state. |
// 2) During the X12 subcycle, a row is selected based on the |
// most recent SRC command or the low two bits of OPA for |
// status register reads and writes. CLK2 causes the |
// selected row to be read onto the column sense lines. |
// 3) During the X21 subcycle, if the current operation is a |
// read, the data from the selected register is gated onto |
// the data bus. |
// 4) During the X22 subcycle, if the current operation is a |
// write, the selected row is written. The previously |
// selected register receives the data from the data bus, |
// while the other registers in the row receive the data |
// read during the X12 subcycle. |
// |
// When the RESET line is asserted, the RAM row read operations |
// are inhibited. This causes the refresh operations to write |
// zeros into the RAM. Also inhibited are the data bus gate |
// signals, preventing data bus values from being written during |
// any erroneous write operations. |
// |
reg [4:0] rfsh_addr = 5'd0; |
reg [4:0] rfsh_next = 5'd0; |
always @(posedge sysclk) begin |
if (m12) |
rfsh_addr <= rfsh_next; |
if (m22) |
rfsh_next <= rfsh_addr + 1'd1; |
end |
|
// |
// Mux the RAM's write port signals |
// |
wire [4:0] ram_addr = reset ? rfsh_addr : reg_addr; |
wire [3:0] ram_data_out = reset ? 4'h0 : data; |
wire ram0_write = reset ? 1'b1 : reg0_write; |
wire ram1_write = reset ? 1'b1 : reg1_write; |
wire ram2_write = reset ? 1'b1 : reg2_write; |
wire ram3_write = reset ? 1'b1 : reg3_write; |
|
// Select the correct RAM output |
wire [3:0] ram0_data_in; |
wire [3:0] ram1_data_in; |
wire [3:0] ram2_data_in; |
wire [3:0] ram3_data_in; |
reg [3:0] reg_data_in; |
always @(*) begin |
case (reg_num) |
2'b00 : reg_data_in = ram0_data_in; |
2'b01 : reg_data_in = ram1_data_in; |
2'b10 : reg_data_in = ram2_data_in; |
2'b11 : reg_data_in = ram3_data_in; |
endcase |
end |
|
wire reg_read = ram_sel & x22 & (rdm | rdx); |
assign data = reg_read ? reg_data_in : 4'bzzzz; |
|
|
// Instantiate RAM0 array |
i4002_ram #( |
.RAM_ARRAY_SIZE (RAM_ARRAY_SIZE) |
) ram0 ( |
.sysclk (sysclk), |
.addr (ram_addr), |
.write (ram0_write), |
.data_in (ram_data_out), |
.data_out (ram0_data_in), |
.addr2 (ram0_addr2), |
.data2_out (ram0_data2_out) |
); |
|
// Instantiate RAM1 array |
i4002_ram #( |
.RAM_ARRAY_SIZE (RAM_ARRAY_SIZE) |
) ram1 ( |
.sysclk (sysclk), |
.addr (ram_addr), |
.write (ram1_write), |
.data_in (ram_data_out), |
.data_out (ram1_data_in), |
.addr2 (ram1_addr2), |
.data2_out (ram1_data2_out) |
); |
|
// Instantiate RAM2 array |
i4002_ram #( |
.RAM_ARRAY_SIZE (RAM_ARRAY_SIZE) |
) ram2 ( |
.sysclk (sysclk), |
.addr (ram_addr), |
.write (ram2_write), |
.data_in (ram_data_out), |
.data_out (ram2_data_in), |
.addr2 (ram2_addr2), |
.data2_out (ram2_data2_out) |
); |
|
// Instantiate RAM3 array |
i4002_ram #( |
.RAM_ARRAY_SIZE (RAM_ARRAY_SIZE) |
) ram3 ( |
.sysclk (sysclk), |
.addr (ram_addr), |
.write (ram3_write), |
.data_in (ram_data_out), |
.data_out (ram3_data_in), |
.addr2 (ram3_addr2), |
.data2_out (ram3_data2_out) |
); |
|
endmodule |
/i4002_ram.v
0,0 → 1,74
`timescale 1ns / 1ps |
`default_nettype none |
//////////////////////////////////////////////////////////////////////// |
// |
// MCS-4 i4002 RAM storage |
// |
// This module defines the RAM allocated to a single i4002 register, |
// containing 16x4-bit "main" memory array and a 4x4-bit "status" |
// array. An i4002 RAM chip contains four of these RAM register modules. |
// |
// This implementation allocates a single 20x4-bit dual-port |
// RAM: ram_array. The "main" memory array is represented by the |
// elements [0:15] of ram_array, while the "status" array is |
// represented by elements [16:19]. |
// |
// This module defines a dual-port array to allow the VFD driver access |
// to the "Working Register", WR, which is stored in RAM0 register 1. |
// The synthesis tools should recognize theinstantiations that do not |
// need to be dual-port and trim the unneeded logic and storage. |
// |
// This file is part of the MCS-4 project hosted at OpenCores: |
// http://www.opencores.org/cores/mcs-4/ |
// |
// Copyright © 2021 by Reece Pollack <rrpollack@opencores.org> |
// |
// These materials are provided under the Creative Commons |
// "Attribution-NonCommercial-ShareAlike" (CC BY-NC-SA) Public License. |
// They are NOT "public domain", and are protected by copyright. |
// |
// This work based on materials provided by Intel Corporation and |
// others under the same license. See the file doc/License for |
// details of this license. |
// |
//////////////////////////////////////////////////////////////////////// |
|
module i4002_ram #( |
parameter RAM_ARRAY_SIZE = 32 // Size of the RAM array |
) ( |
input wire sysclk, |
input wire [4:0] addr, // Address |
input wire write, // Write Enable |
input wire [3:0] data_in, // Data input to write |
output wire [3:0] data_out, // Data output (unregistered) |
|
input wire [4:0] addr2, // 2nd port address |
output wire [3:0] data2_out // 2nd port data output (unregisted) |
); |
|
// |
// Infer a 32x4 distributed dual-port RAM |
// |
// The "status" characters are stored in [16:19] |
// |
(* ram_style="distributed" *) |
reg [3:0] ram_array [0:(RAM_ARRAY_SIZE-1)]; |
always @(posedge sysclk) begin |
if (write) begin |
ram_array[addr] <= data_in; |
end |
end |
assign data_out = ram_array[addr]; |
assign data2_out = ram_array[addr2]; |
|
`ifdef XILINX_ISIM |
// Pre-initialize the RAM |
genvar i; |
generate |
for (i = 0; i < RAM_ARRAY_SIZE; i = i + 1) begin : initial_ram |
initial ram_array[i] = 4'bxxxx; |
end |
endgenerate |
`endif |
|
endmodule |
/i4002_tb.v
0,0 → 1,269
`timescale 1ns / 1ps |
`default_nettype none |
//////////////////////////////////////////////////////////////////////// |
// |
// MCS-4 i4001 RAM testbench |
// |
// This module is a testbench for the i4002 and i4002_ram modules. |
// |
// This testbench instantiates an i4002 RAM module, and enough of |
// the i4004 system bus logic to be able to test RAM access. |
// |
// This file is part of the MCS-4 project hosted at OpenCores: |
// http://www.opencores.org/cores/mcs-4/ |
// |
// Copyright © 2021 by Reece Pollack <rrpollack@opencores.org> |
// |
// These materials are provided under the Creative Commons |
// "Attribution-NonCommercial-ShareAlike" (CC BY-NC-SA) Public License. |
// They are NOT "public domain", and are protected by copyright. |
// |
// This work based on materials provided by Intel Corporation and |
// others under the same license. See the file doc/License for |
// details of this license. |
// |
//////////////////////////////////////////////////////////////////////// |
|
module i4002_tb; |
|
localparam SYSCLK_TCY = 20; // sysclk period in nanoseconds |
|
// Inputs |
reg rst; |
reg reset; |
reg cm; |
reg [4:0] ram0_addr2; |
reg [4:0] ram1_addr2; |
reg [4:0] ram2_addr2; |
reg [4:0] ram3_addr2; |
|
// Outputs |
wire [3:0] oport; |
wire [3:0] ram0_data2_out; |
wire [3:0] ram1_data2_out; |
wire [3:0] ram2_data2_out; |
wire [3:0] ram3_data2_out; |
|
// Bidirs |
wire [3:0] data; |
|
// Simulate the system clock |
reg sysclk; |
always begin |
sysclk = 1'b0; |
#(SYSCLK_TCY / 2); |
sysclk = 1'b1; |
#(SYSCLK_TCY / 2); |
end |
|
// Instantiate a 2-phase clock generator |
wire clk1, clk2; |
clockgen #( |
.SYSCLK_TCY (SYSCLK_TCY) |
) clockgen ( |
.sysclk (sysclk), |
.clk1 (clk1), |
.clk2 (clk2) |
); |
|
// Generate the 8 execution phase indicators |
wire a12, a22, a32, m12, m22, x12, x22, x32, sync; |
timing_generator timing_generator ( |
.clk1 (clk1), |
.clk2 (clk2), |
.a12 (a12), |
.a22 (a22), |
.a32 (a32), |
.m12 (m12), |
.m22 (m22), |
.x12 (x12), |
.x22 (x22), |
.x32 (x32), |
.sync (sync) |
); |
|
|
// Instantiate the Unit Under Test (UUT) |
i4002 uut ( |
.sysclk (sysclk), |
.clk1 (clk1), |
.clk2 (clk2), |
.sync (sync), |
.reset (reset), |
.cm (cm), |
.data (data), |
.oport (oport), |
.ram0_addr2 (ram0_addr2), |
.ram0_data2_out (ram0_data2_out), |
.ram1_addr2 (ram1_addr2), |
.ram1_data2_out (ram1_data2_out), |
.ram2_addr2 (ram2_addr2), |
.ram2_data2_out (ram2_data2_out), |
.ram3_addr2 (ram3_addr2), |
.ram3_data2_out (ram3_data2_out) |
); |
|
reg data_dir = 1'b0; |
reg [3:0] data_out = 4'bxxxx; |
reg [3:0] data_in = 4'bxxxx; |
assign data = data_dir ? data_out : 4'bzzzz; |
|
initial begin |
// Initialize Inputs |
rst = 1; |
reset = 1; |
cm = 0; |
ram0_addr2 = 5'h00; |
ram1_addr2 = 5'h00; |
ram2_addr2 = 5'h00; |
ram3_addr2 = 5'h00; |
|
// Wait 100 ns for global reset to finish |
#100; |
rst = 0; |
@(negedge sync); |
reset = 0; |
end |
|
// Simulate enough of a i4004 CPU to access RAM |
always @(*) begin |
data_dir = 1'b1; |
data_out = 4'bxxxx; |
cm = 1'b0; |
case (1'b1) |
a12 : task_a12; |
a22 : task_a22; |
a32 : task_a32; |
m12 : task_m12; |
m22 : task_m22; |
x12 : task_x12; |
x22 : task_x22; |
x32 : task_x32; |
default : /* default */; |
endcase |
end |
|
// |
// i4004 simulation registers |
// |
// |
reg [11:0] ip = 12'h000; |
reg [1:0] chip_num = 2'b00; |
reg [1:0] reg_num = 2'b01; |
reg [3:0] char_num = 4'ha; |
reg [3:0] acc; |
|
reg [7:0] rom[0:'hfff]; |
wire [7:0] rom_data = rom[ip]; |
initial begin |
`ifdef USE_READMEMH |
$readmemh("i4002_tb.mem", rom, 0); |
`else |
rom[ 0] = 8'hFD; // DCL |
rom[ 1] = 8'h21; // SRC R0 |
rom[ 2] = 8'hE9; // RDM |
rom[ 3] = 8'hE0; // WRM |
rom[ 4] = 8'h21; // SRC R0 |
rom[ 5] = 8'hE9; // RDM |
rom[ 6] = 8'hE0; // WRM |
rom[ 7] = 8'hFF; // HLT (sim only) |
`endif |
end |
|
// Capture OPR during the M1 phase |
reg [3:0] opr; |
always @(*) begin |
if (clk2 & m12) |
opr = data; |
end |
|
// Capture OPA during the M2 phase |
reg [3:0] opa; |
always @(*) begin |
if (clk2 & m22) |
opa = data; |
end |
|
// Instruction decode |
wire src = (opr == 4'b0010) & opa[0]; |
wire io = (opr == 4'b1110); |
wire ior = io & opa[3]; |
wire iow = io & ~opa[3]; |
wire ag = (opr == 4'b1111); |
wire iac = ag & (opa == 4'b0010); |
|
always @(*) begin |
if (clk2 & ior) begin |
acc = data; |
end |
end |
|
task task_a12(); |
begin |
data_out = ip[3:0]; |
data_dir = 1'b1; |
end |
endtask : task_a12 |
|
task task_a22(); |
begin |
data_out = ip[7:4]; |
end |
endtask : task_a22 |
|
task task_a32(); |
begin |
data_out = ip[11:8]; |
cm = 1'b1; |
end |
endtask : task_a32 |
|
task task_m12(); |
begin |
data_out = rom_data[7:4]; |
end |
endtask : task_m12 |
|
task task_m22(); |
begin |
data_out = rom_data[3:0]; |
cm = io; |
if (~reset) begin |
ip = ip + 'd1; |
end |
end |
endtask : task_m22 |
|
task task_x12(); |
begin |
data_out = opa; |
if ({opr, opa} == 8'hFF) |
$stop(); |
end |
endtask : task_x12 |
|
task task_x22(); |
begin |
if (src) begin |
data_out = {chip_num, reg_num}; |
cm = 1'b1; |
end |
if (iow) begin |
data_out = acc + 'd1; |
end |
if (ior) begin |
data_dir = 1'b0; |
end |
end |
endtask : task_x22 |
|
task task_x32(); |
begin |
if (src) begin |
data_out = char_num; |
end |
end |
endtask : task_x32 |
|
|
endmodule |